commit 2a9d8ce41641af28a0a0894b26dab9b93ac05004 Author: Magnus von Wachenfeldt Date: Fri Dec 4 10:23:49 2015 +0100 move to github diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0698168 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# User-specific files +*.suo +*.user + +# Build results +Debug/ +Release/ +*.ncb +*.aps +*.obj +.builds +TestResult.xml +output/ +*.vshost.exe +*.Publish.xml + +# ReSharper is a .NET coding add-in +_ReSharper* +*.ReSharper + +# Others +**/[Bb]in +**/[Oo]bj +sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +*.log +*.log.20* +AssemblyCache.config +GravityForce/Content/obj/GravityForceContent.contentproj.XactOutput.FileList.txt diff --git a/Library/ICSharpCode.SharpZipLib.dll b/Library/ICSharpCode.SharpZipLib.dll new file mode 100644 index 0000000..e829ebf Binary files /dev/null and b/Library/ICSharpCode.SharpZipLib.dll differ diff --git a/Library/NAudio.WindowsMediaFormat.dll b/Library/NAudio.WindowsMediaFormat.dll new file mode 100644 index 0000000..ca046ea Binary files /dev/null and b/Library/NAudio.WindowsMediaFormat.dll differ diff --git a/Library/NAudio.dll b/Library/NAudio.dll new file mode 100644 index 0000000..58029ff Binary files /dev/null and b/Library/NAudio.dll differ diff --git a/Library/NVorbis.NAudioSupport.dll b/Library/NVorbis.NAudioSupport.dll new file mode 100644 index 0000000..b4c3d60 Binary files /dev/null and b/Library/NVorbis.NAudioSupport.dll differ diff --git a/Library/NVorbis.OpenTKSupport.dll b/Library/NVorbis.OpenTKSupport.dll new file mode 100644 index 0000000..305c830 Binary files /dev/null and b/Library/NVorbis.OpenTKSupport.dll differ diff --git a/Library/NVorbis.dll b/Library/NVorbis.dll new file mode 100644 index 0000000..282aca6 Binary files /dev/null and b/Library/NVorbis.dll differ diff --git a/Lidgren.Network/Documentation/ChangedFromV2.txt b/Lidgren.Network/Documentation/ChangedFromV2.txt new file mode 100644 index 0000000..41c274f --- /dev/null +++ b/Lidgren.Network/Documentation/ChangedFromV2.txt @@ -0,0 +1,10 @@ + + +* The NetBuffer object is gone; instead there are NetOutgoingMessage and NetIncomingMessage objects + +* No need to allocate a read buffer before calling ReadMessage + + + + + diff --git a/Lidgren.Network/Documentation/Discovery.html b/Lidgren.Network/Documentation/Discovery.html new file mode 100644 index 0000000..19261fd --- /dev/null +++ b/Lidgren.Network/Documentation/Discovery.html @@ -0,0 +1,113 @@ + + + + + Peer/server discovery + + + + + + +
+

Peer/server discovery

+

+ Peer discovery is the process of clients detecting what servers are available. Discovery requests can be made in two ways; + locally as a broadcast, which will send a signal to all peers on your subnet. Secondly you can contact an ip address directly + and query it if a server is running. +

+

Responding to discovery requests are done in the same way regardless of how the request is made.

+ +

Here's how to do on the client side; ie. the side which makes a request:

+
+
// Enable DiscoveryResponse messages
+
config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse);
+
 
+
// Emit a discovery signal
+
Client.DiscoverLocalPeers(14242);
+
+ +

This will send a discovery signal to your subnet; Here's how to receive the signal on the server side, and send a response back to the client:

+ +
+
// Enable DiscoveryRequest messages
+
config.EnableMessageType(NetIncomingMessageType.DiscoveryRequest);
+
 
+
// Standard message reading loop
+
while ((inc = Server.ReadMessage()) != null)
+
{
+
    switch (inc.MessageType)
+
    {
+
        case NetIncomingMessageType.DiscoveryRequest:
+
 
+
            // Create a response and write some example data to it
+
            NetOutgoingMessage response = Server.CreateMessage();
+
            response.Write("My server name");
+
 
+
            // Send the response to the sender of the request
+
            Server.SendDiscoveryResponse(response, inc.SenderEndpoint);
+
            break;
+
+ +

When the response then reaches the client, you can read the data you wrote on the server:

+ +
+
// Standard message reading loop
+
while ((inc = Client.ReadMessage()) != null)
+
{
+
    switch (inc.MessageType)
+
    {
+
        case NetIncomingMessageType.DiscoveryResponse:
+
 
+
            Console.WriteLine("Found server at " + inc.SenderEndpoint + " name: " + inc.ReadString());
+
            break;
+
+
+ + diff --git a/Lidgren.Network/Documentation/Improvements.txt b/Lidgren.Network/Documentation/Improvements.txt new file mode 100644 index 0000000..5ef9228 --- /dev/null +++ b/Lidgren.Network/Documentation/Improvements.txt @@ -0,0 +1,22 @@ + +Improvements over last version of library: + +* New delivery type: Reliable sequenced (Lost packets are resent but late arrivals are dropped) +* Disconnects and shutdown requests are now queued properly, so calling shutdown will still send any queued messages before shutting down +* All messages are pooled/recycled for zero garbage +* Reduced CPU usage and lower latencies (in the <1 ms range, but still) due to better socket polling +* All public members of NetPeer/NetConnection are completely thread safe +* Larger number of delivery channels +* More exact roundtrip measurement +* Method serialize entire objects via reflection +* Unique identifier now exists for all peers/connections +* More flexible peer discovery; filters possible and arbitrary data can be sent with response +* Much better protection against malformed messages crashing the app + +API enhancements: +* NetPeerConfiguration immutable properties now locked once NetPeer is initialized +* Messages cannot be send twice by accident +* Impossible to confuse sending and receiving buffers since they're different classes +* No more confusion if user should create a buffer or preallocate and reuse + + diff --git a/Lidgren.Network/Documentation/PacketLayout.txt b/Lidgren.Network/Documentation/PacketLayout.txt new file mode 100644 index 0000000..d709619 --- /dev/null +++ b/Lidgren.Network/Documentation/PacketLayout.txt @@ -0,0 +1,17 @@ + +PER MESSAGE: +7 bits - NetMessageType +1 bit - Is a message fragment? + +[8 bits NetMessageLibraryType, if NetMessageType == Library] + +[16 bits sequence number, if NetMessageType >= UserSequenced] + +8/16 bits - Payload length in bits (variable size ushort) + +[16 bits fragments group id, if fragmented] +[16 bits fragments total count, if fragmented] +[16 bits fragment number, if fragmented] + +[x - Payload] if length > 0 + diff --git a/Lidgren.Network/Documentation/SimulatingBadNetwork.html b/Lidgren.Network/Documentation/SimulatingBadNetwork.html new file mode 100644 index 0000000..80c6a18 --- /dev/null +++ b/Lidgren.Network/Documentation/SimulatingBadNetwork.html @@ -0,0 +1,115 @@ + + + + + Lidgren tutorial + + + + + + +
+

Simulating bad network conditions

+

+ On the internet, your packets are likely to run in to all kinds of trouble. They will be delayed and lost and they might even arrive multiple times at the destination. Lidgren has a few option to simulate how your application or game will react when this happens.
+ They are all configured using the NetPeerConfiguration class - these properties exists:

+

+

+ + + + + + + + + + + + + + + + + + + + + + +
+ SimulatedLoss + +   + + This is a float which simulates lost packets. A value of 0 will disable this feature, a value of 0.5f will make half of your sent packets disappear, chosen randomly. Note that packets may contain several messages - this is the amount of packets lost. +
+   +
+ SimulatedDuplicatesChance + +   + + This is a float which determines the chance that a packet will be duplicated at the destination. 0 means no packets will be duplicated, 0.5f means that on average, every other packet will be duplicated. +
+   +
+ SimulatedMinimumLatency
+ SimulatedRandomLatency +
+   + + These two properties control simulating delay of packets in seconds (not milliseconds, use 0.05 for 50 ms of lag). They work on top of the actual network delay and the total delay will be:
+ Actual one way latency + SimulatedMinimumLatency + [Randomly per packet 0 to SimulatedRandomLatency seconds] +
+ +
+

It's recommended to assume symmetric condtions and configure server and client with the same simulation settings.

+

Simulating bad network conditions only works in DEBUG builds.

+ +
+ + diff --git a/Lidgren.Network/Documentation/TODO.txt b/Lidgren.Network/Documentation/TODO.txt new file mode 100644 index 0000000..e8a7597 --- /dev/null +++ b/Lidgren.Network/Documentation/TODO.txt @@ -0,0 +1,17 @@ + +Completed features: +* Message coalescing +* Peer, connection statistics +* Lag, loss and duplication simulation for testing +* Connection approval +* Throttling +* Clock synchronization to detect jitter per packet (NetTime.RemoteNow) +* Peer discovery +* Message fragmentation + +Missing features: +* Receipts 25% done, need design +* More realistic lag/loss (lumpy) +* Detect estimated packet loss +* More advanced ack packet + diff --git a/Lidgren.Network/Documentation/Tutorial.html b/Lidgren.Network/Documentation/Tutorial.html new file mode 100644 index 0000000..af1c7d7 --- /dev/null +++ b/Lidgren.Network/Documentation/Tutorial.html @@ -0,0 +1,206 @@ + + + + + Lidgren basics tutorial + + + + + + +
+

+ Lidgren basics

+

+ Lidgren network library is all about messages. There are two types of messages:

+
  • Library messages telling you things like a peer has connected or diagnostics messages (warnings, errors) when unexpected things happen.
  • +
  • Data messages which is data sent from a remote (connected or unconnected) peer.
  • +

    + The base class for establishing connections, receiving and sending message are the NetPeer class. Using it you can make a peer-to-peer network, but if you are creating a server/client topology there are special classes called NetServer and NetClient. They inherit NetPeer but sets some defaults and includes some helper methods/properties.

    +

    + Here's how to set up a NetServer:

    +
    +
    NetPeerConfiguration config = new NetPeerConfiguration("MyExampleName");
    +
    config.Port = 14242;
    +
     
    +
    NetServer server = new NetServer(config);
    +
    server.Start();
    +
    +

    + The code above first creates a configuration. It has lots of properties you can change, but the default values should be pretty good for most applications. The string you provide in the constructor (MyExampleName) is an identifier to distinquish it from other applications using the lidgren library. Just make sure you use the same string in both server and client - or you will be unable to communicate between them.

    +

    + Secondly we've set the local port the server should listen to. This is the port number we tell the client(s) what port number to connect to. The local port can be set for a client too, but it's not needed and not recommended.

    +

    + Thirdly we create our server object and fourth we Start() it. Starting the server will create a new network thread and bind to a socket and start listening for connections.

    +

    + Early on we spoke about messages; now is the time to start receiving and sending some. Here's a code snippet for receiving messages:

    +
    +
    NetIncomingMessage msg;
    +
    while ((msg = server.ReadMessage()) != null)
    +
    {
    +
        switch (msg.MessageType)
    +
        {
    +
            case NetIncomingMessageType.VerboseDebugMessage:
    +
            case NetIncomingMessageType.DebugMessage:
    +
            case NetIncomingMessageType.WarningMessage:
    +
            case NetIncomingMessageType.ErrorMessage:
    +
                Console.WriteLine(msg.ReadString());
    +
                break;
    +
            default:
    +
                Console.WriteLine("Unhandled type: " + msg.MessageType);
    +
                break;
    +
        }
    +
        server.Recycle(msg);
    +
    }
    +
    +

    + So, lets dissect the above code. First we declare a NetIncomingMessage, which is the type of incoming messages. Then we read a message and handles it, looping back as long as there are messages to fetch. For each message we find, we switch on sometime called MessageType - it's a description what the message contains. In this code example we only catch messages of type VerboseDebugMessage, DebugMessage, WarningMessage and ErrorMessage. All those four types are emitted by the library to inform about various events. They all contains a single string, so we use the method ReadString() to extract a copy of that string and print it in the console.

    +

    + Reading data will increment the internal message pointer so you can read subsequent data using the Read*() methods.

    +

    + For all other message type we just print that it's currently unhandled.

    +

    + Finally, we recycle the message after we're done with it - this will enable the library to reuse the object and create less garbage.

    +

    + Sending messages are even easier:

    +
    +
    NetOutgoingMessage sendMsg = server.CreateMessage();
    +
    sendMsg.Write("Hello");
    +
    sendMsg.Write(42);
    +
     
    +
    server.SendMessage(sendMsg, recipient, NetDeliveryMethod.ReliableOrdered);
    +
    +

    + The above code first creates a new message, or uses a recycled message, which is why it's not possible to just create a message using new(). It then writes a string ("Hello") and an integer (System.Int32, 4 bytes in size) to the message.

    +

    + Then the message is sent using the SendMessage() method. The first argument is the message to send, the second argument is the recipient connection - which we'll not go into detail about just yet - and the third argument are HOW to deliver the message, or rather how to behave if network conditions are bad and a packet gets lost, duplicated or reordered.

    +

    + There are five delivery methods available:

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Unreliable + +   + + This is just UDP. Messages can be lost, received more than once and messages sent after other messages may be received before them. +
    +   +
    + UnreliableSequenced + +   + + Using this delivery method messages can still be lost; but you're protected against duplicated messages and if a message arrives late; that is, if a message sent after this one has already been received - it will be dropped. This means you will never receive "older" data than what you already have received. +
    +   +
    + ReliableUnordered + +   + + This delivery method ensures that every message sent will be received eventually. It does not however guarantee what order they will be received; late messages may be delivered before older ones. +
    +   +
    + ReliableSequenced + +   + + This delivery method is similar to UnreliableSequenced; except that is guarantees that SOME messages will be received - if you only send one message - it will be received. If you sent two messages quickly, and they get reordered in transit, only the newest message will be received - but at least ONE of them will be received guaranteed. +
    +   +
    ReliableOrdered  + This delivery method guarantees that messages will always be received in the exact order they were sent. +
    +
    +

    + Here's how to read and decode the message above:

    +
    +
    NetIncomingMessage incMsg = server.ReadMessage();
    +
    string str = incMsg.ReadString();
    +
    int a = incMsg.ReadInt32();
    +
    +
    + + diff --git a/Lidgren.Network/Encryption/NetAESEncryption.cs b/Lidgren.Network/Encryption/NetAESEncryption.cs new file mode 100644 index 0000000..0fec6d5 --- /dev/null +++ b/Lidgren.Network/Encryption/NetAESEncryption.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetAESEncryption : NetCryptoProviderBase + { + public NetAESEncryption(NetPeer peer) + : base(peer, new AesCryptoServiceProvider()) + { + } + + public NetAESEncryption(NetPeer peer, string key) + : base(peer, new AesCryptoServiceProvider()) + { + SetKey(key); + } + + public NetAESEncryption(NetPeer peer, byte[] data, int offset, int count) + : base(peer, new AesCryptoServiceProvider()) + { + SetKey(data, offset, count); + } + } +} diff --git a/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs b/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs new file mode 100644 index 0000000..f16aeba --- /dev/null +++ b/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + /// + /// Base for a non-threadsafe encryption class + /// + public abstract class NetBlockEncryptionBase : NetEncryption + { + // temporary space for one block to avoid reallocating every time + private byte[] m_tmp; + + /// + /// Block size in bytes for this cipher + /// + public abstract int BlockSize { get; } + + /// + /// NetBlockEncryptionBase constructor + /// + public NetBlockEncryptionBase(NetPeer peer) + : base(peer) + { + m_tmp = new byte[BlockSize]; + } + + /// + /// Encrypt am outgoing message with this algorithm; no writing can be done to the message after encryption, or message will be corrupted + /// + public override bool Encrypt(NetOutgoingMessage msg) + { + int payloadBitLength = msg.LengthBits; + int numBytes = msg.LengthBytes; + int blockSize = BlockSize; + int numBlocks = (int)Math.Ceiling((double)numBytes / (double)blockSize); + int dstSize = numBlocks * blockSize; + + msg.EnsureBufferSize(dstSize * 8 + (4 * 8)); // add 4 bytes for payload length at end + msg.LengthBits = dstSize * 8; // length will automatically adjust +4 bytes when payload length is written + + for(int i=0;i + /// Decrypt an incoming message encrypted with corresponding Encrypt + /// + /// message to decrypt + /// true if successful; false if failed + public override bool Decrypt(NetIncomingMessage msg) + { + int numEncryptedBytes = msg.LengthBytes - 4; // last 4 bytes is true bit length + int blockSize = BlockSize; + int numBlocks = numEncryptedBytes / blockSize; + if (numBlocks * blockSize != numEncryptedBytes) + return false; + + for (int i = 0; i < numBlocks; i++) + { + DecryptBlock(msg.m_data, (i * blockSize), m_tmp); + Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length); + } + + // read 32 bits of true payload length + uint realSize = NetBitWriter.ReadUInt32(msg.m_data, 32, (numEncryptedBytes * 8)); + msg.m_bitLength = (int)realSize; + return true; + } + + /// + /// Encrypt a block of bytes + /// + protected abstract void EncryptBlock(byte[] source, int sourceOffset, byte[] destination); + + /// + /// Decrypt a block of bytes + /// + protected abstract void DecryptBlock(byte[] source, int sourceOffset, byte[] destination); + } +} diff --git a/Lidgren.Network/Encryption/NetCryptoProviderBase.cs b/Lidgren.Network/Encryption/NetCryptoProviderBase.cs new file mode 100644 index 0000000..9c3e7bc --- /dev/null +++ b/Lidgren.Network/Encryption/NetCryptoProviderBase.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public abstract class NetCryptoProviderBase : NetEncryption + { + protected SymmetricAlgorithm m_algorithm; + + public NetCryptoProviderBase(NetPeer peer, SymmetricAlgorithm algo) + : base(peer) + { + m_algorithm = algo; + m_algorithm.GenerateKey(); + m_algorithm.GenerateIV(); + } + + public override void SetKey(byte[] data, int offset, int count) + { + int len = m_algorithm.Key.Length; + var key = new byte[len]; + for (int i = 0; i < len; i++) + key[i] = data[offset + (i % count)]; + m_algorithm.Key = key; + + len = m_algorithm.IV.Length; + key = new byte[len]; + for (int i = 0; i < len; i++) + key[len - 1 - i] = data[offset + (i % count)]; + m_algorithm.IV = key; + } + + public override bool Encrypt(NetOutgoingMessage msg) + { + int unEncLenBits = msg.LengthBits; + + var ms = new MemoryStream(); + var cs = new CryptoStream(ms, m_algorithm.CreateEncryptor(), CryptoStreamMode.Write); + cs.Write(msg.m_data, 0, msg.LengthBytes); + cs.Close(); + + // get results + var arr = ms.ToArray(); + ms.Close(); + + msg.EnsureBufferSize((arr.Length + 4) * 8); + msg.LengthBits = 0; // reset write pointer + msg.Write((uint)unEncLenBits); + msg.Write(arr); + msg.LengthBits = (arr.Length + 4) * 8; + + return true; + } + + public override bool Decrypt(NetIncomingMessage msg) + { + int unEncLenBits = (int)msg.ReadUInt32(); + + var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4); + var cs = new CryptoStream(ms, m_algorithm.CreateDecryptor(), CryptoStreamMode.Read); + + var result = m_peer.GetStorage(unEncLenBits); + cs.Read(result, 0, NetUtility.BytesToHoldBits(unEncLenBits)); + cs.Close(); + + // TODO: recycle existing msg + + msg.m_data = result; + msg.m_bitLength = unEncLenBits; + msg.m_readPosition = 0; + + return true; + } + } +} diff --git a/Lidgren.Network/Encryption/NetDESEncryption.cs b/Lidgren.Network/Encryption/NetDESEncryption.cs new file mode 100644 index 0000000..b6ab813 --- /dev/null +++ b/Lidgren.Network/Encryption/NetDESEncryption.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetDESEncryption : NetCryptoProviderBase + { + public NetDESEncryption(NetPeer peer) + : base(peer, new DESCryptoServiceProvider()) + { + } + + public NetDESEncryption(NetPeer peer, string key) + : base(peer, new DESCryptoServiceProvider()) + { + SetKey(key); + } + + public NetDESEncryption(NetPeer peer, byte[] data, int offset, int count) + : base(peer, new DESCryptoServiceProvider()) + { + SetKey(data, offset, count); + } + } +} diff --git a/Lidgren.Network/Encryption/NetEncryption.cs b/Lidgren.Network/Encryption/NetEncryption.cs new file mode 100644 index 0000000..fea1e7c --- /dev/null +++ b/Lidgren.Network/Encryption/NetEncryption.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + /// + /// Interface for an encryption algorithm + /// + public abstract class NetEncryption + { + /// + /// NetPeer + /// + protected NetPeer m_peer; + + /// + /// Constructor + /// + public NetEncryption(NetPeer peer) + { + if (peer == null) + throw new NetException("Peer must not be null"); + m_peer = peer; + } + + public void SetKey(string str) + { + var bytes = System.Text.Encoding.ASCII.GetBytes(str); + SetKey(bytes, 0, bytes.Length); + } + + public abstract void SetKey(byte[] data, int offset, int count); + + /// + /// Encrypt an outgoing message in place + /// + public abstract bool Encrypt(NetOutgoingMessage msg); + + /// + /// Decrypt an incoming message in place + /// + public abstract bool Decrypt(NetIncomingMessage msg); + } +} diff --git a/Lidgren.Network/Encryption/NetRC2Encryption.cs b/Lidgren.Network/Encryption/NetRC2Encryption.cs new file mode 100644 index 0000000..a2d0c23 --- /dev/null +++ b/Lidgren.Network/Encryption/NetRC2Encryption.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetRC2Encryption : NetCryptoProviderBase + { + public NetRC2Encryption(NetPeer peer) + : base(peer, new RC2CryptoServiceProvider()) + { + } + + public NetRC2Encryption(NetPeer peer, string key) + : base(peer, new RC2CryptoServiceProvider()) + { + SetKey(key); + } + + public NetRC2Encryption(NetPeer peer, byte[] data, int offset, int count) + : base(peer, new RC2CryptoServiceProvider()) + { + SetKey(data, offset, count); + } + } +} diff --git a/Lidgren.Network/Encryption/NetTripleDESEncryption.cs b/Lidgren.Network/Encryption/NetTripleDESEncryption.cs new file mode 100644 index 0000000..58044d5 --- /dev/null +++ b/Lidgren.Network/Encryption/NetTripleDESEncryption.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetTripleDESEncryption : NetCryptoProviderBase + { + public NetTripleDESEncryption(NetPeer peer) + : base(peer, new TripleDESCryptoServiceProvider()) + { + } + + public NetTripleDESEncryption(NetPeer peer, string key) + : base(peer, new TripleDESCryptoServiceProvider()) + { + SetKey(key); + } + + public NetTripleDESEncryption(NetPeer peer, byte[] data, int offset, int count) + : base(peer, new TripleDESCryptoServiceProvider()) + { + SetKey(data, offset, count); + } + } +} diff --git a/Lidgren.Network/Encryption/NetXorEncryption.cs b/Lidgren.Network/Encryption/NetXorEncryption.cs new file mode 100644 index 0000000..04d8e50 --- /dev/null +++ b/Lidgren.Network/Encryption/NetXorEncryption.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Example class; not very good encryption + /// + public class NetXorEncryption : NetEncryption + { + private byte[] m_key; + + /// + /// NetXorEncryption constructor + /// + public NetXorEncryption(NetPeer peer, byte[] key) + : base(peer) + { + m_key = key; + } + + public override void SetKey(byte[] data, int offset, int count) + { + m_key = new byte[count]; + Array.Copy(data, offset, m_key, 0, count); + } + + /// + /// NetXorEncryption constructor + /// + public NetXorEncryption(NetPeer peer, string key) + : base(peer) + { + m_key = Encoding.UTF8.GetBytes(key); + } + + /// + /// Encrypt an outgoing message + /// + public override bool Encrypt(NetOutgoingMessage msg) + { + int numBytes = msg.LengthBytes; + for (int i = 0; i < numBytes; i++) + { + int offset = i % m_key.Length; + msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + } + return true; + } + + /// + /// Decrypt an incoming message + /// + public override bool Decrypt(NetIncomingMessage msg) + { + int numBytes = msg.LengthBytes; + for (int i = 0; i < numBytes; i++) + { + int offset = i % m_key.Length; + msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + } + return true; + } + } +} diff --git a/Lidgren.Network/Encryption/NetXteaEncryption.cs b/Lidgren.Network/Encryption/NetXteaEncryption.cs new file mode 100644 index 0000000..2d58cfc --- /dev/null +++ b/Lidgren.Network/Encryption/NetXteaEncryption.cs @@ -0,0 +1,154 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Security.Cryptography; +using System.Text; +using System.Security; + +namespace Lidgren.Network +{ + /// + /// Methods to encrypt and decrypt data using the XTEA algorithm + /// + public sealed class NetXtea : NetBlockEncryptionBase + { + private const int c_blockSize = 8; + private const int c_keySize = 16; + private const int c_delta = unchecked((int)0x9E3779B9); + + private readonly int m_numRounds; + private readonly uint[] m_sum0; + private readonly uint[] m_sum1; + + /// + /// Gets the block size for this cipher + /// + public override int BlockSize { get { return c_blockSize; } } + + /// + /// 16 byte key + /// + public NetXtea(NetPeer peer, byte[] key, int rounds) + : base(peer) + { + if (key.Length < c_keySize) + throw new NetException("Key too short!"); + + m_numRounds = rounds; + m_sum0 = new uint[m_numRounds]; + m_sum1 = new uint[m_numRounds]; + uint[] tmp = new uint[8]; + + int num2; + int index = num2 = 0; + while (index < 4) + { + tmp[index] = BitConverter.ToUInt32(key, num2); + index++; + num2 += 4; + } + for (index = num2 = 0; index < 32; index++) + { + m_sum0[index] = ((uint)num2) + tmp[num2 & 3]; + num2 += -1640531527; + m_sum1[index] = ((uint)num2) + tmp[(num2 >> 11) & 3]; + } + } + + /// + /// 16 byte key + /// + public NetXtea(NetPeer peer, byte[] key) + : this(peer, key, 32) + { + } + + /// + /// String to hash for key + /// + public NetXtea(NetPeer peer, string key) + : this(peer, NetUtility.CreateSHA1Hash(key), 32) + { + } + + public override void SetKey(byte[] data, int offset, int length) + { + var key = NetUtility.CreateSHA1Hash(data, offset, length); + NetException.Assert(key.Length == 16); + SetKey(key, 0, 16); + } + + /// + /// Encrypts a block of bytes + /// + protected override void EncryptBlock(byte[] source, int sourceOffset, byte[] destination) + { + uint v0 = BytesToUInt(source, sourceOffset); + uint v1 = BytesToUInt(source, sourceOffset + 4); + + for (int i = 0; i != m_numRounds; i++) + { + v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i]; + v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i]; + } + + UIntToBytes(v0, destination, 0); + UIntToBytes(v1, destination, 0 + 4); + + return; + } + + /// + /// Decrypts a block of bytes + /// + protected override void DecryptBlock(byte[] source, int sourceOffset, byte[] destination) + { + // Pack bytes into integers + uint v0 = BytesToUInt(source, sourceOffset); + uint v1 = BytesToUInt(source, sourceOffset + 4); + + for (int i = m_numRounds - 1; i >= 0; i--) + { + v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i]; + v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i]; + } + + UIntToBytes(v0, destination, 0); + UIntToBytes(v1, destination, 0 + 4); + + return; + } + + private static uint BytesToUInt(byte[] bytes, int offset) + { + uint retval = (uint)(bytes[offset] << 24); + retval |= (uint)(bytes[++offset] << 16); + retval |= (uint)(bytes[++offset] << 8); + return (retval | bytes[++offset]); + } + + private static void UIntToBytes(uint value, byte[] destination, int destinationOffset) + { + destination[destinationOffset++] = (byte)(value >> 24); + destination[destinationOffset++] = (byte)(value >> 16); + destination[destinationOffset++] = (byte)(value >> 8); + destination[destinationOffset++] = (byte)value; + } + } +} diff --git a/Lidgren.Network/Lidgren.Network.csproj b/Lidgren.Network/Lidgren.Network.csproj new file mode 100644 index 0000000..1fc4d23 --- /dev/null +++ b/Lidgren.Network/Lidgren.Network.csproj @@ -0,0 +1,171 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8} + Library + Properties + Lidgren.Network + Lidgren.Network + v4.5.1 + 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + bin\Debug\Lidgren.Network.XML + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + false + + + + + + + + + + + Code + + + + Code + + + + + Code + + + Code + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/Lidgren.Network/NamespaceDoc.cs b/Lidgren.Network/NamespaceDoc.cs new file mode 100644 index 0000000..b7d6db0 --- /dev/null +++ b/Lidgren.Network/NamespaceDoc.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Lidgren Network Library + /// + internal class NamespaceDoc + { + // + } +} diff --git a/Lidgren.Network/NetBigInteger.cs b/Lidgren.Network/NetBigInteger.cs new file mode 100644 index 0000000..3f5c2db --- /dev/null +++ b/Lidgren.Network/NetBigInteger.cs @@ -0,0 +1,2337 @@ +using System; +using System.Text; +using System.Collections; +using System.Diagnostics; +using System.Globalization; + +namespace Lidgren.Network +{ + /// + /// Big integer class based on BouncyCastle (http://www.bouncycastle.org) big integer code + /// + internal class NetBigInteger + { + private const long IMASK = 0xffffffffL; + private const ulong UIMASK = (ulong)IMASK; + + private static readonly int[] ZeroMagnitude = new int[0]; + private static readonly byte[] ZeroEncoding = new byte[0]; + + public static readonly NetBigInteger Zero = new NetBigInteger(0, ZeroMagnitude, false); + public static readonly NetBigInteger One = createUValueOf(1); + public static readonly NetBigInteger Two = createUValueOf(2); + public static readonly NetBigInteger Three = createUValueOf(3); + public static readonly NetBigInteger Ten = createUValueOf(10); + + private const int chunk2 = 1; + private static readonly NetBigInteger radix2 = ValueOf(2); + private static readonly NetBigInteger radix2E = radix2.Pow(chunk2); + + private const int chunk10 = 19; + private static readonly NetBigInteger radix10 = ValueOf(10); + private static readonly NetBigInteger radix10E = radix10.Pow(chunk10); + + private const int chunk16 = 16; + private static readonly NetBigInteger radix16 = ValueOf(16); + private static readonly NetBigInteger radix16E = radix16.Pow(chunk16); + + private const int BitsPerByte = 8; + private const int BitsPerInt = 32; + private const int BytesPerInt = 4; + + private int m_sign; // -1 means -ve; +1 means +ve; 0 means 0; + private int[] m_magnitude; // array of ints with [0] being the most significant + private int m_numBits = -1; // cache BitCount() value + private int m_numBitLength = -1; // cache calcBitLength() value + private long m_quote = -1L; // -m^(-1) mod b, b = 2^32 (see Montgomery mult.) + + private static int GetByteLength( + int nBits) + { + return (nBits + BitsPerByte - 1) / BitsPerByte; + } + + private NetBigInteger() + { + } + + private NetBigInteger( + int signum, + int[] mag, + bool checkMag) + { + if (checkMag) + { + int i = 0; + while (i < mag.Length && mag[i] == 0) + { + ++i; + } + + if (i == mag.Length) + { + // sign = 0; + m_magnitude = ZeroMagnitude; + } + else + { + m_sign = signum; + + if (i == 0) + { + m_magnitude = mag; + } + else + { + // strip leading 0 words + m_magnitude = new int[mag.Length - i]; + Array.Copy(mag, i, m_magnitude, 0, m_magnitude.Length); + } + } + } + else + { + m_sign = signum; + m_magnitude = mag; + } + } + + public NetBigInteger( + string value) + : this(value, 10) + { + } + + public NetBigInteger( + string str, + int radix) + { + if (str.Length == 0) + throw new FormatException("Zero length BigInteger"); + + NumberStyles style; + int chunk; + NetBigInteger r; + NetBigInteger rE; + + switch (radix) + { + case 2: + // Is there anyway to restrict to binary digits? + style = NumberStyles.Integer; + chunk = chunk2; + r = radix2; + rE = radix2E; + break; + case 10: + // This style seems to handle spaces and minus sign already (our processing redundant?) + style = NumberStyles.Integer; + chunk = chunk10; + r = radix10; + rE = radix10E; + break; + case 16: + // TODO Should this be HexNumber? + style = NumberStyles.AllowHexSpecifier; + chunk = chunk16; + r = radix16; + rE = radix16E; + break; + default: + throw new FormatException("Only bases 2, 10, or 16 allowed"); + } + + + int index = 0; + m_sign = 1; + + if (str[0] == '-') + { + if (str.Length == 1) + throw new FormatException("Zero length BigInteger"); + + m_sign = -1; + index = 1; + } + + // strip leading zeros from the string str + while (index < str.Length && Int32.Parse(str[index].ToString(), style) == 0) + { + index++; + } + + if (index >= str.Length) + { + // zero value - we're done + m_sign = 0; + m_magnitude = ZeroMagnitude; + return; + } + + ////// + // could we work out the max number of ints required to store + // str.Length digits in the given base, then allocate that + // storage in one hit?, then Generate the magnitude in one hit too? + ////// + + NetBigInteger b = Zero; + + + int next = index + chunk; + + if (next <= str.Length) + { + do + { + string s = str.Substring(index, chunk); + ulong i = ulong.Parse(s, style); + NetBigInteger bi = createUValueOf(i); + + switch (radix) + { + case 2: + if (i > 1) + throw new FormatException("Bad character in radix 2 string: " + s); + + b = b.ShiftLeft(1); + break; + case 16: + b = b.ShiftLeft(64); + break; + default: + b = b.Multiply(rE); + break; + } + + b = b.Add(bi); + + index = next; + next += chunk; + } + while (next <= str.Length); + } + + if (index < str.Length) + { + string s = str.Substring(index); + ulong i = ulong.Parse(s, style); + NetBigInteger bi = createUValueOf(i); + + if (b.m_sign > 0) + { + if (radix == 2) + { + // NB: Can't reach here since we are parsing one char at a time + Debug.Assert(false); + } + else if (radix == 16) + { + b = b.ShiftLeft(s.Length << 2); + } + else + { + b = b.Multiply(r.Pow(s.Length)); + } + + b = b.Add(bi); + } + else + { + b = bi; + } + } + + // Note: This is the previous (slower) algorithm + // while (index < value.Length) + // { + // char c = value[index]; + // string s = c.ToString(); + // int i = Int32.Parse(s, style); + // + // b = b.Multiply(r).Add(ValueOf(i)); + // index++; + // } + + m_magnitude = b.m_magnitude; + } + + public NetBigInteger( + byte[] bytes) + : this(bytes, 0, bytes.Length) + { + } + + public NetBigInteger( + byte[] bytes, + int offset, + int length) + { + if (length == 0) + throw new FormatException("Zero length BigInteger"); + if ((sbyte)bytes[offset] < 0) + { + m_sign = -1; + + int end = offset + length; + + int iBval; + // strip leading sign bytes + for (iBval = offset; iBval < end && ((sbyte)bytes[iBval] == -1); iBval++) + { + } + + if (iBval >= end) + { + m_magnitude = One.m_magnitude; + } + else + { + int numBytes = end - iBval; + byte[] inverse = new byte[numBytes]; + + int index = 0; + while (index < numBytes) + { + inverse[index++] = (byte)~bytes[iBval++]; + } + + Debug.Assert(iBval == end); + + while (inverse[--index] == byte.MaxValue) + { + inverse[index] = byte.MinValue; + } + + inverse[index]++; + + m_magnitude = MakeMagnitude(inverse, 0, inverse.Length); + } + } + else + { + // strip leading zero bytes and return magnitude bytes + m_magnitude = MakeMagnitude(bytes, offset, length); + m_sign = m_magnitude.Length > 0 ? 1 : 0; + } + } + + private static int[] MakeMagnitude( + byte[] bytes, + int offset, + int length) + { + int end = offset + length; + + // strip leading zeros + int firstSignificant; + for (firstSignificant = offset; firstSignificant < end + && bytes[firstSignificant] == 0; firstSignificant++) + { + } + + if (firstSignificant >= end) + { + return ZeroMagnitude; + } + + int nInts = (end - firstSignificant + 3) / BytesPerInt; + int bCount = (end - firstSignificant) % BytesPerInt; + if (bCount == 0) + { + bCount = BytesPerInt; + } + + if (nInts < 1) + { + return ZeroMagnitude; + } + + int[] mag = new int[nInts]; + + int v = 0; + int magnitudeIndex = 0; + for (int i = firstSignificant; i < end; ++i) + { + v <<= 8; + v |= bytes[i] & 0xff; + bCount--; + if (bCount <= 0) + { + mag[magnitudeIndex] = v; + magnitudeIndex++; + bCount = BytesPerInt; + v = 0; + } + } + + if (magnitudeIndex < mag.Length) + { + mag[magnitudeIndex] = v; + } + + return mag; + } + + public NetBigInteger( + int sign, + byte[] bytes) + : this(sign, bytes, 0, bytes.Length) + { + } + + public NetBigInteger( + int sign, + byte[] bytes, + int offset, + int length) + { + if (sign < -1 || sign > 1) + throw new FormatException("Invalid sign value"); + + if (sign == 0) + { + //sign = 0; + m_magnitude = ZeroMagnitude; + } + else + { + // copy bytes + m_magnitude = MakeMagnitude(bytes, offset, length); + m_sign = m_magnitude.Length < 1 ? 0 : sign; + } + } + + public NetBigInteger Abs() + { + return m_sign >= 0 ? this : Negate(); + } + + // return a = a + b - b preserved. + private static int[] AddMagnitudes( + int[] a, + int[] b) + { + int tI = a.Length - 1; + int vI = b.Length - 1; + long m = 0; + + while (vI >= 0) + { + m += ((long)(uint)a[tI] + (long)(uint)b[vI--]); + a[tI--] = (int)m; + m = (long)((ulong)m >> 32); + } + + if (m != 0) + { + while (tI >= 0 && ++a[tI--] == 0) + { + } + } + + return a; + } + + public NetBigInteger Add( + NetBigInteger value) + { + if (m_sign == 0) + return value; + + if (m_sign != value.m_sign) + { + if (value.m_sign == 0) + return this; + + if (value.m_sign < 0) + return Subtract(value.Negate()); + + return value.Subtract(Negate()); + } + + return AddToMagnitude(value.m_magnitude); + } + + private NetBigInteger AddToMagnitude( + int[] magToAdd) + { + int[] big, small; + if (m_magnitude.Length < magToAdd.Length) + { + big = magToAdd; + small = m_magnitude; + } + else + { + big = m_magnitude; + small = magToAdd; + } + + // Conservatively avoid over-allocation when no overflow possible + uint limit = uint.MaxValue; + if (big.Length == small.Length) + limit -= (uint)small[0]; + + bool possibleOverflow = (uint)big[0] >= limit; + + int[] bigCopy; + if (possibleOverflow) + { + bigCopy = new int[big.Length + 1]; + big.CopyTo(bigCopy, 1); + } + else + { + bigCopy = (int[])big.Clone(); + } + + bigCopy = AddMagnitudes(bigCopy, small); + + return new NetBigInteger(m_sign, bigCopy, possibleOverflow); + } + + public NetBigInteger And( + NetBigInteger value) + { + if (m_sign == 0 || value.m_sign == 0) + { + return Zero; + } + + int[] aMag = m_sign > 0 + ? m_magnitude + : Add(One).m_magnitude; + + int[] bMag = value.m_sign > 0 + ? value.m_magnitude + : value.Add(One).m_magnitude; + + bool resultNeg = m_sign < 0 && value.m_sign < 0; + int resultLength = System.Math.Max(aMag.Length, bMag.Length); + int[] resultMag = new int[resultLength]; + + int aStart = resultMag.Length - aMag.Length; + int bStart = resultMag.Length - bMag.Length; + + for (int i = 0; i < resultMag.Length; ++i) + { + int aWord = i >= aStart ? aMag[i - aStart] : 0; + int bWord = i >= bStart ? bMag[i - bStart] : 0; + + if (m_sign < 0) + { + aWord = ~aWord; + } + + if (value.m_sign < 0) + { + bWord = ~bWord; + } + + resultMag[i] = aWord & bWord; + + if (resultNeg) + { + resultMag[i] = ~resultMag[i]; + } + } + + NetBigInteger result = new NetBigInteger(1, resultMag, true); + + if (resultNeg) + { + result = result.Not(); + } + + return result; + } + + private int calcBitLength( + int indx, + int[] mag) + { + for (; ; ) + { + if (indx >= mag.Length) + return 0; + + if (mag[indx] != 0) + break; + + ++indx; + } + + // bit length for everything after the first int + int bitLength = 32 * ((mag.Length - indx) - 1); + + // and determine bitlength of first int + int firstMag = mag[indx]; + bitLength += BitLen(firstMag); + + // Check for negative powers of two + if (m_sign < 0 && ((firstMag & -firstMag) == firstMag)) + { + do + { + if (++indx >= mag.Length) + { + --bitLength; + break; + } + } + while (mag[indx] == 0); + } + + return bitLength; + } + + public int BitLength + { + get + { + if (m_numBitLength == -1) + { + m_numBitLength = m_sign == 0 + ? 0 + : calcBitLength(0, m_magnitude); + } + + return m_numBitLength; + } + } + + // + // BitLen(value) is the number of bits in value. + // + private static int BitLen( + int w) + { + // Binary search - decision tree (5 tests, rarely 6) + return (w < 1 << 15 ? (w < 1 << 7 + ? (w < 1 << 3 ? (w < 1 << 1 + ? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1) + : (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5 + ? (w < 1 << 4 ? 4 : 5) + : (w < 1 << 6 ? 6 : 7))) + : (w < 1 << 11 + ? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11)) + : (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19 + ? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19)) + : (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27 + ? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27)) + : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31))))); + } + + private bool QuickPow2Check() + { + return m_sign > 0 && m_numBits == 1; + } + + public int CompareTo( + object obj) + { + return CompareTo((NetBigInteger)obj); + } + + + // unsigned comparison on two arrays - note the arrays may + // start with leading zeros. + private static int CompareTo( + int xIndx, + int[] x, + int yIndx, + int[] y) + { + while (xIndx != x.Length && x[xIndx] == 0) + { + xIndx++; + } + + while (yIndx != y.Length && y[yIndx] == 0) + { + yIndx++; + } + + return CompareNoLeadingZeroes(xIndx, x, yIndx, y); + } + + private static int CompareNoLeadingZeroes( + int xIndx, + int[] x, + int yIndx, + int[] y) + { + int diff = (x.Length - y.Length) - (xIndx - yIndx); + + if (diff != 0) + { + return diff < 0 ? -1 : 1; + } + + // lengths of magnitudes the same, test the magnitude values + + while (xIndx < x.Length) + { + uint v1 = (uint)x[xIndx++]; + uint v2 = (uint)y[yIndx++]; + + if (v1 != v2) + return v1 < v2 ? -1 : 1; + } + + return 0; + } + + public int CompareTo( + NetBigInteger value) + { + return m_sign < value.m_sign ? -1 + : m_sign > value.m_sign ? 1 + : m_sign == 0 ? 0 + : m_sign * CompareNoLeadingZeroes(0, m_magnitude, 0, value.m_magnitude); + } + + // return z = x / y - done in place (z value preserved, x contains the remainder) + private int[] Divide( + int[] x, + int[] y) + { + int xStart = 0; + while (xStart < x.Length && x[xStart] == 0) + { + ++xStart; + } + + int yStart = 0; + while (yStart < y.Length && y[yStart] == 0) + { + ++yStart; + } + + Debug.Assert(yStart < y.Length); + + int xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + int[] count; + + if (xyCmp > 0) + { + int yBitLength = calcBitLength(yStart, y); + int xBitLength = calcBitLength(xStart, x); + int shift = xBitLength - yBitLength; + + int[] iCount; + int iCountStart = 0; + + int[] c; + int cStart = 0; + int cBitLength = yBitLength; + if (shift > 0) + { + // iCount = ShiftLeft(One.magnitude, shift); + iCount = new int[(shift >> 5) + 1]; + iCount[0] = 1 << (shift % 32); + + c = ShiftLeft(y, shift); + cBitLength += shift; + } + else + { + iCount = new int[] { 1 }; + + int len = y.Length - yStart; + c = new int[len]; + Array.Copy(y, yStart, c, 0, len); + } + + count = new int[iCount.Length]; + + for (; ; ) + { + if (cBitLength < xBitLength + || CompareNoLeadingZeroes(xStart, x, cStart, c) >= 0) + { + Subtract(xStart, x, cStart, c); + AddMagnitudes(count, iCount); + + while (x[xStart] == 0) + { + if (++xStart == x.Length) + return count; + } + + //xBitLength = calcBitLength(xStart, x); + xBitLength = 32 * (x.Length - xStart - 1) + BitLen(x[xStart]); + + if (xBitLength <= yBitLength) + { + if (xBitLength < yBitLength) + return count; + + xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp <= 0) + break; + } + } + + shift = cBitLength - xBitLength; + + // NB: The case where c[cStart] is 1-bit is harmless + if (shift == 1) + { + uint firstC = (uint)c[cStart] >> 1; + uint firstX = (uint)x[xStart]; + if (firstC > firstX) + ++shift; + } + + if (shift < 2) + { + c = ShiftRightOneInPlace(cStart, c); + --cBitLength; + iCount = ShiftRightOneInPlace(iCountStart, iCount); + } + else + { + c = ShiftRightInPlace(cStart, c, shift); + cBitLength -= shift; + iCount = ShiftRightInPlace(iCountStart, iCount, shift); + } + + //cStart = c.Length - ((cBitLength + 31) / 32); + while (c[cStart] == 0) + { + ++cStart; + } + + while (iCount[iCountStart] == 0) + { + ++iCountStart; + } + } + } + else + { + count = new int[1]; + } + + if (xyCmp == 0) + { + AddMagnitudes(count, One.m_magnitude); + Array.Clear(x, xStart, x.Length - xStart); + } + + return count; + } + + public NetBigInteger Divide( + NetBigInteger val) + { + if (val.m_sign == 0) + throw new ArithmeticException("Division by zero error"); + + if (m_sign == 0) + return Zero; + + if (val.QuickPow2Check()) // val is power of two + { + NetBigInteger result = Abs().ShiftRight(val.Abs().BitLength - 1); + return val.m_sign == m_sign ? result : result.Negate(); + } + + int[] mag = (int[])m_magnitude.Clone(); + + return new NetBigInteger(m_sign * val.m_sign, Divide(mag, val.m_magnitude), true); + } + + public NetBigInteger[] DivideAndRemainder( + NetBigInteger val) + { + if (val.m_sign == 0) + throw new ArithmeticException("Division by zero error"); + + NetBigInteger[] biggies = new NetBigInteger[2]; + + if (m_sign == 0) + { + biggies[0] = Zero; + biggies[1] = Zero; + } + else if (val.QuickPow2Check()) // val is power of two + { + int e = val.Abs().BitLength - 1; + NetBigInteger quotient = Abs().ShiftRight(e); + int[] remainder = LastNBits(e); + + biggies[0] = val.m_sign == m_sign ? quotient : quotient.Negate(); + biggies[1] = new NetBigInteger(m_sign, remainder, true); + } + else + { + int[] remainder = (int[])m_magnitude.Clone(); + int[] quotient = Divide(remainder, val.m_magnitude); + + biggies[0] = new NetBigInteger(m_sign * val.m_sign, quotient, true); + biggies[1] = new NetBigInteger(m_sign, remainder, true); + } + + return biggies; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + NetBigInteger biggie = obj as NetBigInteger; + if (biggie == null) + return false; + + if (biggie.m_sign != m_sign || biggie.m_magnitude.Length != m_magnitude.Length) + return false; + + for (int i = 0; i < m_magnitude.Length; i++) + { + if (biggie.m_magnitude[i] != m_magnitude[i]) + { + return false; + } + } + + return true; + } + + public NetBigInteger Gcd( + NetBigInteger value) + { + if (value.m_sign == 0) + return Abs(); + + if (m_sign == 0) + return value.Abs(); + + NetBigInteger r; + NetBigInteger u = this; + NetBigInteger v = value; + + while (v.m_sign != 0) + { + r = u.Mod(v); + u = v; + v = r; + } + + return u; + } + + public override int GetHashCode() + { + int hc = m_magnitude.Length; + if (m_magnitude.Length > 0) + { + hc ^= m_magnitude[0]; + + if (m_magnitude.Length > 1) + { + hc ^= m_magnitude[m_magnitude.Length - 1]; + } + } + + return m_sign < 0 ? ~hc : hc; + } + + private NetBigInteger Inc() + { + if (m_sign == 0) + return One; + + if (m_sign < 0) + return new NetBigInteger(-1, doSubBigLil(m_magnitude, One.m_magnitude), true); + + return AddToMagnitude(One.m_magnitude); + } + + public int IntValue + { + get + { + return m_sign == 0 ? 0 + : m_sign > 0 ? m_magnitude[m_magnitude.Length - 1] + : -m_magnitude[m_magnitude.Length - 1]; + } + } + + public NetBigInteger Max( + NetBigInteger value) + { + return CompareTo(value) > 0 ? this : value; + } + + public NetBigInteger Min( + NetBigInteger value) + { + return CompareTo(value) < 0 ? this : value; + } + + public NetBigInteger Mod( + NetBigInteger m) + { + if (m.m_sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + NetBigInteger biggie = Remainder(m); + + return (biggie.m_sign >= 0 ? biggie : biggie.Add(m)); + } + + public NetBigInteger ModInverse( + NetBigInteger m) + { + if (m.m_sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + NetBigInteger x = new NetBigInteger(); + NetBigInteger gcd = ExtEuclid(this, m, x, null); + + if (!gcd.Equals(One)) + throw new ArithmeticException("Numbers not relatively prime."); + + if (x.m_sign < 0) + { + x.m_sign = 1; + //x = m.Subtract(x); + x.m_magnitude = doSubBigLil(m.m_magnitude, x.m_magnitude); + } + + return x; + } + + private static NetBigInteger ExtEuclid( + NetBigInteger a, + NetBigInteger b, + NetBigInteger u1Out, + NetBigInteger u2Out) + { + NetBigInteger u1 = One; + NetBigInteger u3 = a; + NetBigInteger v1 = Zero; + NetBigInteger v3 = b; + + while (v3.m_sign > 0) + { + NetBigInteger[] q = u3.DivideAndRemainder(v3); + + NetBigInteger tmp = v1.Multiply(q[0]); + NetBigInteger tn = u1.Subtract(tmp); + u1 = v1; + v1 = tn; + + u3 = v3; + v3 = q[1]; + } + + if (u1Out != null) + { + u1Out.m_sign = u1.m_sign; + u1Out.m_magnitude = u1.m_magnitude; + } + + if (u2Out != null) + { + NetBigInteger tmp = u1.Multiply(a); + tmp = u3.Subtract(tmp); + NetBigInteger res = tmp.Divide(b); + u2Out.m_sign = res.m_sign; + u2Out.m_magnitude = res.m_magnitude; + } + + return u3; + } + + private static void ZeroOut( + int[] x) + { + Array.Clear(x, 0, x.Length); + } + + public NetBigInteger ModPow( + NetBigInteger exponent, + NetBigInteger m) + { + if (m.m_sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + if (m.Equals(One)) + return Zero; + + if (exponent.m_sign == 0) + return One; + + if (m_sign == 0) + return Zero; + + int[] zVal = null; + int[] yAccum = null; + int[] yVal; + + // Montgomery exponentiation is only possible if the modulus is odd, + // but AFAIK, this is always the case for crypto algo's + bool useMonty = ((m.m_magnitude[m.m_magnitude.Length - 1] & 1) == 1); + long mQ = 0; + if (useMonty) + { + mQ = m.GetMQuote(); + + // tmp = this * R mod m + NetBigInteger tmp = ShiftLeft(32 * m.m_magnitude.Length).Mod(m); + zVal = tmp.m_magnitude; + + useMonty = (zVal.Length <= m.m_magnitude.Length); + + if (useMonty) + { + yAccum = new int[m.m_magnitude.Length + 1]; + if (zVal.Length < m.m_magnitude.Length) + { + int[] longZ = new int[m.m_magnitude.Length]; + zVal.CopyTo(longZ, longZ.Length - zVal.Length); + zVal = longZ; + } + } + } + + if (!useMonty) + { + if (m_magnitude.Length <= m.m_magnitude.Length) + { + //zAccum = new int[m.magnitude.Length * 2]; + zVal = new int[m.m_magnitude.Length]; + m_magnitude.CopyTo(zVal, zVal.Length - m_magnitude.Length); + } + else + { + // + // in normal practice we'll never see .. + // + NetBigInteger tmp = Remainder(m); + + //zAccum = new int[m.magnitude.Length * 2]; + zVal = new int[m.m_magnitude.Length]; + tmp.m_magnitude.CopyTo(zVal, zVal.Length - tmp.m_magnitude.Length); + } + + yAccum = new int[m.m_magnitude.Length * 2]; + } + + yVal = new int[m.m_magnitude.Length]; + + // + // from LSW to MSW + // + for (int i = 0; i < exponent.m_magnitude.Length; i++) + { + int v = exponent.m_magnitude[i]; + int bits = 0; + + if (i == 0) + { + while (v > 0) + { + v <<= 1; + bits++; + } + + // + // first time in initialise y + // + zVal.CopyTo(yVal, 0); + + v <<= 1; + bits++; + } + + while (v != 0) + { + if (useMonty) + { + // Montgomery square algo doesn't exist, and a normal + // square followed by a Montgomery reduction proved to + // be almost as heavy as a Montgomery mulitply. + MultiplyMonty(yAccum, yVal, yVal, m.m_magnitude, mQ); + } + else + { + Square(yAccum, yVal); + Remainder(yAccum, m.m_magnitude); + Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, yVal.Length); + ZeroOut(yAccum); + } + bits++; + + if (v < 0) + { + if (useMonty) + { + MultiplyMonty(yAccum, yVal, zVal, m.m_magnitude, mQ); + } + else + { + Multiply(yAccum, yVal, zVal); + Remainder(yAccum, m.m_magnitude); + Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, + yVal.Length); + ZeroOut(yAccum); + } + } + + v <<= 1; + } + + while (bits < 32) + { + if (useMonty) + { + MultiplyMonty(yAccum, yVal, yVal, m.m_magnitude, mQ); + } + else + { + Square(yAccum, yVal); + Remainder(yAccum, m.m_magnitude); + Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, yVal.Length); + ZeroOut(yAccum); + } + bits++; + } + } + + if (useMonty) + { + // Return y * R^(-1) mod m by doing y * 1 * R^(-1) mod m + ZeroOut(zVal); + zVal[zVal.Length - 1] = 1; + MultiplyMonty(yAccum, yVal, zVal, m.m_magnitude, mQ); + } + + NetBigInteger result = new NetBigInteger(1, yVal, true); + + return exponent.m_sign > 0 + ? result + : result.ModInverse(m); + } + + // return w with w = x * x - w is assumed to have enough space. + private static int[] Square( + int[] w, + int[] x) + { + // Note: this method allows w to be only (2 * x.Length - 1) words if result will fit + // if (w.Length != 2 * x.Length) + // throw new ArgumentException("no I don't think so..."); + + ulong u1, u2, c; + + int wBase = w.Length - 1; + + for (int i = x.Length - 1; i != 0; i--) + { + ulong v = (ulong)(uint)x[i]; + + u1 = v * v; + u2 = u1 >> 32; + u1 = (uint)u1; + + u1 += (ulong)(uint)w[wBase]; + + w[wBase] = (int)(uint)u1; + c = u2 + (u1 >> 32); + + for (int j = i - 1; j >= 0; j--) + { + --wBase; + u1 = v * (ulong)(uint)x[j]; + u2 = u1 >> 31; // multiply by 2! + u1 = (uint)(u1 << 1); // multiply by 2! + u1 += c + (ulong)(uint)w[wBase]; + + w[wBase] = (int)(uint)u1; + c = u2 + (u1 >> 32); + } + + c += (ulong)(uint)w[--wBase]; + w[wBase] = (int)(uint)c; + + if (--wBase >= 0) + { + w[wBase] = (int)(uint)(c >> 32); + } + else + { + Debug.Assert((uint)(c >> 32) == 0); + } + wBase += i; + } + + u1 = (ulong)(uint)x[0]; + u1 = u1 * u1; + u2 = u1 >> 32; + u1 = u1 & IMASK; + + u1 += (ulong)(uint)w[wBase]; + + w[wBase] = (int)(uint)u1; + if (--wBase >= 0) + { + w[wBase] = (int)(uint)(u2 + (u1 >> 32) + (ulong)(uint)w[wBase]); + } + else + { + Debug.Assert((uint)(u2 + (u1 >> 32)) == 0); + } + + return w; + } + + // return x with x = y * z - x is assumed to have enough space. + private static int[] Multiply( + int[] x, + int[] y, + int[] z) + { + int i = z.Length; + + if (i < 1) + return x; + + int xBase = x.Length - y.Length; + + for (; ; ) + { + long a = z[--i] & IMASK; + long val = 0; + + for (int j = y.Length - 1; j >= 0; j--) + { + val += a * (y[j] & IMASK) + (x[xBase + j] & IMASK); + + x[xBase + j] = (int)val; + + val = (long)((ulong)val >> 32); + } + + --xBase; + + if (i < 1) + { + if (xBase >= 0) + { + x[xBase] = (int)val; + } + else + { + Debug.Assert(val == 0); + } + break; + } + + x[xBase] = (int)val; + } + + return x; + } + + private static long FastExtEuclid( + long a, + long b, + long[] uOut) + { + long u1 = 1; + long u3 = a; + long v1 = 0; + long v3 = b; + + while (v3 > 0) + { + long q, tn; + + q = u3 / v3; + + tn = u1 - (v1 * q); + u1 = v1; + v1 = tn; + + tn = u3 - (v3 * q); + u3 = v3; + v3 = tn; + } + + uOut[0] = u1; + uOut[1] = (u3 - (u1 * a)) / b; + + return u3; + } + + private static long FastModInverse( + long v, + long m) + { + if (m < 1) + throw new ArithmeticException("Modulus must be positive"); + + long[] x = new long[2]; + long gcd = FastExtEuclid(v, m, x); + + if (gcd != 1) + throw new ArithmeticException("Numbers not relatively prime."); + + if (x[0] < 0) + { + x[0] += m; + } + + return x[0]; + } + + private long GetMQuote() + { + Debug.Assert(m_sign > 0); + + if (m_quote != -1) + { + return m_quote; // already calculated + } + + if (m_magnitude.Length == 0 || (m_magnitude[m_magnitude.Length - 1] & 1) == 0) + { + return -1; // not for even numbers + } + + long v = (((~m_magnitude[m_magnitude.Length - 1]) | 1) & 0xffffffffL); + m_quote = FastModInverse(v, 0x100000000L); + + return m_quote; + } + + private static void MultiplyMonty( + int[] a, + int[] x, + int[] y, + int[] m, + long mQuote) + // mQuote = -m^(-1) mod b + { + if (m.Length == 1) + { + x[0] = (int)MultiplyMontyNIsOne((uint)x[0], (uint)y[0], (uint)m[0], (ulong)mQuote); + return; + } + + int n = m.Length; + int nMinus1 = n - 1; + long y_0 = y[nMinus1] & IMASK; + + // 1. a = 0 (Notation: a = (a_{n} a_{n-1} ... a_{0})_{b} ) + Array.Clear(a, 0, n + 1); + + // 2. for i from 0 to (n - 1) do the following: + for (int i = n; i > 0; i--) + { + long x_i = x[i - 1] & IMASK; + + // 2.1 u = ((a[0] + (x[i] * y[0]) * mQuote) mod b + long u = ((((a[n] & IMASK) + ((x_i * y_0) & IMASK)) & IMASK) * mQuote) & IMASK; + + // 2.2 a = (a + x_i * y + u * m) / b + long prod1 = x_i * y_0; + long prod2 = u * (m[nMinus1] & IMASK); + long tmp = (a[n] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK); + long carry = (long)((ulong)prod1 >> 32) + (long)((ulong)prod2 >> 32) + (long)((ulong)tmp >> 32); + for (int j = nMinus1; j > 0; j--) + { + prod1 = x_i * (y[j - 1] & IMASK); + prod2 = u * (m[j - 1] & IMASK); + tmp = (a[j] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK) + (carry & IMASK); + carry = (long)((ulong)carry >> 32) + (long)((ulong)prod1 >> 32) + + (long)((ulong)prod2 >> 32) + (long)((ulong)tmp >> 32); + a[j + 1] = (int)tmp; // division by b + } + carry += (a[0] & IMASK); + a[1] = (int)carry; + a[0] = (int)((ulong)carry >> 32); // OJO!!!!! + } + + // 3. if x >= m the x = x - m + if (CompareTo(0, a, 0, m) >= 0) + { + Subtract(0, a, 0, m); + } + + // put the result in x + Array.Copy(a, 1, x, 0, n); + } + + private static uint MultiplyMontyNIsOne( + uint x, + uint y, + uint m, + ulong mQuote) + { + ulong um = m; + ulong prod1 = (ulong)x * (ulong)y; + ulong u = (prod1 * mQuote) & UIMASK; + ulong prod2 = u * um; + ulong tmp = (prod1 & UIMASK) + (prod2 & UIMASK); + ulong carry = (prod1 >> 32) + (prod2 >> 32) + (tmp >> 32); + + if (carry > um) + { + carry -= um; + } + + return (uint)(carry & UIMASK); + } + + public NetBigInteger Modulus( + NetBigInteger val) + { + return Mod(val); + } + + public NetBigInteger Multiply( + NetBigInteger val) + { + if (m_sign == 0 || val.m_sign == 0) + return Zero; + + if (val.QuickPow2Check()) // val is power of two + { + NetBigInteger result = ShiftLeft(val.Abs().BitLength - 1); + return val.m_sign > 0 ? result : result.Negate(); + } + + if (QuickPow2Check()) // this is power of two + { + NetBigInteger result = val.ShiftLeft(Abs().BitLength - 1); + return m_sign > 0 ? result : result.Negate(); + } + + int maxBitLength = BitLength + val.BitLength; + int resLength = (maxBitLength + BitsPerInt - 1) / BitsPerInt; + + int[] res = new int[resLength]; + + if (val == this) + { + Square(res, m_magnitude); + } + else + { + Multiply(res, m_magnitude, val.m_magnitude); + } + + return new NetBigInteger(m_sign * val.m_sign, res, true); + } + + public NetBigInteger Negate() + { + if (m_sign == 0) + return this; + + return new NetBigInteger(-m_sign, m_magnitude, false); + } + + public NetBigInteger Not() + { + return Inc().Negate(); + } + + public NetBigInteger Pow(int exp) + { + if (exp < 0) + { + throw new ArithmeticException("Negative exponent"); + } + + if (exp == 0) + { + return One; + } + + if (m_sign == 0 || Equals(One)) + { + return this; + } + + NetBigInteger y = One; + NetBigInteger z = this; + + for (; ; ) + { + if ((exp & 0x1) == 1) + { + y = y.Multiply(z); + } + exp >>= 1; + if (exp == 0) break; + z = z.Multiply(z); + } + + return y; + } + + private int Remainder( + int m) + { + Debug.Assert(m > 0); + + long acc = 0; + for (int pos = 0; pos < m_magnitude.Length; ++pos) + { + long posVal = (uint)m_magnitude[pos]; + acc = (acc << 32 | posVal) % m; + } + + return (int)acc; + } + + // return x = x % y - done in place (y value preserved) + private int[] Remainder( + int[] x, + int[] y) + { + int xStart = 0; + while (xStart < x.Length && x[xStart] == 0) + { + ++xStart; + } + + int yStart = 0; + while (yStart < y.Length && y[yStart] == 0) + { + ++yStart; + } + + Debug.Assert(yStart < y.Length); + + int xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp > 0) + { + int yBitLength = calcBitLength(yStart, y); + int xBitLength = calcBitLength(xStart, x); + int shift = xBitLength - yBitLength; + + int[] c; + int cStart = 0; + int cBitLength = yBitLength; + if (shift > 0) + { + c = ShiftLeft(y, shift); + cBitLength += shift; + Debug.Assert(c[0] != 0); + } + else + { + int len = y.Length - yStart; + c = new int[len]; + Array.Copy(y, yStart, c, 0, len); + } + + for (; ; ) + { + if (cBitLength < xBitLength + || CompareNoLeadingZeroes(xStart, x, cStart, c) >= 0) + { + Subtract(xStart, x, cStart, c); + + while (x[xStart] == 0) + { + if (++xStart == x.Length) + return x; + } + + //xBitLength = calcBitLength(xStart, x); + xBitLength = 32 * (x.Length - xStart - 1) + BitLen(x[xStart]); + + if (xBitLength <= yBitLength) + { + if (xBitLength < yBitLength) + return x; + + xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp <= 0) + break; + } + } + + shift = cBitLength - xBitLength; + + // NB: The case where c[cStart] is 1-bit is harmless + if (shift == 1) + { + uint firstC = (uint)c[cStart] >> 1; + uint firstX = (uint)x[xStart]; + if (firstC > firstX) + ++shift; + } + + if (shift < 2) + { + c = ShiftRightOneInPlace(cStart, c); + --cBitLength; + } + else + { + c = ShiftRightInPlace(cStart, c, shift); + cBitLength -= shift; + } + + //cStart = c.Length - ((cBitLength + 31) / 32); + while (c[cStart] == 0) + { + ++cStart; + } + } + } + + if (xyCmp == 0) + { + Array.Clear(x, xStart, x.Length - xStart); + } + + return x; + } + + public NetBigInteger Remainder( + NetBigInteger n) + { + if (n.m_sign == 0) + throw new ArithmeticException("Division by zero error"); + + if (m_sign == 0) + return Zero; + + // For small values, use fast remainder method + if (n.m_magnitude.Length == 1) + { + int val = n.m_magnitude[0]; + + if (val > 0) + { + if (val == 1) + return Zero; + + int rem = Remainder(val); + + return rem == 0 + ? Zero + : new NetBigInteger(m_sign, new int[] { rem }, false); + } + } + + if (CompareNoLeadingZeroes(0, m_magnitude, 0, n.m_magnitude) < 0) + return this; + + int[] result; + if (n.QuickPow2Check()) // n is power of two + { + result = LastNBits(n.Abs().BitLength - 1); + } + else + { + result = (int[])m_magnitude.Clone(); + result = Remainder(result, n.m_magnitude); + } + + return new NetBigInteger(m_sign, result, true); + } + + private int[] LastNBits( + int n) + { + if (n < 1) + return ZeroMagnitude; + + int numWords = (n + BitsPerInt - 1) / BitsPerInt; + numWords = System.Math.Min(numWords, m_magnitude.Length); + int[] result = new int[numWords]; + + Array.Copy(m_magnitude, m_magnitude.Length - numWords, result, 0, numWords); + + int hiBits = n % 32; + if (hiBits != 0) + { + result[0] &= ~(-1 << hiBits); + } + + return result; + } + + + // do a left shift - this returns a new array. + private static int[] ShiftLeft( + int[] mag, + int n) + { + int nInts = (int)((uint)n >> 5); + int nBits = n & 0x1f; + int magLen = mag.Length; + int[] newMag; + + if (nBits == 0) + { + newMag = new int[magLen + nInts]; + mag.CopyTo(newMag, 0); + } + else + { + int i = 0; + int nBits2 = 32 - nBits; + int highBits = (int)((uint)mag[0] >> nBits2); + + if (highBits != 0) + { + newMag = new int[magLen + nInts + 1]; + newMag[i++] = highBits; + } + else + { + newMag = new int[magLen + nInts]; + } + + int m = mag[0]; + for (int j = 0; j < magLen - 1; j++) + { + int next = mag[j + 1]; + + newMag[i++] = (m << nBits) | (int)((uint)next >> nBits2); + m = next; + } + + newMag[i] = mag[magLen - 1] << nBits; + } + + return newMag; + } + + public NetBigInteger ShiftLeft( + int n) + { + if (m_sign == 0 || m_magnitude.Length == 0) + return Zero; + + if (n == 0) + return this; + + if (n < 0) + return ShiftRight(-n); + + NetBigInteger result = new NetBigInteger(m_sign, ShiftLeft(m_magnitude, n), true); + + if (m_numBits != -1) + { + result.m_numBits = m_sign > 0 + ? m_numBits + : m_numBits + n; + } + + if (m_numBitLength != -1) + { + result.m_numBitLength = m_numBitLength + n; + } + + return result; + } + + // do a right shift - this does it in place. + private static int[] ShiftRightInPlace( + int start, + int[] mag, + int n) + { + int nInts = (int)((uint)n >> 5) + start; + int nBits = n & 0x1f; + int magEnd = mag.Length - 1; + + if (nInts != start) + { + int delta = (nInts - start); + + for (int i = magEnd; i >= nInts; i--) + { + mag[i] = mag[i - delta]; + } + for (int i = nInts - 1; i >= start; i--) + { + mag[i] = 0; + } + } + + if (nBits != 0) + { + int nBits2 = 32 - nBits; + int m = mag[magEnd]; + + for (int i = magEnd; i > nInts; --i) + { + int next = mag[i - 1]; + + mag[i] = (int)((uint)m >> nBits) | (next << nBits2); + m = next; + } + + mag[nInts] = (int)((uint)mag[nInts] >> nBits); + } + + return mag; + } + + // do a right shift by one - this does it in place. + private static int[] ShiftRightOneInPlace( + int start, + int[] mag) + { + int i = mag.Length; + int m = mag[i - 1]; + + while (--i > start) + { + int next = mag[i - 1]; + mag[i] = ((int)((uint)m >> 1)) | (next << 31); + m = next; + } + + mag[start] = (int)((uint)mag[start] >> 1); + + return mag; + } + + public NetBigInteger ShiftRight( + int n) + { + if (n == 0) + return this; + + if (n < 0) + return ShiftLeft(-n); + + if (n >= BitLength) + return (m_sign < 0 ? One.Negate() : Zero); + + // int[] res = (int[]) magnitude.Clone(); + // + // res = ShiftRightInPlace(0, res, n); + // + // return new BigInteger(sign, res, true); + + int resultLength = (BitLength - n + 31) >> 5; + int[] res = new int[resultLength]; + + int numInts = n >> 5; + int numBits = n & 31; + + if (numBits == 0) + { + Array.Copy(m_magnitude, 0, res, 0, res.Length); + } + else + { + int numBits2 = 32 - numBits; + + int magPos = m_magnitude.Length - 1 - numInts; + for (int i = resultLength - 1; i >= 0; --i) + { + res[i] = (int)((uint)m_magnitude[magPos--] >> numBits); + + if (magPos >= 0) + { + res[i] |= m_magnitude[magPos] << numBits2; + } + } + } + + Debug.Assert(res[0] != 0); + + return new NetBigInteger(m_sign, res, false); + } + + public int SignValue + { + get { return m_sign; } + } + + // returns x = x - y - we assume x is >= y + private static int[] Subtract( + int xStart, + int[] x, + int yStart, + int[] y) + { + Debug.Assert(yStart < y.Length); + Debug.Assert(x.Length - xStart >= y.Length - yStart); + + int iT = x.Length; + int iV = y.Length; + long m; + int borrow = 0; + + do + { + m = (x[--iT] & IMASK) - (y[--iV] & IMASK) + borrow; + x[iT] = (int)m; + + // borrow = (m < 0) ? -1 : 0; + borrow = (int)(m >> 63); + } + while (iV > yStart); + + if (borrow != 0) + { + while (--x[--iT] == -1) + { + } + } + + return x; + } + + public NetBigInteger Subtract( + NetBigInteger n) + { + if (n.m_sign == 0) + return this; + + if (m_sign == 0) + return n.Negate(); + + if (m_sign != n.m_sign) + return Add(n.Negate()); + + int compare = CompareNoLeadingZeroes(0, m_magnitude, 0, n.m_magnitude); + if (compare == 0) + return Zero; + + NetBigInteger bigun, lilun; + if (compare < 0) + { + bigun = n; + lilun = this; + } + else + { + bigun = this; + lilun = n; + } + + return new NetBigInteger(m_sign * compare, doSubBigLil(bigun.m_magnitude, lilun.m_magnitude), true); + } + + private static int[] doSubBigLil( + int[] bigMag, + int[] lilMag) + { + int[] res = (int[])bigMag.Clone(); + + return Subtract(0, res, 0, lilMag); + } + + public byte[] ToByteArray() + { + return ToByteArray(false); + } + + public byte[] ToByteArrayUnsigned() + { + return ToByteArray(true); + } + + private byte[] ToByteArray( + bool unsigned) + { + if (m_sign == 0) + return unsigned ? ZeroEncoding : new byte[1]; + + int nBits = (unsigned && m_sign > 0) + ? BitLength + : BitLength + 1; + + int nBytes = GetByteLength(nBits); + byte[] bytes = new byte[nBytes]; + + int magIndex = m_magnitude.Length; + int bytesIndex = bytes.Length; + + if (m_sign > 0) + { + while (magIndex > 1) + { + uint mag = (uint)m_magnitude[--magIndex]; + bytes[--bytesIndex] = (byte)mag; + bytes[--bytesIndex] = (byte)(mag >> 8); + bytes[--bytesIndex] = (byte)(mag >> 16); + bytes[--bytesIndex] = (byte)(mag >> 24); + } + + uint lastMag = (uint)m_magnitude[0]; + while (lastMag > byte.MaxValue) + { + bytes[--bytesIndex] = (byte)lastMag; + lastMag >>= 8; + } + + bytes[--bytesIndex] = (byte)lastMag; + } + else // sign < 0 + { + bool carry = true; + + while (magIndex > 1) + { + uint mag = ~((uint)m_magnitude[--magIndex]); + + if (carry) + { + carry = (++mag == uint.MinValue); + } + + bytes[--bytesIndex] = (byte)mag; + bytes[--bytesIndex] = (byte)(mag >> 8); + bytes[--bytesIndex] = (byte)(mag >> 16); + bytes[--bytesIndex] = (byte)(mag >> 24); + } + + uint lastMag = (uint)m_magnitude[0]; + + if (carry) + { + // Never wraps because magnitude[0] != 0 + --lastMag; + } + + while (lastMag > byte.MaxValue) + { + bytes[--bytesIndex] = (byte)~lastMag; + lastMag >>= 8; + } + + bytes[--bytesIndex] = (byte)~lastMag; + + if (bytesIndex > 0) + { + bytes[--bytesIndex] = byte.MaxValue; + } + } + + return bytes; + } + + public override string ToString() + { + return ToString(10); + } + + public string ToString( + int radix) + { + switch (radix) + { + case 2: + case 10: + case 16: + break; + default: + throw new FormatException("Only bases 2, 10, 16 are allowed"); + } + + // NB: Can only happen to internally managed instances + if (m_magnitude == null) + return "null"; + + if (m_sign == 0) + return "0"; + + Debug.Assert(m_magnitude.Length > 0); + + StringBuilder sb = new StringBuilder(); + + if (radix == 16) + { + sb.Append(m_magnitude[0].ToString("x")); + + for (int i = 1; i < m_magnitude.Length; i++) + { + sb.Append(m_magnitude[i].ToString("x8")); + } + } + else if (radix == 2) + { + sb.Append('1'); + + for (int i = BitLength - 2; i >= 0; --i) + { + sb.Append(TestBit(i) ? '1' : '0'); + } + } + else + { + // This is algorithm 1a from chapter 4.4 in Seminumerical Algorithms, slow but it works + Stack S = new Stack(); + NetBigInteger bs = ValueOf(radix); + + NetBigInteger u = Abs(); + NetBigInteger b; + + while (u.m_sign != 0) + { + b = u.Mod(bs); + if (b.m_sign == 0) + { + S.Push("0"); + } + else + { + // see how to interact with different bases + S.Push(b.m_magnitude[0].ToString("d")); + } + u = u.Divide(bs); + } + + // Then pop the stack + while (S.Count != 0) + { + sb.Append((string)S.Pop()); + } + } + + string s = sb.ToString(); + + Debug.Assert(s.Length > 0); + + // Strip leading zeros. (We know this number is not all zeroes though) + if (s[0] == '0') + { + int nonZeroPos = 0; + while (s[++nonZeroPos] == '0') { } + + s = s.Substring(nonZeroPos); + } + + if (m_sign == -1) + { + s = "-" + s; + } + + return s; + } + + private static NetBigInteger createUValueOf( + ulong value) + { + int msw = (int)(value >> 32); + int lsw = (int)value; + + if (msw != 0) + return new NetBigInteger(1, new int[] { msw, lsw }, false); + + if (lsw != 0) + { + NetBigInteger n = new NetBigInteger(1, new int[] { lsw }, false); + // Check for a power of two + if ((lsw & -lsw) == lsw) + { + n.m_numBits = 1; + } + return n; + } + + return Zero; + } + + private static NetBigInteger createValueOf( + long value) + { + if (value < 0) + { + if (value == long.MinValue) + return createValueOf(~value).Not(); + + return createValueOf(-value).Negate(); + } + + return createUValueOf((ulong)value); + } + + public static NetBigInteger ValueOf( + long value) + { + switch (value) + { + case 0: + return Zero; + case 1: + return One; + case 2: + return Two; + case 3: + return Three; + case 10: + return Ten; + } + + return createValueOf(value); + } + + public int GetLowestSetBit() + { + if (m_sign == 0) + return -1; + + int w = m_magnitude.Length; + + while (--w > 0) + { + if (m_magnitude[w] != 0) + break; + } + + int word = (int)m_magnitude[w]; + Debug.Assert(word != 0); + + int b = (word & 0x0000FFFF) == 0 + ? (word & 0x00FF0000) == 0 + ? 7 + : 15 + : (word & 0x000000FF) == 0 + ? 23 + : 31; + + while (b > 0) + { + if ((word << b) == int.MinValue) + break; + + b--; + } + + return ((m_magnitude.Length - w) * 32 - (b + 1)); + } + + public bool TestBit( + int n) + { + if (n < 0) + throw new ArithmeticException("Bit position must not be negative"); + + if (m_sign < 0) + return !Not().TestBit(n); + + int wordNum = n / 32; + if (wordNum >= m_magnitude.Length) + return false; + + int word = m_magnitude[m_magnitude.Length - 1 - wordNum]; + return ((word >> (n % 32)) & 1) > 0; + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetBitVector.cs b/Lidgren.Network/NetBitVector.cs new file mode 100644 index 0000000..af4b4d7 --- /dev/null +++ b/Lidgren.Network/NetBitVector.cs @@ -0,0 +1,172 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Fixed size vector of booleans + /// + public sealed class NetBitVector + { + private readonly int m_capacity; + private readonly int[] m_data; + private int m_numBitsSet; + + /// + /// Gets the number of bits/booleans stored in this vector + /// + public int Capacity { get { return m_capacity; } } + + /// + /// NetBitVector constructor + /// + public NetBitVector(int bitsCapacity) + { + m_capacity = bitsCapacity; + m_data = new int[(bitsCapacity + 31) / 32]; + } + + /// + /// Returns true if all bits/booleans are set to zero/false + /// + public bool IsEmpty() + { + return (m_numBitsSet == 0); + } + + /// + /// Returns the number of bits/booleans set to one/true + /// + /// + public int Count() + { + return m_numBitsSet; + } + + /// + /// Shift all bits one step down, cycling the first bit to the top + /// + public void RotateDown() + { + int lenMinusOne = m_data.Length - 1; + + int firstBit = m_data[0] & 1; + for (int i = 0; i < lenMinusOne; i++) + m_data[i] = ((m_data[i] >> 1) & ~(1 << 31)) | m_data[i + 1] << 31; + + int lastIndex = m_capacity - 1 - (32 * lenMinusOne); + + // special handling of last int + int cur = m_data[lenMinusOne]; + cur = cur >> 1; + cur |= firstBit << lastIndex; + + m_data[lenMinusOne] = cur; + } + + /// + /// Gets the first (lowest) index set to true + /// + public int GetFirstSetIndex() + { + int idx = 0; + + int data = m_data[0]; + while (data == 0) + { + idx++; + data = m_data[idx]; + } + + int a = 0; + while (((data >> a) & 1) == 0) + a++; + + return (idx * 32) + a; + } + + /// + /// Gets the bit/bool at the specified index + /// + public bool Get(int bitIndex) + { + NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity); + + return (m_data[bitIndex / 32] & (1 << (bitIndex % 32))) != 0; + } + + /// + /// Sets or clears the bit/bool at the specified index + /// + public void Set(int bitIndex, bool value) + { + NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity); + + int idx = bitIndex / 32; + if (value) + { + if ((m_data[idx] & (1 << (bitIndex % 32))) == 0) + m_numBitsSet++; + m_data[idx] |= (1 << (bitIndex % 32)); + } + else + { + if ((m_data[idx] & (1 << (bitIndex % 32))) != 0) + m_numBitsSet--; + m_data[idx] &= (~(1 << (bitIndex % 32))); + } + } + + /// + /// Gets the bit/bool at the specified index + /// + [System.Runtime.CompilerServices.IndexerName("Bit")] + public bool this[int index] + { + get { return Get(index); } + set { Set(index, value); } + } + + /// + /// Sets all bits/booleans to zero/false + /// + public void Clear() + { + Array.Clear(m_data, 0, m_data.Length); + m_numBitsSet = 0; + NetException.Assert(this.IsEmpty()); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + StringBuilder bdr = new StringBuilder(m_capacity + 2); + bdr.Append('['); + for (int i = 0; i < m_capacity; i++) + bdr.Append(Get(m_capacity - i - 1) ? '1' : '0'); + bdr.Append(']'); + return bdr.ToString(); + } + } +} diff --git a/Lidgren.Network/NetBitWriter.cs b/Lidgren.Network/NetBitWriter.cs new file mode 100644 index 0000000..c641c49 --- /dev/null +++ b/Lidgren.Network/NetBitWriter.cs @@ -0,0 +1,513 @@ +//#define UNSAFE +//#define BIGENDIAN +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; + +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Helper class for NetBuffer to write/read bits + /// + public static class NetBitWriter + { + /// + /// Read 1-8 bits from a buffer into a byte + /// + public static byte ReadByte(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits < 9)), "Read() can only read between 1 and 8 bits"); + + int bytePtr = readBitOffset >> 3; + int startReadAtIndex = readBitOffset - (bytePtr * 8); // (readBitOffset % 8); + + if (startReadAtIndex == 0 && numberOfBits == 8) + return fromBuffer[bytePtr]; + + // mask away unused bits lower than (right of) relevant bits in first byte + byte returnValue = (byte)(fromBuffer[bytePtr] >> startReadAtIndex); + + int numberOfBitsInSecondByte = numberOfBits - (8 - startReadAtIndex); + + if (numberOfBitsInSecondByte < 1) + { + // we don't need to read from the second byte, but we DO need + // to mask away unused bits higher than (left of) relevant bits + return (byte)(returnValue & (255 >> (8 - numberOfBits))); + } + + byte second = fromBuffer[bytePtr + 1]; + + // mask away unused bits higher than (left of) relevant bits in second byte + second &= (byte)(255 >> (8 - numberOfBitsInSecondByte)); + + return (byte)(returnValue | (byte)(second << (numberOfBits - numberOfBitsInSecondByte))); + } + + /// + /// Read several bytes from a buffer + /// + public static void ReadBytes(byte[] fromBuffer, int numberOfBytes, int readBitOffset, byte[] destination, int destinationByteOffset) + { + int readPtr = readBitOffset >> 3; + int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8); + + if (startReadAtIndex == 0) + { + Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes); + return; + } + + int secondPartLen = 8 - startReadAtIndex; + int secondMask = 255 >> secondPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + // mask away unused bits lower than (right of) relevant bits in byte + int b = fromBuffer[readPtr] >> startReadAtIndex; + + readPtr++; + + // mask away unused bits higher than (left of) relevant bits in second byte + int second = fromBuffer[readPtr] & secondMask; + + destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen)); + } + + return; + } + + /// + /// Write 0-8 bits of data to buffer + /// + public static void WriteByte(byte source, int numberOfBits, byte[] destination, int destBitOffset) + { + if (numberOfBits == 0) + return; + + NetException.Assert(((numberOfBits >= 0) && (numberOfBits <= 8)), "Must write between 0 and 8 bits!"); + + // Mask out all the bits we dont want + source = (byte)(source & (0xFF >> (8 - numberOfBits))); + + int p = destBitOffset >> 3; + int bitsUsed = destBitOffset & 0x7; // mod 8 + int bitsFree = 8 - bitsUsed; + int bitsLeft = bitsFree - numberOfBits; + + // Fast path, everything fits in the first byte + if (bitsLeft >= 0) + { + int mask = (0xFF >> bitsFree) | (0xFF << (8 - bitsLeft)); + + destination[p] = (byte)( + // Mask out lower and upper bits + (destination[p] & mask) | + + // Insert new bits + (source << bitsUsed) + ); + + return; + } + + destination[p] = (byte)( + // Mask out upper bits + (destination[p] & (0xFF >> bitsFree)) | + + // Write the lower bits to the upper bits in the first byte + (source << bitsUsed) + ); + + p += 1; + + destination[p] = (byte)( + // Mask out lower bits + (destination[p] & (0xFF << (numberOfBits - bitsFree))) | + + // Write the upper bits to the lower bits of the second byte + (source >> bitsFree) + ); + } + + /// + /// Write several whole bytes + /// + public static void WriteBytes(byte[] source, int sourceByteOffset, int numberOfBytes, byte[] destination, int destBitOffset) + { + int dstBytePtr = destBitOffset >> 3; + int firstPartLen = (destBitOffset % 8); + + if (firstPartLen == 0) + { + Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes); + return; + } + + int lastPartLen = 8 - firstPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + byte src = source[sourceByteOffset + i]; + + // write last part of this byte + destination[dstBytePtr] &= (byte)(255 >> lastPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src << firstPartLen); // write first half + + dstBytePtr++; + + // write first part of next byte + destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half + } + + return; + } + + /// + /// Reads an unsigned 16 bit integer + /// + [CLSCompliant(false)] +#if UNSAFE + public static unsafe ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits"); + + if (numberOfBits == 16 && ((readBitOffset % 8) == 0)) + { + fixed (byte* ptr = &(fromBuffer[readBitOffset / 8])) + { + return *(((ushort*)ptr)); + } + } +#else + public static ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits"); +#endif + ushort returnValue; + if (numberOfBits <= 8) + { + returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset); + return returnValue; + } + returnValue = ReadByte(fromBuffer, 8, readBitOffset); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + returnValue |= (ushort)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8); + } + +#if BIGENDIAN + // reorder bytes + uint retVal = returnValue; + retVal = ((retVal & 0x0000ff00) >> 8) | ((retVal & 0x000000ff) << 8); + return (ushort)retVal; +#else + return returnValue; +#endif + } + + /// + /// Reads the specified number of bits into an UInt32 + /// + [CLSCompliant(false)] +#if UNSAFE + public static unsafe uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); + + if (numberOfBits == 32 && ((readBitOffset % 8) == 0)) + { + fixed (byte* ptr = &(fromBuffer[readBitOffset / 8])) + { + return *(((uint*)ptr)); + } + } +#else + + public static uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); +#endif + uint returnValue; + if (numberOfBits <= 8) + { + returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset); + return returnValue; + } + returnValue = ReadByte(fromBuffer, 8, readBitOffset); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8); + return returnValue; + } + returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 8); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + uint r = ReadByte(fromBuffer, numberOfBits, readBitOffset); + r <<= 16; + returnValue |= r; + return returnValue; + } + returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 16); + numberOfBits -= 8; + readBitOffset += 8; + + returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 24); + +#if BIGENDIAN + // reorder bytes + return + ((returnValue & 0xff000000) >> 24) | + ((returnValue & 0x00ff0000) >> 8) | + ((returnValue & 0x0000ff00) << 8) | + ((returnValue & 0x000000ff) << 24); +#else + return returnValue; +#endif + } + + //[CLSCompliant(false)] + //public static ulong ReadUInt64(byte[] fromBuffer, int numberOfBits, int readBitOffset) + + /// + /// Writes an unsigned 16 bit integer + /// + [CLSCompliant(false)] + public static void WriteUInt16(ushort source, int numberOfBits, byte[] destination, int destinationBitOffset) + { + if (numberOfBits == 0) + return; + + NetException.Assert((numberOfBits >= 0 && numberOfBits <= 16), "numberOfBits must be between 0 and 16"); +#if BIGENDIAN + // reorder bytes + uint intSource = source; + intSource = ((intSource & 0x0000ff00) >> 8) | ((intSource & 0x000000ff) << 8); + source = (ushort)intSource; +#endif + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return; + } + + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + + numberOfBits -= 8; + if (numberOfBits > 0) + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset + 8); + } + + /// + /// Writes the specified number of bits into a byte array + /// + [CLSCompliant(false)] + public static int WriteUInt32(uint source, int numberOfBits, byte[] destination, int destinationBitOffset) + { +#if BIGENDIAN + // reorder bytes + source = ((source & 0xff000000) >> 24) | + ((source & 0x00ff0000) >> 8) | + ((source & 0x0000ff00) << 8) | + ((source & 0x000000ff) << 24); +#endif + + int returnValue = destinationBitOffset + numberOfBits; + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + + /// + /// Writes the specified number of bits into a byte array + /// + [CLSCompliant(false)] + public static int WriteUInt64(ulong source, int numberOfBits, byte[] destination, int destinationBitOffset) + { +#if BIGENDIAN + source = ((source & 0xff00000000000000L) >> 56) | + ((source & 0x00ff000000000000L) >> 40) | + ((source & 0x0000ff0000000000L) >> 24) | + ((source & 0x000000ff00000000L) >> 8) | + ((source & 0x00000000ff000000L) << 8) | + ((source & 0x0000000000ff0000L) << 24) | + ((source & 0x000000000000ff00L) << 40) | + ((source & 0x00000000000000ffL) << 56); +#endif + + int returnValue = destinationBitOffset + numberOfBits; + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + return returnValue; + } + + // + // Variable size + // + + /// + /// Write Base128 encoded variable sized unsigned integer + /// + /// number of bytes written + [CLSCompliant(false)] + public static int WriteVariableUInt32(byte[] intoBuffer, int offset, uint value) + { + int retval = 0; + uint num1 = (uint)value; + while (num1 >= 0x80) + { + intoBuffer[offset + retval] = (byte)(num1 | 0x80); + num1 = num1 >> 7; + retval++; + } + intoBuffer[offset + retval] = (byte)num1; + return retval + 1; + } + + /// + /// Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset! + /// + [CLSCompliant(false)] + public static uint ReadVariableUInt32(byte[] buffer, ref int offset) + { + int num1 = 0; + int num2 = 0; + while (true) + { + NetException.Assert(num2 != 0x23, "Bad 7-bit encoded integer"); + + byte num3 = buffer[offset++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + return (uint)num1; + } + } + } +} diff --git a/Lidgren.Network/NetBuffer.Peek.cs b/Lidgren.Network/NetBuffer.Peek.cs new file mode 100644 index 0000000..fa4adc9 --- /dev/null +++ b/Lidgren.Network/NetBuffer.Peek.cs @@ -0,0 +1,312 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; +using System.Net; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Gets the internal data buffer + /// + public byte[] PeekDataBuffer() { return m_data; } + + // + // 1 bit + // + /// + /// Reads a 1-bit Boolean without advancing the read pointer + /// + public bool PeekBoolean() + { + NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition); + return (retval > 0 ? true : false); + } + + // + // 8 bit + // + /// + /// Reads a Byte without advancing the read pointer + /// + public byte PeekByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + return retval; + } + + /// + /// Reads an SByte without advancing the read pointer + /// + [CLSCompliant(false)] + public sbyte PeekSByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + return (sbyte)retval; + } + + /// + /// Reads the specified number of bits into a Byte without advancing the read pointer + /// + public byte PeekByte(int numberOfBits) + { + byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition); + return retval; + } + + /// + /// Reads the specified number of bytes without advancing the read pointer + /// + public byte[] PeekBytes(int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + + byte[] retval = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); + return retval; + } + + /// + /// Reads the specified number of bytes without advancing the read pointer + /// + public void PeekBytes(byte[] into, int offset, int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + NetException.Assert(offset + numberOfBytes <= into.Length); + + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); + return; + } + + // + // 16 bit + // + /// + /// Reads an Int16 without advancing the read pointer + /// + public Int16 PeekInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + return (short)retval; + } + + /// + /// Reads a UInt16 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt16 PeekUInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + return (ushort)retval; + } + + // + // 32 bit + // + /// + /// Reads an Int32 without advancing the read pointer + /// + public Int32 PeekInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + return (Int32)retval; + } + + /// + /// Reads the specified number of bits into an Int32 without advancing the read pointer + /// + public Int32 PeekInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + + if (numberOfBits == 32) + return (int)retval; + + int signBit = 1 << (numberOfBits - 1); + if ((retval & signBit) == 0) + return (int)retval; // positive + + // negative + unchecked + { + uint mask = ((uint)-1) >> (33 - numberOfBits); + uint tmp = (retval & mask) + 1; + return -((int)tmp); + } + } + + /// + /// Reads a UInt32 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt32 PeekUInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + return retval; + } + + /// + /// Reads the specified number of bits into a UInt32 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt32 PeekUInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits"); + //NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size"); + + UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + return retval; + } + + // + // 64 bit + // + /// + /// Reads a UInt64 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt64 PeekUInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition + 32); + + ulong retval = low + (high << 32); + + return retval; + } + + /// + /// Reads an Int64 without advancing the read pointer + /// + public Int64 PeekInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + unchecked + { + ulong retval = PeekUInt64(); + long longRetval = (long)retval; + return longRetval; + } + } + + /// + /// Reads the specified number of bits into an UInt64 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt64 PeekUInt64(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + ulong retval; + if (numberOfBits <= 32) + { + retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + } + else + { + retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32; + } + return retval; + } + + /// + /// Reads the specified number of bits into an Int64 without advancing the read pointer + /// + public Int64 PeekInt64(int numberOfBits) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits"); + return (long)PeekUInt64(numberOfBits); + } + + // + // Floating point + // + /// + /// Reads a 32-bit Single without advancing the read pointer + /// + public float PeekFloat() + { + return PeekSingle(); + } + + /// + /// Reads a 32-bit Single without advancing the read pointer + /// + public float PeekSingle() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); + return retval; + } + + byte[] bytes = PeekBytes(4); + return BitConverter.ToSingle(bytes, 0); + } + + /// + /// Reads a 64-bit Double without advancing the read pointer + /// + public double PeekDouble() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // read directly + double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); + return retval; + } + + byte[] bytes = PeekBytes(8); + return BitConverter.ToDouble(bytes, 0); + } + + /// + /// Reads a string without advancing the read pointer + /// + public string PeekString() + { + int wasReadPosition = m_readPosition; + string retval = ReadString(); + m_readPosition = wasReadPosition; + return retval; + } + } +} + diff --git a/Lidgren.Network/NetBuffer.Read.Reflection.cs b/Lidgren.Network/NetBuffer.Read.Reflection.cs new file mode 100644 index 0000000..82b64aa --- /dev/null +++ b/Lidgren.Network/NetBuffer.Read.Reflection.cs @@ -0,0 +1,107 @@ + /* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Reflection; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Reads all public and private declared instance fields of the object in alphabetical order using reflection + /// + public void ReadAllFields(object target) + { + ReadAllFields(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Reads all fields with the specified binding of the object in alphabetical order using reflection + /// + public void ReadAllFields(object target, BindingFlags flags) + { + if (target == null) + throw new ArgumentNullException("target"); + + Type tp = target.GetType(); + + FieldInfo[] fields = tp.GetFields(flags); + NetUtility.SortMembersList(fields); + + foreach (FieldInfo fi in fields) + { + object value; + + // find read method + MethodInfo readMethod; + if (s_readMethods.TryGetValue(fi.FieldType, out readMethod)) + { + // read value + value = readMethod.Invoke(this, null); + + // set the value + fi.SetValue(target, value); + } + } + } + + /// + /// Reads all public and private declared instance fields of the object in alphabetical order using reflection + /// + public void ReadAllProperties(object target) + { + ReadAllProperties(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Reads all fields with the specified binding of the object in alphabetical order using reflection + /// + public void ReadAllProperties(object target, BindingFlags flags) + { + if (target == null) + throw new ArgumentNullException("target"); + + Type tp = target.GetType(); + + PropertyInfo[] fields = tp.GetProperties(flags); + NetUtility.SortMembersList(fields); + foreach (PropertyInfo fi in fields) + { + object value; + + // find read method + MethodInfo readMethod; + if (s_readMethods.TryGetValue(fi.PropertyType, out readMethod)) + { + // read value + value = readMethod.Invoke(this, null); + + // set the value +#if UNITY_WEBPLAYER || UNITY_4_5 + var setMethod = fi.GetSetMethod(); +#else + var setMethod = fi.SetMethod; +#endif + if (setMethod != null) + setMethod.Invoke(target, new object[] { value }); + } + } + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetBuffer.Read.cs b/Lidgren.Network/NetBuffer.Read.cs new file mode 100644 index 0000000..4109ab3 --- /dev/null +++ b/Lidgren.Network/NetBuffer.Read.cs @@ -0,0 +1,675 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; + +namespace Lidgren.Network +{ + /// + /// Base class for NetIncomingMessage and NetOutgoingMessage + /// + public partial class NetBuffer + { + private const string c_readOverflowError = "Trying to read past the buffer size - likely caused by mismatching Write/Reads, different size or order."; + + /// + /// Reads a boolean value (stored as a single bit) written using Write(bool) + /// + public bool ReadBoolean() + { + NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition); + m_readPosition += 1; + return (retval > 0 ? true : false); + } + + /// + /// Reads a byte + /// + public byte ReadByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return retval; + } + + /// + /// Reads a byte and returns true or false for success + /// + public bool ReadByte(out byte result) + { + if (m_bitLength - m_readPosition < 8) + { + result = 0; + return false; + } + result = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return true; + } + + /// + /// Reads a signed byte + /// + [CLSCompliant(false)] + public sbyte ReadSByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return (sbyte)retval; + } + + /// + /// Reads 1 to 8 bits into a byte + /// + public byte ReadByte(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 8, "ReadByte(bits) can only read between 1 and 8 bits"); + byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + return retval; + } + + /// + /// Reads the specified number of bytes + /// + public byte[] ReadBytes(int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError); + + byte[] retval = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); + m_readPosition += (8 * numberOfBytes); + return retval; + } + + /// + /// Reads the specified number of bytes and returns true for success + /// + public bool ReadBytes(int numberOfBytes, out byte[] result) + { + if (m_bitLength - m_readPosition + 7 < (numberOfBytes * 8)) + { + result = null; + return false; + } + + result = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, result, 0); + m_readPosition += (8 * numberOfBytes); + return true; + } + + /// + /// Reads the specified number of bytes into a preallocated array + /// + /// The destination array + /// The offset where to start writing in the destination array + /// The number of bytes to read + public void ReadBytes(byte[] into, int offset, int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError); + NetException.Assert(offset + numberOfBytes <= into.Length); + + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); + m_readPosition += (8 * numberOfBytes); + return; + } + + /// + /// Reads the specified number of bits into a preallocated array + /// + /// The destination array + /// The offset where to start writing in the destination array + /// The number of bits to read + public void ReadBits(byte[] into, int offset, int numberOfBits) + { + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + NetException.Assert(offset + NetUtility.BytesToHoldBits(numberOfBits) <= into.Length); + + int numberOfWholeBytes = numberOfBits / 8; + int extraBits = numberOfBits - (numberOfWholeBytes * 8); + + NetBitWriter.ReadBytes(m_data, numberOfWholeBytes, m_readPosition, into, offset); + m_readPosition += (8 * numberOfWholeBytes); + + if (extraBits > 0) + into[offset + numberOfWholeBytes] = ReadByte(extraBits); + + return; + } + + /// + /// Reads a 16 bit signed integer written using Write(Int16) + /// + public Int16 ReadInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + m_readPosition += 16; + return (short)retval; + } + + /// + /// Reads a 16 bit unsigned integer written using Write(UInt16) + /// + [CLSCompliant(false)] + public UInt16 ReadUInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + m_readPosition += 16; + return (ushort)retval; + } + + /// + /// Reads a 32 bit signed integer written using Write(Int32) + /// + public Int32 ReadInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return (Int32)retval; + } + + /// + /// Reads a 32 bit signed integer written using Write(Int32) + /// + [CLSCompliant(false)] + public bool ReadInt32(out Int32 result) + { + if (m_bitLength - m_readPosition < 32) + { + result = 0; + return false; + } + + result = (Int32)NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return true; + } + + /// + /// Reads a signed integer stored in 1 to 32 bits, written using Write(Int32, Int32) + /// + public Int32 ReadInt32(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadInt32(bits) can only read between 1 and 32 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + + if (numberOfBits == 32) + return (int)retval; + + int signBit = 1 << (numberOfBits - 1); + if ((retval & signBit) == 0) + return (int)retval; // positive + + // negative + unchecked + { + uint mask = ((uint)-1) >> (33 - numberOfBits); + uint tmp = (retval & mask) + 1; + return -((int)tmp); + } + } + + /// + /// Reads an 32 bit unsigned integer written using Write(UInt32) + /// + [CLSCompliant(false)] + public UInt32 ReadUInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return retval; + } + + /// + /// Reads an 32 bit unsigned integer written using Write(UInt32) and returns true for success + /// + [CLSCompliant(false)] + public bool ReadUInt32(out UInt32 result) + { + if (m_bitLength - m_readPosition < 32) + { + result = 0; + return false; + } + result = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return true; + } + + /// + /// Reads an unsigned integer stored in 1 to 32 bits, written using Write(UInt32, Int32) + /// + [CLSCompliant(false)] + public UInt32 ReadUInt32(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadUInt32(bits) can only read between 1 and 32 bits"); + //NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size"); + + UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + return retval; + } + + /// + /// Reads a 64 bit unsigned integer written using Write(UInt64) + /// + [CLSCompliant(false)] + public UInt64 ReadUInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + + ulong retval = low + (high << 32); + + m_readPosition += 32; + return retval; + } + + /// + /// Reads a 64 bit signed integer written using Write(Int64) + /// + public Int64 ReadInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + unchecked + { + ulong retval = ReadUInt64(); + long longRetval = (long)retval; + return longRetval; + } + } + + /// + /// Reads an unsigned integer stored in 1 to 64 bits, written using Write(UInt64, Int32) + /// + [CLSCompliant(false)] + public UInt64 ReadUInt64(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 64, "ReadUInt64(bits) can only read between 1 and 64 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + ulong retval; + if (numberOfBits <= 32) + { + retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + } + else + { + retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32; + } + m_readPosition += numberOfBits; + return retval; + } + + /// + /// Reads a signed integer stored in 1 to 64 bits, written using Write(Int64, Int32) + /// + public Int64 ReadInt64(int numberOfBits) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 64)), "ReadInt64(bits) can only read between 1 and 64 bits"); + return (long)ReadUInt64(numberOfBits); + } + + /// + /// Reads a 32 bit floating point value written using Write(Single) + /// + public float ReadFloat() + { + return ReadSingle(); + } + + /// + /// Reads a 32 bit floating point value written using Write(Single) + /// + public float ReadSingle() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); + m_readPosition += 32; + return retval; + } + + byte[] bytes = ReadBytes(4); + return BitConverter.ToSingle(bytes, 0); + } + + /// + /// Reads a 32 bit floating point value written using Write(Single) + /// + public bool ReadSingle(out float result) + { + if (m_bitLength - m_readPosition < 32) + { + result = 0.0f; + return false; + } + + if ((m_readPosition & 7) == 0) // read directly + { + result = BitConverter.ToSingle(m_data, m_readPosition >> 3); + m_readPosition += 32; + return true; + } + + byte[] bytes = ReadBytes(4); + result = BitConverter.ToSingle(bytes, 0); + return true; + } + + /// + /// Reads a 64 bit floating point value written using Write(Double) + /// + public double ReadDouble() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // read directly + double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); + m_readPosition += 64; + return retval; + } + + byte[] bytes = ReadBytes(8); + return BitConverter.ToDouble(bytes, 0); + } + + // + // Variable bit count + // + + /// + /// Reads a variable sized UInt32 written using WriteVariableUInt32() + /// + [CLSCompliant(false)] + public uint ReadVariableUInt32() + { + int num1 = 0; + int num2 = 0; + while (m_bitLength - m_readPosition >= 8) + { + byte num3 = this.ReadByte(); + num1 |= (num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + return (uint)num1; + } + + // ouch; failed to find enough bytes; malformed variable length number? + return (uint)num1; + } + + /// + /// Reads a variable sized UInt32 written using WriteVariableUInt32() and returns true for success + /// + [CLSCompliant(false)] + public bool ReadVariableUInt32(out uint result) + { + int num1 = 0; + int num2 = 0; + while (m_bitLength - m_readPosition >= 8) + { + byte num3; + if (ReadByte(out num3) == false) + { + result = 0; + return false; + } + num1 |= (num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + { + result = (uint)num1; + return true; + } + } + result = (uint)num1; + return false; + } + + /// + /// Reads a variable sized Int32 written using WriteVariableInt32() + /// + public int ReadVariableInt32() + { + uint n = ReadVariableUInt32(); + return (int)(n >> 1) ^ -(int)(n & 1); // decode zigzag + } + + /// + /// Reads a variable sized Int64 written using WriteVariableInt64() + /// + public Int64 ReadVariableInt64() + { + UInt64 n = ReadVariableUInt64(); + return (Int64)(n >> 1) ^ -(long)(n & 1); // decode zigzag + } + + /// + /// Reads a variable sized UInt32 written using WriteVariableInt64() + /// + [CLSCompliant(false)] + public UInt64 ReadVariableUInt64() + { + UInt64 num1 = 0; + int num2 = 0; + while (m_bitLength - m_readPosition >= 8) + { + //if (num2 == 0x23) + // throw new FormatException("Bad 7-bit encoded integer"); + + byte num3 = this.ReadByte(); + num1 |= ((UInt64)num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + return num1; + } + + // ouch; failed to find enough bytes; malformed variable length number? + return num1; + } + + /// + /// Reads a 32 bit floating point value written using WriteSignedSingle() + /// + /// The number of bits used when writing the value + /// A floating point value larger or equal to -1 and smaller or equal to 1 + public float ReadSignedSingle(int numberOfBits) + { + uint encodedVal = ReadUInt32(numberOfBits); + int maxVal = (1 << numberOfBits) - 1; + return ((float)(encodedVal + 1) / (float)(maxVal + 1) - 0.5f) * 2.0f; + } + + /// + /// Reads a 32 bit floating point value written using WriteUnitSingle() + /// + /// The number of bits used when writing the value + /// A floating point value larger or equal to 0 and smaller or equal to 1 + public float ReadUnitSingle(int numberOfBits) + { + uint encodedVal = ReadUInt32(numberOfBits); + int maxVal = (1 << numberOfBits) - 1; + return (float)(encodedVal + 1) / (float)(maxVal + 1); + } + + /// + /// Reads a 32 bit floating point value written using WriteRangedSingle() + /// + /// The minimum value used when writing the value + /// The maximum value used when writing the value + /// The number of bits used when writing the value + /// A floating point value larger or equal to MIN and smaller or equal to MAX + public float ReadRangedSingle(float min, float max, int numberOfBits) + { + float range = max - min; + int maxVal = (1 << numberOfBits) - 1; + float encodedVal = (float)ReadUInt32(numberOfBits); + float unit = encodedVal / (float)maxVal; + return min + (unit * range); + } + + /// + /// Reads a 32 bit integer value written using WriteRangedInteger() + /// + /// The minimum value used when writing the value + /// The maximum value used when writing the value + /// A signed integer value larger or equal to MIN and smaller or equal to MAX + public int ReadRangedInteger(int min, int max) + { + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); + + uint rvalue = ReadUInt32(numBits); + return (int)(min + rvalue); + } + + /// + /// Reads a string written using Write(string) + /// + public string ReadString() + { + int byteLen = (int)ReadVariableUInt32(); + + if (byteLen <= 0) + return String.Empty; + + if ((ulong)(m_bitLength - m_readPosition) < ((ulong)byteLen * 8)) + { + // not enough data +#if DEBUG + + throw new NetException(c_readOverflowError); +#else + m_readPosition = m_bitLength; + return null; // unfortunate; but we need to protect against DDOS +#endif + } + + if ((m_readPosition & 7) == 0) + { + // read directly + string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen); + m_readPosition += (8 * byteLen); + return retval; + } + + byte[] bytes = ReadBytes(byteLen); + return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + /// + /// Reads a string written using Write(string) and returns true for success + /// + public bool ReadString(out string result) + { + uint byteLen; + if (ReadVariableUInt32(out byteLen) == false) + { + result = String.Empty; + return false; + } + + if (byteLen <= 0) + { + result = String.Empty; + return true; + } + + if (m_bitLength - m_readPosition < (byteLen * 8)) + { + result = String.Empty; + return false; + } + + if ((m_readPosition & 7) == 0) + { + // read directly + result = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, (int)byteLen); + m_readPosition += (8 * (int)byteLen); + return true; + } + + byte[] bytes; + if (ReadBytes((int)byteLen, out bytes) == false) + { + result = String.Empty; + return false; + } + + result = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + return true; + } + + /// + /// Reads a value, in local time comparable to NetTime.Now, written using WriteTime() for the connection supplied + /// + public double ReadTime(NetConnection connection, bool highPrecision) + { + double remoteTime = (highPrecision ? ReadDouble() : (double)ReadSingle()); + + if (connection == null) + throw new NetException("Cannot call ReadTime() on message without a connected sender (ie. unconnected messages)"); + + // lets bypass NetConnection.GetLocalTime for speed + return remoteTime - connection.m_remoteTimeOffset; + } + + /// + /// Reads a stored IPv4 endpoint description + /// + public IPEndPoint ReadIPEndPoint() + { + byte len = ReadByte(); + byte[] addressBytes = ReadBytes(len); + int port = (int)ReadUInt16(); + + IPAddress address = new IPAddress(addressBytes); + return new IPEndPoint(address, port); + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void SkipPadBits() + { + m_readPosition = ((m_readPosition + 7) >> 3) * 8; + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void ReadPadBits() + { + m_readPosition = ((m_readPosition + 7) >> 3) * 8; + } + + /// + /// Pads data with the specified number of bits. + /// + public void SkipPadBits(int numberOfBits) + { + m_readPosition += numberOfBits; + } + } +} diff --git a/Lidgren.Network/NetBuffer.Write.Reflection.cs b/Lidgren.Network/NetBuffer.Write.Reflection.cs new file mode 100644 index 0000000..2d467b9 --- /dev/null +++ b/Lidgren.Network/NetBuffer.Write.Reflection.cs @@ -0,0 +1,98 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Reflection; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Writes all public and private declared instance fields of the object in alphabetical order using reflection + /// + public void WriteAllFields(object ob) + { + WriteAllFields(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Writes all fields with specified binding in alphabetical order using reflection + /// + public void WriteAllFields(object ob, BindingFlags flags) + { + if (ob == null) + return; + Type tp = ob.GetType(); + + FieldInfo[] fields = tp.GetFields(flags); + NetUtility.SortMembersList(fields); + + foreach (FieldInfo fi in fields) + { + object value = fi.GetValue(ob); + + // find the appropriate Write method + MethodInfo writeMethod; + if (s_writeMethods.TryGetValue(fi.FieldType, out writeMethod)) + writeMethod.Invoke(this, new object[] { value }); + else + throw new NetException("Failed to find write method for type " + fi.FieldType); + } + } + + /// + /// Writes all public and private declared instance properties of the object in alphabetical order using reflection + /// + public void WriteAllProperties(object ob) + { + WriteAllProperties(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Writes all properties with specified binding in alphabetical order using reflection + /// + public void WriteAllProperties(object ob, BindingFlags flags) + { + if (ob == null) + return; + Type tp = ob.GetType(); + + PropertyInfo[] fields = tp.GetProperties(flags); + NetUtility.SortMembersList(fields); + + foreach (PropertyInfo fi in fields) + { +#if UNITY_WEBPLAYER || UNITY_4_5 + MethodInfo getMethod = fi.GetGetMethod(); +#else + MethodInfo getMethod = fi.GetMethod; +#endif + if (getMethod != null) + { + object value = getMethod.Invoke(ob, null); + + // find the appropriate Write method + MethodInfo writeMethod; + if (s_writeMethods.TryGetValue(fi.PropertyType, out writeMethod)) + writeMethod.Invoke(this, new object[] { value }); + } + } + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetBuffer.Write.cs b/Lidgren.Network/NetBuffer.Write.cs new file mode 100644 index 0000000..26a7d1b --- /dev/null +++ b/Lidgren.Network/NetBuffer.Write.cs @@ -0,0 +1,676 @@ +//#define UNSAFE +//#define BIGENDIAN +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Text; +using System.Runtime.InteropServices; + +namespace Lidgren.Network +{ + /// + /// Utility struct for writing Singles + /// + [StructLayout(LayoutKind.Explicit)] + public struct SingleUIntUnion + { + /// + /// Value as a 32 bit float + /// + [FieldOffset(0)] + public float SingleValue; + + /// + /// Value as an unsigned 32 bit integer + /// + [FieldOffset(0)] + [CLSCompliant(false)] + public uint UIntValue; + } + + public partial class NetBuffer + { + /// + /// Ensures the buffer can hold this number of bits + /// + public void EnsureBufferSize(int numberOfBits) + { + int byteLen = ((numberOfBits + 7) >> 3); + if (m_data == null) + { + m_data = new byte[byteLen + c_overAllocateAmount]; + return; + } + if (m_data.Length < byteLen) + Array.Resize(ref m_data, byteLen + c_overAllocateAmount); + return; + } + + /// + /// Ensures the buffer can hold this number of bits + /// + internal void InternalEnsureBufferSize(int numberOfBits) + { + int byteLen = ((numberOfBits + 7) >> 3); + if (m_data == null) + { + m_data = new byte[byteLen]; + return; + } + if (m_data.Length < byteLen) + Array.Resize(ref m_data, byteLen); + return; + } + + /// + /// Writes a boolean value using 1 bit + /// + public void Write(bool value) + { + EnsureBufferSize(m_bitLength + 1); + NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength); + m_bitLength += 1; + } + + /// + /// Write a byte + /// + public void Write(byte source) + { + EnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte(source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + /// + /// Writes a signed byte + /// + [CLSCompliant(false)] + public void Write(sbyte source) + { + EnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + /// + /// Writes 1 to 8 bits of a byte + /// + public void Write(byte source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes all bytes in an array + /// + public void Write(byte[] source) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = source.Length * 8; + EnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength); + m_bitLength += bits; + } + + /// + /// Writes the specified number of bytes from an array + /// + public void Write(byte[] source, int offsetInBytes, int numberOfBytes) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = numberOfBytes * 8; + EnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength); + m_bitLength += bits; + } + + /// + /// Writes an unsigned 16 bit integer + /// + /// + [CLSCompliant(false)] + public void Write(UInt16 source) + { + EnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt16(source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + /// + /// Writes a 16 bit unsigned integer at a given offset in the buffer + /// + [CLSCompliant(false)] + public void WriteAt(Int32 offset, UInt16 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 16); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt16(source, 16, m_data, offset); + m_bitLength = newBitLength; + } + + /// + /// Writes an unsigned integer using 1 to 16 bits + /// + [CLSCompliant(false)] + public void Write(UInt16 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt16(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes a signed 16 bit integer + /// + public void Write(Int16 source) + { + EnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt16((ushort)source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + /// + /// Writes a 16 bit signed integer at a given offset in the buffer + /// + public void WriteAt(Int32 offset, Int16 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 16); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt16((ushort)source, 16, m_data, offset); + m_bitLength = newBitLength; + } + +#if UNSAFE + /// + /// Writes a 32 bit signed integer + /// + public unsafe void Write(Int32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((int*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength); + } + m_bitLength += 32; + } +#else + /// + /// Writes a 32 bit signed integer + /// + public void Write(Int32 source) + { + EnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + + /// + /// Writes a 32 bit signed integer at a given offset in the buffer + /// + public void WriteAt(Int32 offset, Int32 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 32); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, offset); + m_bitLength = newBitLength; + } + +#if UNSAFE + /// + /// Writes a 32 bit unsigned integer + /// + public unsafe void Write(UInt32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((uint*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength); + } + + m_bitLength += 32; + } +#else + /// + /// Writes a 32 bit unsigned integer + /// + [CLSCompliant(false)] + public void Write(UInt32 source) + { + EnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + + /// + /// Writes a 32 bit unsigned integer at a given offset in the buffer + /// + [CLSCompliant(false)] + public void WriteAt(Int32 offset, UInt32 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 32); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt32(source, 32, m_data, offset); + m_bitLength = newBitLength; + } + + /// + /// Writes a 32 bit signed integer + /// + [CLSCompliant(false)] + public void Write(UInt32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes a signed integer using 1 to 32 bits + /// + public void Write(Int32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + + if (numberOfBits != 32) + { + // make first bit sign + int signBit = 1 << (numberOfBits - 1); + if (source < 0) + source = (-source - 1) | signBit; + else + source &= (~signBit); + } + + NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); + + m_bitLength += numberOfBits; + } + + /// + /// Writes a 64 bit unsigned integer + /// + [CLSCompliant(false)] + public void Write(UInt64 source) + { + EnsureBufferSize(m_bitLength + 64); + NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + /// + /// Writes a 64 bit unsigned integer at a given offset in the buffer + /// + [CLSCompliant(false)] + public void WriteAt(Int32 offset, UInt64 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 64); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt64(source, 64, m_data, offset); + m_bitLength = newBitLength; + } + + /// + /// Writes an unsigned integer using 1 to 64 bits + /// + [CLSCompliant(false)] + public void Write(UInt64 source, int numberOfBits) + { + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes a 64 bit signed integer + /// + public void Write(Int64 source) + { + EnsureBufferSize(m_bitLength + 64); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + /// + /// Writes a signed integer using 1 to 64 bits + /// + public void Write(Int64 source, int numberOfBits) + { + EnsureBufferSize(m_bitLength + numberOfBits); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + // + // Floating point + // +#if UNSAFE + /// + /// Writes a 32 bit floating point value + /// + public unsafe void Write(float source) + { + uint val = *((uint*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + /// + /// Writes a 32 bit floating point value + /// + public void Write(float source) + { + // Use union to avoid BitConverter.GetBytes() which allocates memory on the heap + SingleUIntUnion su; + su.UIntValue = 0; // must initialize every member of the union to avoid warning + su.SingleValue = source; + +#if BIGENDIAN + // swap byte order + su.UIntValue = NetUtility.SwapByteOrder(su.UIntValue); +#endif + Write(su.UIntValue); + } +#endif + +#if UNSAFE + /// + /// Writes a 64 bit floating point value + /// + public unsafe void Write(double source) + { + ulong val = *((ulong*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + /// + /// Writes a 64 bit floating point value + /// + public void Write(double source) + { + byte[] val = BitConverter.GetBytes(source); +#if BIGENDIAN + // 0 1 2 3 4 5 6 7 + + // swap byte order + byte tmp = val[7]; + val[7] = val[0]; + val[0] = tmp; + + tmp = val[6]; + val[6] = val[1]; + val[1] = tmp; + + tmp = val[5]; + val[5] = val[2]; + val[2] = tmp; + + tmp = val[4]; + val[4] = val[3]; + val[3] = tmp; +#endif + Write(val); + } +#endif + + // + // Variable bits + // + + /// + /// Write Base128 encoded variable sized unsigned integer of up to 32 bits + /// + /// number of bytes written + [CLSCompliant(false)] + public int WriteVariableUInt32(uint value) + { + int retval = 1; + uint num1 = (uint)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Write Base128 encoded variable sized signed integer of up to 32 bits + /// + /// number of bytes written + public int WriteVariableInt32(int value) + { + uint zigzag = (uint)(value << 1) ^ (uint)(value >> 31); + return WriteVariableUInt32(zigzag); + } + + /// + /// Write Base128 encoded variable sized signed integer of up to 64 bits + /// + /// number of bytes written + public int WriteVariableInt64(Int64 value) + { + ulong zigzag = (ulong)(value << 1) ^ (ulong)(value >> 63); + return WriteVariableUInt64(zigzag); + } + + /// + /// Write Base128 encoded variable sized unsigned integer of up to 64 bits + /// + /// number of bytes written + [CLSCompliant(false)] + public int WriteVariableUInt64(UInt64 value) + { + int retval = 1; + UInt64 num1 = (UInt64)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Compress (lossy) a float in the range -1..1 using numberOfBits bits + /// + public void WriteSignedSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value); + + float unit = (value + 1.0f) * 0.5f; + int maxVal = (1 << numberOfBits) - 1; + uint writeVal = (uint)(unit * (float)maxVal); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress (lossy) a float in the range 0..1 using numberOfBits bits + /// + public void WriteUnitSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value); + + int maxValue = (1 << numberOfBits) - 1; + uint writeVal = (uint)(value * (float)maxValue); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress a float within a specified range using a certain number of bits + /// + public void WriteRangedSingle(float value, float min, float max, int numberOfBits) + { + NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value); + + float range = max - min; + float unit = ((value - min) / range); + int maxVal = (1 << numberOfBits) - 1; + Write((UInt32)((float)maxVal * unit), numberOfBits); + } + + /// + /// Writes an integer with the least amount of bits need for the specified range + /// Returns number of bits written + /// + public int WriteRangedInteger(int min, int max, int value) + { + NetException.Assert(value >= min && value <= max, "Value not within min/max range!"); + + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); + + uint rvalue = (uint)(value - min); + Write(rvalue, numBits); + + return numBits; + } + + /// + /// Write a string + /// + public void Write(string source) + { + if (string.IsNullOrEmpty(source)) + { + WriteVariableUInt32(0); + return; + } + + byte[] bytes = Encoding.UTF8.GetBytes(source); + EnsureBufferSize(m_bitLength + 8 + (bytes.Length * 8)); + WriteVariableUInt32((uint)bytes.Length); + Write(bytes); + } + + /// + /// Writes an endpoint description + /// + public void Write(IPEndPoint endPoint) + { + byte[] bytes = endPoint.Address.GetAddressBytes(); + Write((byte)bytes.Length); + Write(bytes); + Write((ushort)endPoint.Port); + } + + /// + /// Writes the current local time to a message; readable (and convertable to local time) by the remote host using ReadTime() + /// + public void WriteTime(bool highPrecision) + { + double localTime = NetTime.Now; + if (highPrecision) + Write(localTime); + else + Write((float)localTime); + } + + /// + /// Writes a local timestamp to a message; readable (and convertable to local time) by the remote host using ReadTime() + /// + public void WriteTime(double localTime, bool highPrecision) + { + if (highPrecision) + Write(localTime); + else + Write((float)localTime); + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void WritePadBits() + { + m_bitLength = ((m_bitLength + 7) >> 3) * 8; + EnsureBufferSize(m_bitLength); + } + + /// + /// Pads data with the specified number of bits. + /// + public void WritePadBits(int numberOfBits) + { + m_bitLength += numberOfBits; + EnsureBufferSize(m_bitLength); + } + + /// + /// Append all the bits of message to this message + /// + public void Write(NetBuffer buffer) + { + EnsureBufferSize(m_bitLength + (buffer.LengthBytes * 8)); + + Write(buffer.m_data, 0, buffer.LengthBytes); + + // did we write excessive bits? + int bitsInLastByte = (buffer.m_bitLength % 8); + if (bitsInLastByte != 0) + { + int excessBits = 8 - bitsInLastByte; + m_bitLength -= excessBits; + } + } + } +} diff --git a/Lidgren.Network/NetBuffer.cs b/Lidgren.Network/NetBuffer.cs new file mode 100644 index 0000000..34c783f --- /dev/null +++ b/Lidgren.Network/NetBuffer.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Number of bytes to overallocate for each message to avoid resizing + /// + protected const int c_overAllocateAmount = 4; + + private static readonly Dictionary s_readMethods; + private static readonly Dictionary s_writeMethods; + + internal byte[] m_data; + internal int m_bitLength; + internal int m_readPosition; + + /// + /// Gets or sets the internal data buffer + /// + public byte[] Data + { + get { return m_data; } + set { m_data = value; } + } + + /// + /// Gets or sets the length of the used portion of the buffer in bytes + /// + public int LengthBytes + { + get { return ((m_bitLength + 7) >> 3); } + set + { + m_bitLength = value * 8; + InternalEnsureBufferSize(m_bitLength); + } + } + + /// + /// Gets or sets the length of the used portion of the buffer in bits + /// + public int LengthBits + { + get { return m_bitLength; } + set + { + m_bitLength = value; + InternalEnsureBufferSize(m_bitLength); + } + } + + /// + /// Gets or sets the read position in the buffer, in bits (not bytes) + /// + public long Position + { + get { return (long)m_readPosition; } + set { m_readPosition = (int)value; } + } + + /// + /// Gets the position in the buffer in bytes; note that the bits of the first returned byte may already have been read - check the Position property to make sure. + /// + public int PositionInBytes + { + get { return (int)(m_readPosition / 8); } + } + + static NetBuffer() + { + s_readMethods = new Dictionary(); + MethodInfo[] methods = typeof(NetIncomingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public); + foreach (MethodInfo mi in methods) + { + if (mi.GetParameters().Length == 0 && mi.Name.StartsWith("Read", StringComparison.InvariantCulture) && mi.Name.Substring(4) == mi.ReturnType.Name) + { + s_readMethods[mi.ReturnType] = mi; + } + } + + s_writeMethods = new Dictionary(); + methods = typeof(NetOutgoingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public); + foreach (MethodInfo mi in methods) + { + if (mi.Name.Equals("Write", StringComparison.InvariantCulture)) + { + ParameterInfo[] pis = mi.GetParameters(); + if (pis.Length == 1) + s_writeMethods[pis[0].ParameterType] = mi; + } + } + } + } +} diff --git a/Lidgren.Network/NetClient.cs b/Lidgren.Network/NetClient.cs new file mode 100644 index 0000000..588de49 --- /dev/null +++ b/Lidgren.Network/NetClient.cs @@ -0,0 +1,171 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Net; + +namespace Lidgren.Network +{ + /// + /// Specialized version of NetPeer used for a "client" connection. It does not accept any incoming connections and maintains a ServerConnection property + /// + public class NetClient : NetPeer + { + /// + /// Gets the connection to the server, if any + /// + public NetConnection ServerConnection + { + get + { + NetConnection retval = null; + if (m_connections.Count > 0) + { + try + { + retval = m_connections[0]; + } + catch + { + // preempted! + return null; + } + } + return retval; + } + } + + /// + /// Gets the connection status of the server connection (or NetConnectionStatus.Disconnected if no connection) + /// + public NetConnectionStatus ConnectionStatus + { + get + { + var conn = ServerConnection; + if (conn == null) + return NetConnectionStatus.Disconnected; + return conn.Status; + } + } + + /// + /// NetClient constructor + /// + /// + public NetClient(NetPeerConfiguration config) + : base(config) + { + config.AcceptIncomingConnections = false; + } + + /// + /// Connect to a remote server + /// + /// The remote endpoint to connect to + /// The hail message to pass + /// server connection, or null if already connected + public override NetConnection Connect(IPEndPoint remoteEndPoint, NetOutgoingMessage hailMessage) + { + lock (m_connections) + { + if (m_connections.Count > 0) + { + LogWarning("Connect attempt failed; Already connected"); + return null; + } + } + + lock (m_handshakes) + { + if (m_handshakes.Count > 0) + { + LogWarning("Connect attempt failed; Handshake already in progress"); + return null; + } + } + + return base.Connect(remoteEndPoint, hailMessage); + } + + /// + /// Disconnect from server + /// + /// reason for disconnect + public void Disconnect(string byeMessage) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + lock (m_handshakes) + { + if (m_handshakes.Count > 0) + { + LogVerbose("Aborting connection attempt"); + foreach(var hs in m_handshakes) + hs.Value.Disconnect(byeMessage); + return; + } + } + + LogWarning("Disconnect requested when not connected!"); + return; + } + serverConnection.Disconnect(byeMessage); + } + + /// + /// Sends message to server + /// + public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + LogWarning("Cannot send message, no server connection!"); + return NetSendResult.FailedNotConnected; + } + + return serverConnection.SendMessage(msg, method, 0); + } + + /// + /// Sends message to server + /// + public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + LogWarning("Cannot send message, no server connection!"); + return NetSendResult.FailedNotConnected; + } + + return serverConnection.SendMessage(msg, method, sequenceChannel); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetClient " + ServerConnection + "]"; + } + + } +} diff --git a/Lidgren.Network/NetConnection.Handshake.cs b/Lidgren.Network/NetConnection.Handshake.cs new file mode 100644 index 0000000..d67aa8b --- /dev/null +++ b/Lidgren.Network/NetConnection.Handshake.cs @@ -0,0 +1,493 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + internal bool m_connectRequested; + internal bool m_disconnectRequested; + internal bool m_disconnectReqSendBye; + internal string m_disconnectMessage; + internal bool m_connectionInitiator; + internal NetIncomingMessage m_remoteHailMessage; + internal float m_lastHandshakeSendTime; + internal int m_handshakeAttempts; + + /// + /// The message that the remote part specified via Connect() or Approve() - can be null. + /// + public NetIncomingMessage RemoteHailMessage { get { return m_remoteHailMessage; } } + + // heartbeat called when connection still is in m_handshakes of NetPeer + internal void UnconnectedHeartbeat(float now) + { + m_peer.VerifyNetworkThread(); + + if (m_disconnectRequested) + ExecuteDisconnect(m_disconnectMessage, true); + + if (m_connectRequested) + { + switch (m_status) + { + case NetConnectionStatus.Connected: + case NetConnectionStatus.RespondedConnect: + // reconnect + ExecuteDisconnect("Reconnecting", true); + break; + + case NetConnectionStatus.InitiatedConnect: + // send another connect attempt + SendConnect(now); + break; + + case NetConnectionStatus.Disconnected: + m_peer.ThrowOrLog("This connection is Disconnected; spent. A new one should have been created"); + break; + + case NetConnectionStatus.Disconnecting: + // let disconnect finish first + break; + + case NetConnectionStatus.None: + default: + SendConnect(now); + break; + } + return; + } + + if (now - m_lastHandshakeSendTime > m_peerConfiguration.m_resendHandshakeInterval) + { + if (m_handshakeAttempts >= m_peerConfiguration.m_maximumHandshakeAttempts) + { + // failed to connect + ExecuteDisconnect("Failed to establish connection - no response from remote host", true); + return; + } + + // resend handshake + switch (m_status) + { + case NetConnectionStatus.InitiatedConnect: + SendConnect(now); + break; + case NetConnectionStatus.RespondedConnect: + SendConnectResponse(now, true); + break; + case NetConnectionStatus.RespondedAwaitingApproval: + // awaiting approval + m_lastHandshakeSendTime = now; // postpone handshake resend + break; + case NetConnectionStatus.None: + case NetConnectionStatus.ReceivedInitiation: + default: + m_peer.LogWarning("Time to resend handshake, but status is " + m_status); + break; + } + } + } + + internal void ExecuteDisconnect(string reason, bool sendByeMessage) + { + m_peer.VerifyNetworkThread(); + + // clear send queues + for (int i = 0; i < m_sendChannels.Length; i++) + { + NetSenderChannelBase channel = m_sendChannels[i]; + if (channel != null) + channel.Reset(); + } + + if (sendByeMessage) + SendDisconnect(reason, true); + + if (m_status == NetConnectionStatus.ReceivedInitiation) + { + // nothing much has happened yet; no need to send disconnected status message + m_status = NetConnectionStatus.Disconnected; + } + else + { + SetStatus(NetConnectionStatus.Disconnected, reason); + } + + // in case we're still in handshake + lock (m_peer.m_handshakes) + m_peer.m_handshakes.Remove(m_remoteEndPoint); + + m_disconnectRequested = false; + m_connectRequested = false; + m_handshakeAttempts = 0; + } + + internal void SendConnect(float now) + { + m_peer.VerifyNetworkThread(); + + int preAllocate = 13 + m_peerConfiguration.AppIdentifier.Length; + preAllocate += (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes); + + NetOutgoingMessage om = m_peer.CreateMessage(preAllocate); + om.m_messageType = NetMessageType.Connect; + om.Write(m_peerConfiguration.AppIdentifier); + om.Write(m_peer.m_uniqueIdentifier); + om.Write(now); + + WriteLocalHail(om); + + m_peer.SendLibrary(om, m_remoteEndPoint); + + m_connectRequested = false; + m_lastHandshakeSendTime = now; + m_handshakeAttempts++; + + if (m_handshakeAttempts > 1) + m_peer.LogDebug("Resending Connect..."); + SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect"); + } + + internal void SendConnectResponse(float now, bool onLibraryThread) + { + if (onLibraryThread) + m_peer.VerifyNetworkThread(); + + NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 13 + (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes)); + om.m_messageType = NetMessageType.ConnectResponse; + om.Write(m_peerConfiguration.AppIdentifier); + om.Write(m_peer.m_uniqueIdentifier); + om.Write(now); + + WriteLocalHail(om); + + if (onLibraryThread) + m_peer.SendLibrary(om, m_remoteEndPoint); + else + m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple(m_remoteEndPoint, om)); + + m_lastHandshakeSendTime = now; + m_handshakeAttempts++; + + if (m_handshakeAttempts > 1) + m_peer.LogDebug("Resending ConnectResponse..."); + + SetStatus(NetConnectionStatus.RespondedConnect, "Remotely requested connect"); + } + + internal void SendDisconnect(string reason, bool onLibraryThread) + { + if (onLibraryThread) + m_peer.VerifyNetworkThread(); + + NetOutgoingMessage om = m_peer.CreateMessage(reason); + om.m_messageType = NetMessageType.Disconnect; + if (onLibraryThread) + m_peer.SendLibrary(om, m_remoteEndPoint); + else + m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple(m_remoteEndPoint, om)); + } + + private void WriteLocalHail(NetOutgoingMessage om) + { + if (m_localHailMessage != null) + { + byte[] hi = m_localHailMessage.Data; + if (hi != null && hi.Length >= m_localHailMessage.LengthBytes) + { + if (om.LengthBytes + m_localHailMessage.LengthBytes > m_peerConfiguration.m_maximumTransmissionUnit - 10) + m_peer.ThrowOrLog("Hail message too large; can maximally be " + (m_peerConfiguration.m_maximumTransmissionUnit - 10 - om.LengthBytes)); + om.Write(m_localHailMessage.Data, 0, m_localHailMessage.LengthBytes); + } + } + } + + internal void SendConnectionEstablished() + { + NetOutgoingMessage om = m_peer.CreateMessage(4); + om.m_messageType = NetMessageType.ConnectionEstablished; + om.Write((float)NetTime.Now); + m_peer.SendLibrary(om, m_remoteEndPoint); + + m_handshakeAttempts = 0; + + InitializePing(); + if (m_status != NetConnectionStatus.Connected) + SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier)); + } + + /// + /// Approves this connection; sending a connection response to the remote host + /// + public void Approve() + { + if (m_status != NetConnectionStatus.RespondedAwaitingApproval) + { + m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status); + return; + } + + m_localHailMessage = null; + m_handshakeAttempts = 0; + SendConnectResponse((float)NetTime.Now, false); + } + + /// + /// Approves this connection; sending a connection response to the remote host + /// + /// The local hail message that will be set as RemoteHailMessage on the remote host + public void Approve(NetOutgoingMessage localHail) + { + if (m_status != NetConnectionStatus.RespondedAwaitingApproval) + { + m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status); + return; + } + + m_localHailMessage = localHail; + m_handshakeAttempts = 0; + SendConnectResponse((float)NetTime.Now, false); + } + + /// + /// Denies this connection; disconnecting it + /// + public void Deny() + { + Deny(string.Empty); + } + + /// + /// Denies this connection; disconnecting it + /// + /// The stated reason for the disconnect, readable as a string in the StatusChanged message on the remote host + public void Deny(string reason) + { + // send disconnect; remove from handshakes + SendDisconnect(reason, false); + + // remove from handshakes + lock (m_peer.m_handshakes) + m_peer.m_handshakes.Remove(m_remoteEndPoint); + } + + internal void ReceivedHandshake(double now, NetMessageType tp, int ptr, int payloadLength) + { + m_peer.VerifyNetworkThread(); + + byte[] hail; + switch (tp) + { + case NetMessageType.Connect: + if (m_status == NetConnectionStatus.ReceivedInitiation) + { + // Whee! Server full has already been checked + bool ok = ValidateHandshakeData(ptr, payloadLength, out hail); + if (ok) + { + if (hail != null) + { + m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail); + m_remoteHailMessage.LengthBits = (hail.Length * 8); + } + else + { + m_remoteHailMessage = null; + } + + if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval)) + { + // ok, let's not add connection just yet + NetIncomingMessage appMsg = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, (m_remoteHailMessage == null ? 0 : m_remoteHailMessage.LengthBytes)); + appMsg.m_receiveTime = now; + appMsg.m_senderConnection = this; + appMsg.m_senderEndPoint = this.m_remoteEndPoint; + if (m_remoteHailMessage != null) + appMsg.Write(m_remoteHailMessage.m_data, 0, m_remoteHailMessage.LengthBytes); + SetStatus(NetConnectionStatus.RespondedAwaitingApproval, "Awaiting approval"); + m_peer.ReleaseMessage(appMsg); + return; + } + + SendConnectResponse((float)now, true); + } + return; + } + if (m_status == NetConnectionStatus.RespondedAwaitingApproval) + { + m_peer.LogWarning("Ignoring multiple Connect() most likely due to a delayed Approval"); + return; + } + if (m_status == NetConnectionStatus.RespondedConnect) + { + // our ConnectResponse must have been lost + SendConnectResponse((float)now, true); + return; + } + m_peer.LogDebug("Unhandled Connect: " + tp + ", status is " + m_status + " length: " + payloadLength); + break; + case NetMessageType.ConnectResponse: + HandleConnectResponse(now, tp, ptr, payloadLength); + break; + + case NetMessageType.ConnectionEstablished: + switch (m_status) + { + case NetConnectionStatus.Connected: + // ok... + break; + case NetConnectionStatus.Disconnected: + case NetConnectionStatus.Disconnecting: + case NetConnectionStatus.None: + // too bad, almost made it + break; + case NetConnectionStatus.ReceivedInitiation: + // uh, a little premature... ignore + break; + case NetConnectionStatus.InitiatedConnect: + // weird, should have been RespondedConnect... + break; + case NetConnectionStatus.RespondedConnect: + // awesome + + NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + InitializeRemoteTimeOffset(msg.ReadSingle()); + + m_peer.AcceptConnection(this); + InitializePing(); + SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier)); + return; + } + break; + + case NetMessageType.Disconnect: + // ouch + string reason = "Ouch"; + try + { + NetIncomingMessage inc = m_peer.SetupReadHelperMessage(ptr, payloadLength); + reason = inc.ReadString(); + } + catch + { + } + ExecuteDisconnect(reason, false); + break; + + case NetMessageType.Discovery: + m_peer.HandleIncomingDiscoveryRequest(now, m_remoteEndPoint, ptr, payloadLength); + return; + + case NetMessageType.DiscoveryResponse: + m_peer.HandleIncomingDiscoveryResponse(now, m_remoteEndPoint, ptr, payloadLength); + return; + + case NetMessageType.Ping: + // silently ignore + return; + + default: + m_peer.LogDebug("Unhandled type during handshake: " + tp + " length: " + payloadLength); + break; + } + } + + private void HandleConnectResponse(double now, NetMessageType tp, int ptr, int payloadLength) + { + byte[] hail; + switch (m_status) + { + case NetConnectionStatus.InitiatedConnect: + // awesome + bool ok = ValidateHandshakeData(ptr, payloadLength, out hail); + if (ok) + { + if (hail != null) + { + m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail); + m_remoteHailMessage.LengthBits = (hail.Length * 8); + } + else + { + m_remoteHailMessage = null; + } + + m_peer.AcceptConnection(this); + SendConnectionEstablished(); + return; + } + break; + case NetConnectionStatus.RespondedConnect: + // hello, wtf? + break; + case NetConnectionStatus.Disconnecting: + case NetConnectionStatus.Disconnected: + case NetConnectionStatus.ReceivedInitiation: + case NetConnectionStatus.None: + // wtf? anyway, bye! + break; + case NetConnectionStatus.Connected: + // my ConnectionEstablished must have been lost, send another one + SendConnectionEstablished(); + return; + } + } + + private bool ValidateHandshakeData(int ptr, int payloadLength, out byte[] hail) + { + hail = null; + + // create temporary incoming message + NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + try + { + string remoteAppIdentifier = msg.ReadString(); + long remoteUniqueIdentifier = msg.ReadInt64(); + InitializeRemoteTimeOffset(msg.ReadSingle()); + + int remainingBytes = payloadLength - (msg.PositionInBytes - ptr); + if (remainingBytes > 0) + hail = msg.ReadBytes(remainingBytes); + + if (remoteAppIdentifier != m_peer.m_configuration.AppIdentifier) + { + ExecuteDisconnect("Wrong application identifier!", true); + return false; + } + + m_remoteUniqueIdentifier = remoteUniqueIdentifier; + } + catch(Exception ex) + { + // whatever; we failed + ExecuteDisconnect("Handshake data validation failed", true); + m_peer.LogWarning("ReadRemoteHandshakeData failed: " + ex.Message); + return false; + } + return true; + } + + /// + /// Disconnect from the remote peer + /// + /// the message to send with the disconnect message + public void Disconnect(string byeMessage) + { + // user or library thread + if (m_status == NetConnectionStatus.None || m_status == NetConnectionStatus.Disconnected) + return; + + m_peer.LogVerbose("Disconnect requested for " + this); + m_disconnectMessage = byeMessage; + + if (m_status != NetConnectionStatus.Disconnected && m_status != NetConnectionStatus.None) + SetStatus(NetConnectionStatus.Disconnecting, byeMessage); + + m_handshakeAttempts = 0; + m_disconnectRequested = true; + m_disconnectReqSendBye = true; + } + } +} diff --git a/Lidgren.Network/NetConnection.Latency.cs b/Lidgren.Network/NetConnection.Latency.cs new file mode 100644 index 0000000..832261b --- /dev/null +++ b/Lidgren.Network/NetConnection.Latency.cs @@ -0,0 +1,149 @@ +using System; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + private float m_sentPingTime; + private int m_sentPingNumber; + private float m_averageRoundtripTime; + private float m_timeoutDeadline = float.MaxValue; + + // local time value + m_remoteTimeOffset = remote time value + internal double m_remoteTimeOffset; + + /// + /// Gets the current average roundtrip time in seconds + /// + public float AverageRoundtripTime { get { return m_averageRoundtripTime; } } + + /// + /// Time offset between this peer and the remote peer + /// + public float RemoteTimeOffset { get { return (float)m_remoteTimeOffset; } } + + // this might happen more than once + internal void InitializeRemoteTimeOffset(float remoteSendTime) + { + m_remoteTimeOffset = (remoteSendTime + (m_averageRoundtripTime / 2.0)) - NetTime.Now; + } + + /// + /// Gets local time value comparable to NetTime.Now from a remote value + /// + public double GetLocalTime(double remoteTimestamp) + { + return remoteTimestamp - m_remoteTimeOffset; + } + + /// + /// Gets the remote time value for a local time value produced by NetTime.Now + /// + public double GetRemoteTime(double localTimestamp) + { + return localTimestamp + m_remoteTimeOffset; + } + + internal void InitializePing() + { + float now = (float)NetTime.Now; + + // randomize ping sent time (0.25 - 1.0 x ping interval) + m_sentPingTime = now; + m_sentPingTime -= (m_peerConfiguration.PingInterval * 0.25f); // delay ping for a little while + m_sentPingTime -= (MWCRandom.Instance.NextSingle() * (m_peerConfiguration.PingInterval * 0.75f)); + m_timeoutDeadline = now + (m_peerConfiguration.m_connectionTimeout * 2.0f); // initially allow a little more time + + // make it better, quick :-) + SendPing(); + } + + internal void SendPing() + { + m_peer.VerifyNetworkThread(); + + m_sentPingNumber++; + + m_sentPingTime = (float)NetTime.Now; + NetOutgoingMessage om = m_peer.CreateMessage(1); + om.Write((byte)m_sentPingNumber); // truncating to 0-255 + om.m_messageType = NetMessageType.Ping; + + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + bool connectionReset; + m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset); + + m_statistics.PacketSent(len, 1); + m_peer.Recycle(om); + } + + internal void SendPong(int pingNumber) + { + m_peer.VerifyNetworkThread(); + + NetOutgoingMessage om = m_peer.CreateMessage(5); + om.Write((byte)pingNumber); + om.Write((float)NetTime.Now); // we should update this value to reflect the exact point in time the packet is SENT + om.m_messageType = NetMessageType.Pong; + + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + bool connectionReset; + + m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset); + + m_statistics.PacketSent(len, 1); + m_peer.Recycle(om); + } + + internal void ReceivedPong(float now, int pongNumber, float remoteSendTime) + { + if ((byte)pongNumber != (byte)m_sentPingNumber) + { + m_peer.LogVerbose("Ping/Pong mismatch; dropped message?"); + return; + } + + m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout; + + float rtt = now - m_sentPingTime; + NetException.Assert(rtt >= 0); + + double diff = (remoteSendTime + (rtt / 2.0)) - now; + + if (m_averageRoundtripTime < 0) + { + m_remoteTimeOffset = diff; + m_averageRoundtripTime = rtt; + m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + " Remote time is: " + (now + diff)); + } + else + { + m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f); + + m_remoteTimeOffset = ((m_remoteTimeOffset * (double)(m_sentPingNumber - 1)) + diff) / (double)m_sentPingNumber; + m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + ", remote time to " + (now + m_remoteTimeOffset) + " (ie. diff " + m_remoteTimeOffset + ")"); + } + + // update resend delay for all channels + float resendDelay = GetResendDelay(); + foreach (var chan in m_sendChannels) + { + var rchan = chan as NetReliableSenderChannel; + if (rchan != null) + rchan.m_resendDelay = resendDelay; + } + + // m_peer.LogVerbose("Timeout deadline pushed to " + m_timeoutDeadline); + + // notify the application that average rtt changed + if (m_peer.m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionLatencyUpdated)) + { + NetIncomingMessage update = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionLatencyUpdated, 4); + update.m_senderConnection = this; + update.m_senderEndPoint = this.m_remoteEndPoint; + update.Write(rtt); + m_peer.ReleaseMessage(update); + } + } + } +} diff --git a/Lidgren.Network/NetConnection.MTU.cs b/Lidgren.Network/NetConnection.MTU.cs new file mode 100644 index 0000000..d5f64da --- /dev/null +++ b/Lidgren.Network/NetConnection.MTU.cs @@ -0,0 +1,182 @@ +using System; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + private enum ExpandMTUStatus + { + None, + InProgress, + Finished + } + + private const int c_protocolMaxMTU = (int)((((float)ushort.MaxValue / 8.0f) - 1.0f)); + + private ExpandMTUStatus m_expandMTUStatus; + + private int m_largestSuccessfulMTU; + private int m_smallestFailedMTU; + + private int m_lastSentMTUAttemptSize; + private double m_lastSentMTUAttemptTime; + private int m_mtuAttemptFails; + + internal int m_currentMTU; + + /// + /// Gets the current MTU in bytes. If PeerConfiguration.AutoExpandMTU is false, this will be PeerConfiguration.MaximumTransmissionUnit. + /// + public int CurrentMTU { get { return m_currentMTU; } } + + internal void InitExpandMTU(double now) + { + m_lastSentMTUAttemptTime = now + m_peerConfiguration.m_expandMTUFrequency + 1.5f + m_averageRoundtripTime; // wait a tiny bit before starting to expand mtu + m_largestSuccessfulMTU = 512; + m_smallestFailedMTU = -1; + m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit; + } + + private void MTUExpansionHeartbeat(double now) + { + if (m_expandMTUStatus == ExpandMTUStatus.Finished) + return; + + if (m_expandMTUStatus == ExpandMTUStatus.None) + { + if (m_peerConfiguration.m_autoExpandMTU == false) + { + FinalizeMTU(m_currentMTU); + return; + } + + // begin expansion + ExpandMTU(now); + return; + } + + if (now > m_lastSentMTUAttemptTime + m_peerConfiguration.ExpandMTUFrequency) + { + m_mtuAttemptFails++; + if (m_mtuAttemptFails == 3) + { + FinalizeMTU(m_currentMTU); + return; + } + + // timed out; ie. failed + m_smallestFailedMTU = m_lastSentMTUAttemptSize; + ExpandMTU(now); + } + } + + private void ExpandMTU(double now) + { + int tryMTU; + + // we've nevered encountered failure + if (m_smallestFailedMTU == -1) + { + // we've never encountered failure; expand by 25% each time + tryMTU = (int)((float)m_currentMTU * 1.25f); + //m_peer.LogDebug("Trying MTU " + tryMTU); + } + else + { + // we HAVE encountered failure; so try in between + tryMTU = (int)(((float)m_smallestFailedMTU + (float)m_largestSuccessfulMTU) / 2.0f); + //m_peer.LogDebug("Trying MTU " + m_smallestFailedMTU + " <-> " + m_largestSuccessfulMTU + " = " + tryMTU); + } + + if (tryMTU > c_protocolMaxMTU) + tryMTU = c_protocolMaxMTU; + + if (tryMTU == m_largestSuccessfulMTU) + { + //m_peer.LogDebug("Found optimal MTU - exiting"); + FinalizeMTU(m_largestSuccessfulMTU); + return; + } + + SendExpandMTU(now, tryMTU); + } + + private void SendExpandMTU(double now, int size) + { + NetOutgoingMessage om = m_peer.CreateMessage(size); + byte[] tmp = new byte[size]; + om.Write(tmp); + om.m_messageType = NetMessageType.ExpandMTURequest; + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + + bool ok = m_peer.SendMTUPacket(len, m_remoteEndPoint); + if (ok == false) + { + //m_peer.LogDebug("Send MTU failed for size " + size); + + // failure + if (m_smallestFailedMTU == -1 || size < m_smallestFailedMTU) + { + m_smallestFailedMTU = size; + m_mtuAttemptFails++; + if (m_mtuAttemptFails >= m_peerConfiguration.ExpandMTUFailAttempts) + { + FinalizeMTU(m_largestSuccessfulMTU); + return; + } + } + ExpandMTU(now); + return; + } + + m_lastSentMTUAttemptSize = size; + m_lastSentMTUAttemptTime = now; + + m_statistics.PacketSent(len, 1); + m_peer.Recycle(om); + } + + private void FinalizeMTU(int size) + { + if (m_expandMTUStatus == ExpandMTUStatus.Finished) + return; + m_expandMTUStatus = ExpandMTUStatus.Finished; + m_currentMTU = size; + if (m_currentMTU != m_peerConfiguration.m_maximumTransmissionUnit) + m_peer.LogDebug("Expanded Maximum Transmission Unit to: " + m_currentMTU + " bytes"); + return; + } + + private void SendMTUSuccess(int size) + { + NetOutgoingMessage om = m_peer.CreateMessage(4); + om.Write(size); + om.m_messageType = NetMessageType.ExpandMTUSuccess; + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + bool connectionReset; + m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset); + m_peer.Recycle(om); + + //m_peer.LogDebug("Received MTU expand request for " + size + " bytes"); + + m_statistics.PacketSent(len, 1); + } + + private void HandleExpandMTUSuccess(double now, int size) + { + if (size > m_largestSuccessfulMTU) + m_largestSuccessfulMTU = size; + + if (size < m_currentMTU) + { + //m_peer.LogDebug("Received low MTU expand success (size " + size + "); current mtu is " + m_currentMTU); + return; + } + + //m_peer.LogDebug("Expanding MTU to " + size); + m_currentMTU = size; + + ExpandMTU(now); + } + } +} diff --git a/Lidgren.Network/NetConnection.cs b/Lidgren.Network/NetConnection.cs new file mode 100644 index 0000000..ee056b6 --- /dev/null +++ b/Lidgren.Network/NetConnection.cs @@ -0,0 +1,563 @@ +using System; +using System.Net; +using System.Threading; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Represents a connection to a remote peer + /// + [DebuggerDisplay("RemoteUniqueIdentifier={RemoteUniqueIdentifier} RemoteEndPoint={remoteEndPoint}")] + public partial class NetConnection + { + private const int m_infrequentEventsSkipFrames = 8; // number of heartbeats to skip checking for infrequent events (ping, timeout etc) + private const int m_messageCoalesceFrames = 3; // number of heartbeats to wait for more incoming messages before sending packet + + internal NetPeer m_peer; + internal NetPeerConfiguration m_peerConfiguration; + internal NetConnectionStatus m_status; + internal NetConnectionStatus m_visibleStatus; + internal IPEndPoint m_remoteEndPoint; + internal NetSenderChannelBase[] m_sendChannels; + internal NetReceiverChannelBase[] m_receiveChannels; + internal NetOutgoingMessage m_localHailMessage; + internal long m_remoteUniqueIdentifier; + internal NetQueue> m_queuedOutgoingAcks; + internal NetQueue> m_queuedIncomingAcks; + private int m_sendBufferWritePtr; + private int m_sendBufferNumMessages; + private object m_tag; + internal NetConnectionStatistics m_statistics; + + /// + /// Gets or sets the application defined object containing data about the connection + /// + public object Tag + { + get { return m_tag; } + set { m_tag = value; } + } + + /// + /// Gets the peer which holds this connection + /// + public NetPeer Peer { get { return m_peer; } } + + /// + /// Gets the current status of the connection (synced to the last status message read) + /// + public NetConnectionStatus Status { get { return m_visibleStatus; } } + + /// + /// Gets various statistics for this connection + /// + public NetConnectionStatistics Statistics { get { return m_statistics; } } + + /// + /// Gets the remote endpoint for the connection + /// + public IPEndPoint RemoteEndPoint { get { return m_remoteEndPoint; } } + + /// + /// Gets the unique identifier of the remote NetPeer for this connection + /// + public long RemoteUniqueIdentifier { get { return m_remoteUniqueIdentifier; } } + + /// + /// Gets the local hail message that was sent as part of the handshake + /// + public NetOutgoingMessage LocalHailMessage { get { return m_localHailMessage; } } + + // gets the time before automatically resending an unacked message + internal float GetResendDelay() + { + float avgRtt = m_averageRoundtripTime; + if (avgRtt <= 0) + avgRtt = 0.1f; // "default" resend is based on 100 ms roundtrip time + return 0.025f + (avgRtt * 2.1f); // 25 ms + double rtt + } + + internal NetConnection(NetPeer peer, IPEndPoint remoteEndPoint) + { + m_peer = peer; + m_peerConfiguration = m_peer.Configuration; + m_status = NetConnectionStatus.None; + m_visibleStatus = NetConnectionStatus.None; + m_remoteEndPoint = remoteEndPoint; + m_sendChannels = new NetSenderChannelBase[NetConstants.NumTotalChannels]; + m_receiveChannels = new NetReceiverChannelBase[NetConstants.NumTotalChannels]; + m_queuedOutgoingAcks = new NetQueue>(4); + m_queuedIncomingAcks = new NetQueue>(4); + m_statistics = new NetConnectionStatistics(this); + m_averageRoundtripTime = -1.0f; + m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit; + } + + /// + /// Change the internal endpoint to this new one. Used when, during handshake, a switch in port is detected (due to NAT) + /// + internal void MutateEndPoint(IPEndPoint endPoint) + { + m_remoteEndPoint = endPoint; + } + + internal void SetStatus(NetConnectionStatus status, string reason) + { + // user or library thread + + if (status == m_status) + return; + + m_status = status; + if (reason == null) + reason = string.Empty; + + if (m_status == NetConnectionStatus.Connected) + { + m_timeoutDeadline = (float)NetTime.Now + m_peerConfiguration.m_connectionTimeout; + m_peer.LogVerbose("Timeout deadline initialized to " + m_timeoutDeadline); + } + + if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged)) + { + NetIncomingMessage info = m_peer.CreateIncomingMessage(NetIncomingMessageType.StatusChanged, 4 + reason.Length + (reason.Length > 126 ? 2 : 1)); + info.m_senderConnection = this; + info.m_senderEndPoint = m_remoteEndPoint; + info.Write((byte)m_status); + info.Write(reason); + m_peer.ReleaseMessage(info); + } + else + { + // app dont want those messages, update visible status immediately + m_visibleStatus = m_status; + } + } + + internal void Heartbeat(float now, uint frameCounter) + { + m_peer.VerifyNetworkThread(); + + NetException.Assert(m_status != NetConnectionStatus.InitiatedConnect && m_status != NetConnectionStatus.RespondedConnect); + + if ((frameCounter % m_infrequentEventsSkipFrames) == 0) + { + if (now > m_timeoutDeadline) + { + // + // connection timed out + // + m_peer.LogVerbose("Connection timed out at " + now + " deadline was " + m_timeoutDeadline); + ExecuteDisconnect("Connection timed out", true); + return; + } + + // send ping? + if (m_status == NetConnectionStatus.Connected) + { + if (now > m_sentPingTime + m_peer.m_configuration.m_pingInterval) + SendPing(); + + // handle expand mtu + MTUExpansionHeartbeat(now); + } + + if (m_disconnectRequested) + { + ExecuteDisconnect(m_disconnectMessage, m_disconnectReqSendBye); + return; + } + } + + bool connectionReset; // TODO: handle connection reset + + // + // Note: at this point m_sendBufferWritePtr and m_sendBufferNumMessages may be non-null; resends may already be queued up + // + + byte[] sendBuffer = m_peer.m_sendBuffer; + int mtu = m_currentMTU; + + if ((frameCounter % m_messageCoalesceFrames) == 0) // coalesce a few frames + { + // + // send ack messages + // + while (m_queuedOutgoingAcks.Count > 0) + { + int acks = (mtu - (m_sendBufferWritePtr + 5)) / 3; // 3 bytes per actual ack + if (acks > m_queuedOutgoingAcks.Count) + acks = m_queuedOutgoingAcks.Count; + + NetException.Assert(acks > 0); + + m_sendBufferNumMessages++; + + // write acks header + sendBuffer[m_sendBufferWritePtr++] = (byte)NetMessageType.Acknowledge; + sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number + sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number + int len = (acks * 3) * 8; // bits + sendBuffer[m_sendBufferWritePtr++] = (byte)len; + sendBuffer[m_sendBufferWritePtr++] = (byte)(len >> 8); + + // write acks + for (int i = 0; i < acks; i++) + { + NetTuple tuple; + m_queuedOutgoingAcks.TryDequeue(out tuple); + + //m_peer.LogVerbose("Sending ack for " + tuple.Item1 + "#" + tuple.Item2); + + sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item1; + sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item2; + sendBuffer[m_sendBufferWritePtr++] = (byte)(tuple.Item2 >> 8); + } + + if (m_queuedOutgoingAcks.Count > 0) + { + // send packet and go for another round of acks + NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0); + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset); + m_statistics.PacketSent(m_sendBufferWritePtr, 1); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + } + + // + // Parse incoming acks (may trigger resends) + // + NetTuple incAck; + while (m_queuedIncomingAcks.TryDequeue(out incAck)) + { + //m_peer.LogVerbose("Received ack for " + acktp + "#" + seqNr); + NetSenderChannelBase chan = m_sendChannels[(int)incAck.Item1 - 1]; + + // If we haven't sent a message on this channel there is no reason to ack it + if (chan == null) + continue; + + chan.ReceiveAcknowledge(now, incAck.Item2); + } + } + + // + // send queued messages + // + if (m_peer.m_executeFlushSendQueue) + { + for (int i = m_sendChannels.Length - 1; i >= 0; i--) // Reverse order so reliable messages are sent first + { + var channel = m_sendChannels[i]; + NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0); + if (channel != null) + channel.SendQueuedMessages(now); + NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0); + } + } + + // + // Put on wire data has been written to send buffer but not yet sent + // + if (m_sendBufferWritePtr > 0) + { + m_peer.VerifyNetworkThread(); + NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0); + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset); + m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + } + + // Queue an item for immediate sending on the wire + // This method is called from the ISenderChannels + internal void QueueSendMessage(NetOutgoingMessage om, int seqNr) + { + m_peer.VerifyNetworkThread(); + + int sz = om.GetEncodedSize(); + //if (sz > m_currentMTU) + // m_peer.LogWarning("Message larger than MTU! Fragmentation must have failed!"); + + bool connReset; // TODO: handle connection reset + + // can fit this message together with previously written to buffer? + if (m_sendBufferWritePtr + sz > m_currentMTU) + { + if (m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0) + { + // previous message in buffer; send these first + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset); + m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + } + + // encode it into buffer regardless if it (now) fits within MTU or not + m_sendBufferWritePtr = om.Encode(m_peer.m_sendBuffer, m_sendBufferWritePtr, seqNr); + m_sendBufferNumMessages++; + + if (m_sendBufferWritePtr > m_currentMTU) + { + // send immediately; we're already over MTU + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset); + m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + } + + /// + /// Send a message to this remote connection + /// + /// The message to send + /// How to deliver the message + /// Sequence channel within the delivery method + public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + return m_peer.SendMessage(msg, this, method, sequenceChannel); + } + + // called by SendMessage() and NetPeer.SendMessage; ie. may be user thread + internal NetSendResult EnqueueMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + if (m_status != NetConnectionStatus.Connected) + return NetSendResult.FailedNotConnected; + + NetMessageType tp = (NetMessageType)((int)method + sequenceChannel); + msg.m_messageType = tp; + + // TODO: do we need to make this more thread safe? + int channelSlot = (int)method - 1 + sequenceChannel; + NetSenderChannelBase chan = m_sendChannels[channelSlot]; + if (chan == null) + chan = CreateSenderChannel(tp); + + if ((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.UnreliableSequenced) && msg.GetEncodedSize() > m_currentMTU) + m_peer.ThrowOrLog("Reliable message too large! Fragmentation failure?"); + + var retval = chan.Enqueue(msg); + //if (retval == NetSendResult.Sent && m_peerConfiguration.m_autoFlushSendQueue == false) + // retval = NetSendResult.Queued; // queued since we're not autoflushing + return retval; + } + + // may be on user thread + private NetSenderChannelBase CreateSenderChannel(NetMessageType tp) + { + NetSenderChannelBase chan; + lock (m_sendChannels) + { + NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp); + int sequenceChannel = (int)tp - (int)method; + + int channelSlot = (int)method - 1 + sequenceChannel; + if (m_sendChannels[channelSlot] != null) + { + // we were pre-empted by another call to this method + chan = m_sendChannels[channelSlot]; + } + else + { + + switch (method) + { + case NetDeliveryMethod.Unreliable: + case NetDeliveryMethod.UnreliableSequenced: + chan = new NetUnreliableSenderChannel(this, NetUtility.GetWindowSize(method)); + break; + case NetDeliveryMethod.ReliableOrdered: + chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method)); + break; + case NetDeliveryMethod.ReliableSequenced: + case NetDeliveryMethod.ReliableUnordered: + default: + chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method)); + break; + } + m_sendChannels[channelSlot] = chan; + } + } + + return chan; + } + + // received a library message while Connected + internal void ReceivedLibraryMessage(NetMessageType tp, int ptr, int payloadLength) + { + m_peer.VerifyNetworkThread(); + + float now = (float)NetTime.Now; + + switch (tp) + { + case NetMessageType.Connect: + m_peer.LogDebug("Received handshake message (" + tp + ") despite connection being in place"); + break; + + case NetMessageType.ConnectResponse: + // handshake message must have been lost + HandleConnectResponse(now, tp, ptr, payloadLength); + break; + + case NetMessageType.ConnectionEstablished: + // do nothing, all's well + break; + + case NetMessageType.LibraryError: + m_peer.ThrowOrLog("LibraryError received by ReceivedLibraryMessage; this usually indicates a malformed message"); + break; + + case NetMessageType.Disconnect: + NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + + m_disconnectRequested = true; + m_disconnectMessage = msg.ReadString(); + m_disconnectReqSendBye = false; + //ExecuteDisconnect(msg.ReadString(), false); + break; + case NetMessageType.Acknowledge: + for (int i = 0; i < payloadLength; i+=3) + { + NetMessageType acktp = (NetMessageType)m_peer.m_receiveBuffer[ptr++]; // netmessagetype + int seqNr = m_peer.m_receiveBuffer[ptr++]; + seqNr |= (m_peer.m_receiveBuffer[ptr++] << 8); + + // need to enqueue this and handle it in the netconnection heartbeat; so be able to send resends together with normal sends + m_queuedIncomingAcks.Enqueue(new NetTuple(acktp, seqNr)); + } + break; + case NetMessageType.Ping: + int pingNr = m_peer.m_receiveBuffer[ptr++]; + SendPong(pingNr); + break; + case NetMessageType.Pong: + NetIncomingMessage pmsg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + int pongNr = pmsg.ReadByte(); + float remoteSendTime = pmsg.ReadSingle(); + ReceivedPong(now, pongNr, remoteSendTime); + break; + case NetMessageType.ExpandMTURequest: + SendMTUSuccess(payloadLength); + break; + case NetMessageType.ExpandMTUSuccess: + if (m_peer.Configuration.AutoExpandMTU == false) + { + m_peer.LogDebug("Received ExpandMTURequest altho AutoExpandMTU is turned off!"); + break; + } + NetIncomingMessage emsg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + int size = emsg.ReadInt32(); + HandleExpandMTUSuccess(now, size); + break; + case NetMessageType.NatIntroduction: + // Unusual situation where server is actually already known, but got a nat introduction - oh well, lets handle it as usual + m_peer.HandleNatIntroduction(ptr); + break; + default: + m_peer.LogWarning("Connection received unhandled library message: " + tp); + break; + } + } + + internal void ReceivedMessage(NetIncomingMessage msg) + { + m_peer.VerifyNetworkThread(); + + NetMessageType tp = msg.m_receivedMessageType; + + int channelSlot = (int)tp - 1; + NetReceiverChannelBase chan = m_receiveChannels[channelSlot]; + if (chan == null) + chan = CreateReceiverChannel(tp); + + chan.ReceiveMessage(msg); + } + + private NetReceiverChannelBase CreateReceiverChannel(NetMessageType tp) + { + m_peer.VerifyNetworkThread(); + + // create receiver channel + NetReceiverChannelBase chan; + NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp); + switch (method) + { + case NetDeliveryMethod.Unreliable: + chan = new NetUnreliableUnorderedReceiver(this); + break; + case NetDeliveryMethod.ReliableOrdered: + chan = new NetReliableOrderedReceiver(this, NetConstants.ReliableOrderedWindowSize); + break; + case NetDeliveryMethod.UnreliableSequenced: + chan = new NetUnreliableSequencedReceiver(this); + break; + case NetDeliveryMethod.ReliableUnordered: + chan = new NetReliableUnorderedReceiver(this, NetConstants.ReliableOrderedWindowSize); + break; + case NetDeliveryMethod.ReliableSequenced: + chan = new NetReliableSequencedReceiver(this, NetConstants.ReliableSequencedWindowSize); + break; + default: + throw new NetException("Unhandled NetDeliveryMethod!"); + } + + int channelSlot = (int)tp - 1; + NetException.Assert(m_receiveChannels[channelSlot] == null); + m_receiveChannels[channelSlot] = chan; + + return chan; + } + + internal void QueueAck(NetMessageType tp, int sequenceNumber) + { + m_queuedOutgoingAcks.Enqueue(new NetTuple(tp, sequenceNumber)); + } + + /// + /// Zero windowSize indicates that the channel is not yet instantiated (used) + /// Negative freeWindowSlots means this amount of messages are currently queued but delayed due to closed window + /// + public void GetSendQueueInfo(NetDeliveryMethod method, int sequenceChannel, out int windowSize, out int freeWindowSlots) + { + int channelSlot = (int)method - 1 + sequenceChannel; + var chan = m_sendChannels[channelSlot]; + if (chan == null) + { + windowSize = NetUtility.GetWindowSize(method); + freeWindowSlots = windowSize; + return; + } + + windowSize = chan.WindowSize; + freeWindowSlots = chan.GetAllowedSends() - chan.m_queuedSends.Count; + return; + } + + public bool CanSendImmediately(NetDeliveryMethod method, int sequenceChannel) + { + int channelSlot = (int)method - 1 + sequenceChannel; + var chan = m_sendChannels[channelSlot]; + if (chan == null) + return true; + return (chan.GetAllowedSends() - chan.m_queuedSends.Count) > 0; + } + + internal void Shutdown(string reason) + { + ExecuteDisconnect(reason, true); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetConnection to " + m_remoteEndPoint + "]"; + } + } +} diff --git a/Lidgren.Network/NetConnectionStatistics.cs b/Lidgren.Network/NetConnectionStatistics.cs new file mode 100644 index 0000000..7d28c1d --- /dev/null +++ b/Lidgren.Network/NetConnectionStatistics.cs @@ -0,0 +1,212 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Uncomment the line below to get statistics in RELEASE builds +//#define USE_RELEASE_STATISTICS + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + internal enum MessageResendReason + { + Delay, + HoleInSequence + } + + /// + /// Statistics for a NetConnection instance + /// + public sealed class NetConnectionStatistics + { + private readonly NetConnection m_connection; + + internal int m_sentPackets; + internal int m_receivedPackets; + + internal int m_sentMessages; + internal int m_receivedMessages; + internal int m_receivedFragments; + + internal int m_sentBytes; + internal int m_receivedBytes; + + internal int m_resentMessagesDueToDelay; + internal int m_resentMessagesDueToHole; + + internal NetConnectionStatistics(NetConnection conn) + { + m_connection = conn; + Reset(); + } + + internal void Reset() + { + m_sentPackets = 0; + m_receivedPackets = 0; + m_sentMessages = 0; + m_receivedMessages = 0; + m_receivedFragments = 0; + m_sentBytes = 0; + m_receivedBytes = 0; + m_resentMessagesDueToDelay = 0; + m_resentMessagesDueToHole = 0; + } + + /// + /// Gets the number of sent packets for this connection + /// + public int SentPackets { get { return m_sentPackets; } } + + /// + /// Gets the number of received packets for this connection + /// + public int ReceivedPackets { get { return m_receivedPackets; } } + + /// + /// Gets the number of sent bytes for this connection + /// + public int SentBytes { get { return m_sentBytes; } } + + /// + /// Gets the number of received bytes for this connection + /// + public int ReceivedBytes { get { return m_receivedBytes; } } + + /// + /// Gets the number of resent reliable messages for this connection + /// + public int ResentMessages { get { return m_resentMessagesDueToHole + m_resentMessagesDueToDelay; } } + + // public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } } + +#if USE_RELEASE_STATISTICS + internal void PacketSent(int numBytes, int numMessages) + { + NetException.Assert(numBytes > 0 && numMessages > 0); + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } +#else + [Conditional("DEBUG")] + internal void PacketSent(int numBytes, int numMessages) + { + NetException.Assert(numBytes > 0 && numMessages > 0); + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } +#endif + +#if USE_RELEASE_STATISTICS + internal void PacketReceived(int numBytes, int numMessages) + { + NetException.Assert(numBytes > 0 && numMessages > 0); + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + } +#else + [Conditional("DEBUG")] + internal void PacketReceived(int numBytes, int numMessages, int numFragments) + { + NetException.Assert(numBytes > 0 && numMessages > 0); + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + m_receivedFragments += numFragments; + } +#endif + +#if USE_RELEASE_STATISTICS + internal void MessageResent(MessageResendReason reason) + { + if (reason == MessageResendReason.Delay) + m_resentMessagesDueToDelay++; + else + m_resentMessagesDueToHole++; + } +#else + [Conditional("DEBUG")] + internal void MessageResent(MessageResendReason reason) + { + if (reason == MessageResendReason.Delay) + m_resentMessagesDueToDelay++; + else + m_resentMessagesDueToHole++; + } +#endif + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + StringBuilder bdr = new StringBuilder(); + //bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime)); + bdr.AppendLine("Current MTU: " + m_connection.m_currentMTU); + bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); + bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets"); + + if (m_resentMessagesDueToDelay > 0) + bdr.AppendLine("Resent messages (delay): " + m_resentMessagesDueToDelay); + if (m_resentMessagesDueToDelay > 0) + bdr.AppendLine("Resent messages (holes): " + m_resentMessagesDueToHole); + + int numUnsent = 0; + int numStored = 0; + foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels) + { + if (sendChan == null) + continue; + numUnsent += sendChan.m_queuedSends.Count; + + var relSendChan = sendChan as NetReliableSenderChannel; + if (relSendChan != null) + { + for (int i = 0; i < relSendChan.m_storedMessages.Length; i++) + if (relSendChan.m_storedMessages[i].Message != null) + numStored++; + } + } + + int numWithheld = 0; + foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels) + { + var relRecChan = recChan as NetReliableOrderedReceiver; + if (relRecChan != null) + { + for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++) + if (relRecChan.m_withheldMessages[i] != null) + numWithheld++; + } + } + + bdr.AppendLine("Unsent messages: " + numUnsent); + bdr.AppendLine("Stored messages: " + numStored); + bdr.AppendLine("Withheld messages: " + numWithheld); + + return bdr.ToString(); + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetConnectionStatus.cs b/Lidgren.Network/NetConnectionStatus.cs new file mode 100644 index 0000000..15c1937 --- /dev/null +++ b/Lidgren.Network/NetConnectionStatus.cs @@ -0,0 +1,68 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + /// + /// Status for a NetConnection instance + /// + public enum NetConnectionStatus + { + /// + /// No connection, or attempt, in place + /// + None, + + /// + /// Connect has been sent; waiting for ConnectResponse + /// + InitiatedConnect, + + /// + /// Connect was received, but ConnectResponse hasn't been sent yet + /// + ReceivedInitiation, + + /// + /// Connect was received and ApprovalMessage released to the application; awaiting Approve() or Deny() + /// + RespondedAwaitingApproval, // We got Connect, released ApprovalMessage + + /// + /// Connect was received and ConnectResponse has been sent; waiting for ConnectionEstablished + /// + RespondedConnect, // we got Connect, sent ConnectResponse + + /// + /// Connected + /// + Connected, // we received ConnectResponse (if initiator) or ConnectionEstablished (if passive) + + /// + /// In the process of disconnecting + /// + Disconnecting, + + /// + /// Disconnected + /// + Disconnected + } +} diff --git a/Lidgren.Network/NetConstants.cs b/Lidgren.Network/NetConstants.cs new file mode 100644 index 0000000..a5a0c37 --- /dev/null +++ b/Lidgren.Network/NetConstants.cs @@ -0,0 +1,57 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + /// + /// All the constants used when compiling the library + /// + internal static class NetConstants + { + internal const int NumTotalChannels = 99; + + internal const int NetChannelsPerDeliveryMethod = 32; + + internal const int NumSequenceNumbers = 1024; + + internal const int HeaderByteSize = 5; + + internal const int UnreliableWindowSize = 128; + internal const int ReliableOrderedWindowSize = 64; + internal const int ReliableSequencedWindowSize = 64; + internal const int DefaultWindowSize = 64; + + internal const int MaxFragmentationGroups = ushort.MaxValue - 1; + + internal const int UnfragmentedMessageHeaderSize = 5; + + /// + /// Number of channels which needs a sequence number to work + /// + internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced1; + + /// + /// Number of reliable channels + /// + internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered; + + internal const string ConnResetMessage = "Connection was reset by remote host"; + } +} diff --git a/Lidgren.Network/NetDeliveryMethod.cs b/Lidgren.Network/NetDeliveryMethod.cs new file mode 100644 index 0000000..d04266d --- /dev/null +++ b/Lidgren.Network/NetDeliveryMethod.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// How the library deals with resends and handling of late messages + /// + public enum NetDeliveryMethod : byte + { + // + // Actually a publicly visible subset of NetMessageType + // + + /// + /// Indicates an error + /// + Unknown = 0, + + /// + /// Unreliable, unordered delivery + /// + Unreliable = 1, + + /// + /// Unreliable delivery, but automatically dropping late messages + /// + UnreliableSequenced = 2, + + /// + /// Reliable delivery, but unordered + /// + ReliableUnordered = 34, + + /// + /// Reliable delivery, except for late messages which are dropped + /// + ReliableSequenced = 35, + + /// + /// Reliable, ordered delivery + /// + ReliableOrdered = 67, + } +} diff --git a/Lidgren.Network/NetException.cs b/Lidgren.Network/NetException.cs new file mode 100644 index 0000000..41a50c7 --- /dev/null +++ b/Lidgren.Network/NetException.cs @@ -0,0 +1,74 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +namespace Lidgren.Network +{ + /// + /// Exception thrown in the Lidgren Network Library + /// + public sealed class NetException : Exception + { + /// + /// NetException constructor + /// + public NetException() + : base() + { + } + + /// + /// NetException constructor + /// + public NetException(string message) + : base(message) + { + } + + /// + /// NetException constructor + /// + public NetException(string message, Exception inner) + : base(message, inner) + { + } + + /// + /// Throws an exception, in DEBUG only, if first parameter is false + /// + [Conditional("DEBUG")] + public static void Assert(bool isOk, string message) + { + if (!isOk) + throw new NetException(message); + } + + /// + /// Throws an exception, in DEBUG only, if first parameter is false + /// + [Conditional("DEBUG")] + public static void Assert(bool isOk) + { + if (!isOk) + throw new NetException(); + } + } +} diff --git a/Lidgren.Network/NetFragmentationHelper.cs b/Lidgren.Network/NetFragmentationHelper.cs new file mode 100644 index 0000000..85efe4d --- /dev/null +++ b/Lidgren.Network/NetFragmentationHelper.cs @@ -0,0 +1,175 @@ +using System; + +namespace Lidgren.Network +{ + internal static class NetFragmentationHelper + { + internal static int WriteHeader( + byte[] destination, + int ptr, + int group, + int totalBits, + int chunkByteSize, + int chunkNumber) + { + uint num1 = (uint)group; + while (num1 >= 0x80) + { + destination[ptr++] = (byte)(num1 | 0x80); + num1 = num1 >> 7; + } + destination[ptr++] = (byte)num1; + + // write variable length fragment total bits + uint num2 = (uint)totalBits; + while (num2 >= 0x80) + { + destination[ptr++] = (byte)(num2 | 0x80); + num2 = num2 >> 7; + } + destination[ptr++] = (byte)num2; + + // write variable length fragment chunk size + uint num3 = (uint)chunkByteSize; + while (num3 >= 0x80) + { + destination[ptr++] = (byte)(num3 | 0x80); + num3 = num3 >> 7; + } + destination[ptr++] = (byte)num3; + + // write variable length fragment chunk number + uint num4 = (uint)chunkNumber; + while (num4 >= 0x80) + { + destination[ptr++] = (byte)(num4 | 0x80); + num4 = num4 >> 7; + } + destination[ptr++] = (byte)num4; + + return ptr; + } + + internal static int ReadHeader(byte[] buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber) + { + int num1 = 0; + int num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + group = num1; + break; + } + } + + num1 = 0; + num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + totalBits = num1; + break; + } + } + + num1 = 0; + num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + chunkByteSize = num1; + break; + } + } + + num1 = 0; + num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + chunkNumber = num1; + break; + } + } + + return ptr; + } + + internal static int GetFragmentationHeaderSize(int groupId, int totalBytes, int chunkByteSize, int numChunks) + { + int len = 4; + + // write variable length fragment group id + uint num1 = (uint)groupId; + while (num1 >= 0x80) + { + len++; + num1 = num1 >> 7; + } + + // write variable length fragment total bits + uint num2 = (uint)(totalBytes * 8); + while (num2 >= 0x80) + { + len++; + num2 = num2 >> 7; + } + + // write variable length fragment chunk byte size + uint num3 = (uint)chunkByteSize; + while (num3 >= 0x80) + { + len++; + num3 = num3 >> 7; + } + + // write variable length fragment chunk number + uint num4 = (uint)numChunks; + while (num4 >= 0x80) + { + len++; + num4 = num4 >> 7; + } + + return len; + } + + internal static int GetBestChunkSize(int group, int totalBytes, int mtu) + { + int tryChunkSize = mtu - NetConstants.HeaderByteSize - 4; // naive approximation + int est = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, totalBytes / tryChunkSize); + tryChunkSize = mtu - NetConstants.HeaderByteSize - est; // slightly less naive approximation + + int headerSize = 0; + do + { + tryChunkSize--; // keep reducing chunk size until it fits within MTU including header + + int numChunks = totalBytes / tryChunkSize; + if (numChunks * tryChunkSize < totalBytes) + numChunks++; + + headerSize = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, numChunks); // 4+ bytes + + } while (tryChunkSize + headerSize + NetConstants.HeaderByteSize + 1 >= mtu); + + return tryChunkSize; + } + } +} diff --git a/Lidgren.Network/NetFragmentationInfo.cs b/Lidgren.Network/NetFragmentationInfo.cs new file mode 100644 index 0000000..2c8b70e --- /dev/null +++ b/Lidgren.Network/NetFragmentationInfo.cs @@ -0,0 +1,12 @@ +using System; + +namespace Lidgren.Network +{ + public sealed class NetFragmentationInfo + { + public int TotalFragmentCount; + public bool[] Received; + public int TotalReceived; + public int FragmentSize; + } +} diff --git a/Lidgren.Network/NetIncomingMessage.cs b/Lidgren.Network/NetIncomingMessage.cs new file mode 100644 index 0000000..3dadf77 --- /dev/null +++ b/Lidgren.Network/NetIncomingMessage.cs @@ -0,0 +1,115 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Net; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Incoming message either sent from a remote peer or generated within the library + /// + [DebuggerDisplay("Type={MessageType} LengthBits={LengthBits}")] + public sealed class NetIncomingMessage : NetBuffer + { + internal NetIncomingMessageType m_incomingMessageType; + internal IPEndPoint m_senderEndPoint; + internal NetConnection m_senderConnection; + internal int m_sequenceNumber; + internal NetMessageType m_receivedMessageType; + internal bool m_isFragment; + internal double m_receiveTime; + + /// + /// Gets the type of this incoming message + /// + public NetIncomingMessageType MessageType { get { return m_incomingMessageType; } } + + /// + /// Gets the delivery method this message was sent with (if user data) + /// + public NetDeliveryMethod DeliveryMethod { get { return NetUtility.GetDeliveryMethod(m_receivedMessageType); } } + + /// + /// Gets the sequence channel this message was sent with (if user data) + /// + public int SequenceChannel { get { return (int)m_receivedMessageType - (int)NetUtility.GetDeliveryMethod(m_receivedMessageType); } } + + /// + /// IPEndPoint of sender, if any + /// + public IPEndPoint SenderEndPoint { get { return m_senderEndPoint; } } + + /// + /// NetConnection of sender, if any + /// + public NetConnection SenderConnection { get { return m_senderConnection; } } + + /// + /// What local time the message was received from the network + /// + public double ReceiveTime { get { return m_receiveTime; } } + + internal NetIncomingMessage() + { + } + + internal NetIncomingMessage(NetIncomingMessageType tp) + { + m_incomingMessageType = tp; + } + + internal void Reset() + { + m_incomingMessageType = NetIncomingMessageType.Error; + m_readPosition = 0; + m_receivedMessageType = NetMessageType.LibraryError; + m_senderConnection = null; + m_bitLength = 0; + m_isFragment = false; + } + + /// + /// Decrypt a message + /// + /// The encryption algorithm used to encrypt the message + /// true on success + public bool Decrypt(NetEncryption encryption) + { + return encryption.Decrypt(this); + } + + /// + /// Reads a value, in local time comparable to NetTime.Now, written using WriteTime() + /// Must have a connected sender + /// + public double ReadTime(bool highPrecision) + { + return ReadTime(m_senderConnection, highPrecision); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetIncomingMessage #" + m_sequenceNumber + " " + this.LengthBytes + " bytes]"; + } + } +} diff --git a/Lidgren.Network/NetIncomingMessageType.cs b/Lidgren.Network/NetIncomingMessageType.cs new file mode 100644 index 0000000..73f4ae7 --- /dev/null +++ b/Lidgren.Network/NetIncomingMessageType.cs @@ -0,0 +1,105 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Lidgren.Network +{ + /// + /// The type of a NetIncomingMessage + /// + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] + public enum NetIncomingMessageType + { + // + // library note: values are power-of-two, but they are not flags - it's a convenience for NetPeerConfiguration.DisabledMessageTypes + // + + /// + /// Error; this value should never appear + /// + Error = 0, + + /// + /// Status for a connection changed + /// + StatusChanged = 1 << 0, // Data (string) + + /// + /// Data sent using SendUnconnectedMessage + /// + UnconnectedData = 1 << 1, // Data Based on data received + + /// + /// Connection approval is needed + /// + ConnectionApproval = 1 << 2, // Data + + /// + /// Application data + /// + Data = 1 << 3, // Data Based on data received + + /// + /// Receipt of delivery + /// + Receipt = 1 << 4, // Data + + /// + /// Discovery request for a response + /// + DiscoveryRequest = 1 << 5, // (no data) + + /// + /// Discovery response to a request + /// + DiscoveryResponse = 1 << 6, // Data + + /// + /// Verbose debug message + /// + VerboseDebugMessage = 1 << 7, // Data (string) + + /// + /// Debug message + /// + DebugMessage = 1 << 8, // Data (string) + + /// + /// Warning message + /// + WarningMessage = 1 << 9, // Data (string) + + /// + /// Error message + /// + ErrorMessage = 1 << 10, // Data (string) + + /// + /// NAT introduction was successful + /// + NatIntroductionSuccess = 1 << 11, // Data (as passed to master server) + + /// + /// A roundtrip was measured and NetConnection.AverageRoundtripTime was updated + /// + ConnectionLatencyUpdated = 1 << 12, // Seconds as a Single + } +} diff --git a/Lidgren.Network/NetMessageType.cs b/Lidgren.Network/NetMessageType.cs new file mode 100644 index 0000000..ef56eee --- /dev/null +++ b/Lidgren.Network/NetMessageType.cs @@ -0,0 +1,175 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + internal enum NetMessageType : byte + { + Unconnected = 0, + + UserUnreliable = 1, + + UserSequenced1 = 2, + UserSequenced2 = 3, + UserSequenced3 = 4, + UserSequenced4 = 5, + UserSequenced5 = 6, + UserSequenced6 = 7, + UserSequenced7 = 8, + UserSequenced8 = 9, + UserSequenced9 = 10, + UserSequenced10 = 11, + UserSequenced11 = 12, + UserSequenced12 = 13, + UserSequenced13 = 14, + UserSequenced14 = 15, + UserSequenced15 = 16, + UserSequenced16 = 17, + UserSequenced17 = 18, + UserSequenced18 = 19, + UserSequenced19 = 20, + UserSequenced20 = 21, + UserSequenced21 = 22, + UserSequenced22 = 23, + UserSequenced23 = 24, + UserSequenced24 = 25, + UserSequenced25 = 26, + UserSequenced26 = 27, + UserSequenced27 = 28, + UserSequenced28 = 29, + UserSequenced29 = 30, + UserSequenced30 = 31, + UserSequenced31 = 32, + UserSequenced32 = 33, + + UserReliableUnordered = 34, + + UserReliableSequenced1 = 35, + UserReliableSequenced2 = 36, + UserReliableSequenced3 = 37, + UserReliableSequenced4 = 38, + UserReliableSequenced5 = 39, + UserReliableSequenced6 = 40, + UserReliableSequenced7 = 41, + UserReliableSequenced8 = 42, + UserReliableSequenced9 = 43, + UserReliableSequenced10 = 44, + UserReliableSequenced11 = 45, + UserReliableSequenced12 = 46, + UserReliableSequenced13 = 47, + UserReliableSequenced14 = 48, + UserReliableSequenced15 = 49, + UserReliableSequenced16 = 50, + UserReliableSequenced17 = 51, + UserReliableSequenced18 = 52, + UserReliableSequenced19 = 53, + UserReliableSequenced20 = 54, + UserReliableSequenced21 = 55, + UserReliableSequenced22 = 56, + UserReliableSequenced23 = 57, + UserReliableSequenced24 = 58, + UserReliableSequenced25 = 59, + UserReliableSequenced26 = 60, + UserReliableSequenced27 = 61, + UserReliableSequenced28 = 62, + UserReliableSequenced29 = 63, + UserReliableSequenced30 = 64, + UserReliableSequenced31 = 65, + UserReliableSequenced32 = 66, + + UserReliableOrdered1 = 67, + UserReliableOrdered2 = 68, + UserReliableOrdered3 = 69, + UserReliableOrdered4 = 70, + UserReliableOrdered5 = 71, + UserReliableOrdered6 = 72, + UserReliableOrdered7 = 73, + UserReliableOrdered8 = 74, + UserReliableOrdered9 = 75, + UserReliableOrdered10 = 76, + UserReliableOrdered11 = 77, + UserReliableOrdered12 = 78, + UserReliableOrdered13 = 79, + UserReliableOrdered14 = 80, + UserReliableOrdered15 = 81, + UserReliableOrdered16 = 82, + UserReliableOrdered17 = 83, + UserReliableOrdered18 = 84, + UserReliableOrdered19 = 85, + UserReliableOrdered20 = 86, + UserReliableOrdered21 = 87, + UserReliableOrdered22 = 88, + UserReliableOrdered23 = 89, + UserReliableOrdered24 = 90, + UserReliableOrdered25 = 91, + UserReliableOrdered26 = 92, + UserReliableOrdered27 = 93, + UserReliableOrdered28 = 94, + UserReliableOrdered29 = 95, + UserReliableOrdered30 = 96, + UserReliableOrdered31 = 97, + UserReliableOrdered32 = 98, + + Unused1 = 99, + Unused2 = 100, + Unused3 = 101, + Unused4 = 102, + Unused5 = 103, + Unused6 = 104, + Unused7 = 105, + Unused8 = 106, + Unused9 = 107, + Unused10 = 108, + Unused11 = 109, + Unused12 = 110, + Unused13 = 111, + Unused14 = 112, + Unused15 = 113, + Unused16 = 114, + Unused17 = 115, + Unused18 = 116, + Unused19 = 117, + Unused20 = 118, + Unused21 = 119, + Unused22 = 120, + Unused23 = 121, + Unused24 = 122, + Unused25 = 123, + Unused26 = 124, + Unused27 = 125, + Unused28 = 126, + Unused29 = 127, + + LibraryError = 128, + Ping = 129, // used for RTT calculation + Pong = 130, // used for RTT calculation + Connect = 131, + ConnectResponse = 132, + ConnectionEstablished = 133, + Acknowledge = 134, + Disconnect = 135, + Discovery = 136, + DiscoveryResponse = 137, + NatPunchMessage = 138, // send between peers + NatIntroduction = 139, // send to master server + ExpandMTURequest = 140, + ExpandMTUSuccess = 141, + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetNatIntroduction.cs b/Lidgren.Network/NetNatIntroduction.cs new file mode 100644 index 0000000..ddbcf7e --- /dev/null +++ b/Lidgren.Network/NetNatIntroduction.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + /// + /// Send NetIntroduction to hostExternal and clientExternal; introducing client to host + /// + public void Introduce( + IPEndPoint hostInternal, + IPEndPoint hostExternal, + IPEndPoint clientInternal, + IPEndPoint clientExternal, + string token) + { + // send message to client + NetOutgoingMessage msg = CreateMessage(10 + token.Length + 1); + msg.m_messageType = NetMessageType.NatIntroduction; + msg.Write((byte)0); + msg.Write(hostInternal); + msg.Write(hostExternal); + msg.Write(token); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(clientExternal, msg)); + + // send message to host + msg = CreateMessage(10 + token.Length + 1); + msg.m_messageType = NetMessageType.NatIntroduction; + msg.Write((byte)1); + msg.Write(clientInternal); + msg.Write(clientExternal); + msg.Write(token); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(hostExternal, msg)); + } + + /// + /// Called when host/client receives a NatIntroduction message from a master server + /// + internal void HandleNatIntroduction(int ptr) + { + VerifyNetworkThread(); + + // read intro + NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length + + byte hostByte = tmp.ReadByte(); + IPEndPoint remoteInternal = tmp.ReadIPEndPoint(); + IPEndPoint remoteExternal = tmp.ReadIPEndPoint(); + string token = tmp.ReadString(); + bool isHost = (hostByte != 0); + + LogDebug("NAT introduction received; we are designated " + (isHost ? "host" : "client")); + + NetOutgoingMessage punch; + + if (!isHost && m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess) == false) + return; // no need to punch - we're not listening for nat intros! + + // send internal punch + punch = CreateMessage(1); + punch.m_messageType = NetMessageType.NatPunchMessage; + punch.Write(hostByte); + punch.Write(token); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(remoteInternal, punch)); + LogDebug("NAT punch sent to " + remoteInternal); + + // send external punch + punch = CreateMessage(1); + punch.m_messageType = NetMessageType.NatPunchMessage; + punch.Write(hostByte); + punch.Write(token); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(remoteExternal, punch)); + LogDebug("NAT punch sent to " + remoteExternal); + + } + + /// + /// Called when receiving a NatPunchMessage from a remote endpoint + /// + private void HandleNatPunch(int ptr, IPEndPoint senderEndPoint) + { + NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length + + byte fromHostByte = tmp.ReadByte(); + if (fromHostByte == 0) + { + // it's from client + LogDebug("NAT punch received from " + senderEndPoint + " we're host, so we ignore this"); + return; // don't alert hosts about nat punch successes; only clients + } + string token = tmp.ReadString(); + + LogDebug("NAT punch received from " + senderEndPoint + " we're client, so we've succeeded - token is " + token); + + // + // Release punch success to client; enabling him to Connect() to msg.SenderIPEndPoint if token is ok + // + NetIncomingMessage punchSuccess = CreateIncomingMessage(NetIncomingMessageType.NatIntroductionSuccess, 10); + punchSuccess.m_senderEndPoint = senderEndPoint; + punchSuccess.Write(token); + ReleaseMessage(punchSuccess); + + // send a return punch just for good measure + var punch = CreateMessage(1); + punch.m_messageType = NetMessageType.NatPunchMessage; + punch.Write((byte)0); + punch.Write(token); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(senderEndPoint, punch)); + } + } +} diff --git a/Lidgren.Network/NetOutgoingMessage.cs b/Lidgren.Network/NetOutgoingMessage.cs new file mode 100644 index 0000000..9855a49 --- /dev/null +++ b/Lidgren.Network/NetOutgoingMessage.cs @@ -0,0 +1,135 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Outgoing message used to send data to remote peer(s) + /// + [DebuggerDisplay("LengthBits={LengthBits}")] + public sealed class NetOutgoingMessage : NetBuffer + { + internal NetMessageType m_messageType; + internal bool m_isSent; + internal int m_recyclingCount; + + internal int m_fragmentGroup; // which group of fragments ths belongs to + internal int m_fragmentGroupTotalBits; // total number of bits in this group + internal int m_fragmentChunkByteSize; // size, in bytes, of every chunk but the last one + internal int m_fragmentChunkNumber; // which number chunk this is, starting with 0 + + internal NetOutgoingMessage() + { + } + + internal void Reset() + { + m_messageType = NetMessageType.LibraryError; + m_bitLength = 0; + m_isSent = false; + m_recyclingCount = 0; + m_fragmentGroup = 0; + } + + internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber) + { + // 8 bits - NetMessageType + // 1 bit - Fragment? + // 15 bits - Sequence number + // 16 bits - Payload length in bits + + intoBuffer[ptr++] = (byte)m_messageType; + + byte low = (byte)((sequenceNumber << 1) | (m_fragmentGroup == 0 ? 0 : 1)); + intoBuffer[ptr++] = low; + intoBuffer[ptr++] = (byte)(sequenceNumber >> 7); + + if (m_fragmentGroup == 0) + { + intoBuffer[ptr++] = (byte)m_bitLength; + intoBuffer[ptr++] = (byte)(m_bitLength >> 8); + + int byteLen = NetUtility.BytesToHoldBits(m_bitLength); + if (byteLen > 0) + { + Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen); + ptr += byteLen; + } + } + else + { + int wasPtr = ptr; + intoBuffer[ptr++] = (byte)m_bitLength; + intoBuffer[ptr++] = (byte)(m_bitLength >> 8); + + // + // write fragmentation header + // + ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber); + int hdrLen = ptr - wasPtr - 2; + + // update length + int realBitLength = m_bitLength + (hdrLen * 8); + intoBuffer[wasPtr] = (byte)realBitLength; + intoBuffer[wasPtr + 1] = (byte)(realBitLength >> 8); + + int byteLen = NetUtility.BytesToHoldBits(m_bitLength); + if (byteLen > 0) + { + Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen); + ptr += byteLen; + } + } + + NetException.Assert(ptr > 0); + return ptr; + } + + internal int GetEncodedSize() + { + int retval = NetConstants.UnfragmentedMessageHeaderSize; // regular headers + if (m_fragmentGroup != 0) + retval += NetFragmentationHelper.GetFragmentationHeaderSize(m_fragmentGroup, m_fragmentGroupTotalBits / 8, m_fragmentChunkByteSize, m_fragmentChunkNumber); + retval += this.LengthBytes; + return retval; + } + + /// + /// Encrypt this message using the provided algorithm; no more writing can be done before sending it or the message will be corrupt! + /// + public bool Encrypt(NetEncryption encryption) + { + return encryption.Encrypt(this); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + if (m_isSent) + return "[NetOutgoingMessage " + m_messageType + " " + this.LengthBytes + " bytes]"; + + return "[NetOutgoingMessage " + this.LengthBytes + " bytes]"; + } + } +} diff --git a/Lidgren.Network/NetPeer.Discovery.cs b/Lidgren.Network/NetPeer.Discovery.cs new file mode 100644 index 0000000..c00c826 --- /dev/null +++ b/Lidgren.Network/NetPeer.Discovery.cs @@ -0,0 +1,60 @@ +using System; +using System.Net; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + /// + /// Emit a discovery signal to all hosts on your subnet + /// + public void DiscoverLocalPeers(int serverPort) + { + NetOutgoingMessage om = CreateMessage(0); + om.m_messageType = NetMessageType.Discovery; + m_unsentUnconnectedMessages.Enqueue(new NetTuple(new IPEndPoint(IPAddress.Broadcast, serverPort), om)); + } + + /// + /// Emit a discovery signal to a single known host + /// + public bool DiscoverKnownPeer(string host, int serverPort) + { + IPAddress address = NetUtility.Resolve(host); + if (address == null) + return false; + DiscoverKnownPeer(new IPEndPoint(address, serverPort)); + return true; + } + + /// + /// Emit a discovery signal to a single known host + /// + public void DiscoverKnownPeer(IPEndPoint endPoint) + { + NetOutgoingMessage om = CreateMessage(0); + om.m_messageType = NetMessageType.Discovery; + m_unsentUnconnectedMessages.Enqueue(new NetTuple(endPoint, om)); + } + + /// + /// Send a discovery response message + /// + public void SendDiscoveryResponse(NetOutgoingMessage msg, IPEndPoint recipient) + { + if (recipient == null) + throw new ArgumentNullException("recipient"); + + if (msg == null) + msg = CreateMessage(0); + else if (msg.m_isSent) + throw new NetException("Message has already been sent!"); + + if (msg.LengthBytes >= m_configuration.MaximumTransmissionUnit) + throw new NetException("Cannot send discovery message larger than MTU (currently " + m_configuration.MaximumTransmissionUnit + " bytes)"); + + msg.m_messageType = NetMessageType.DiscoveryResponse; + m_unsentUnconnectedMessages.Enqueue(new NetTuple(recipient, msg)); + } + } +} diff --git a/Lidgren.Network/NetPeer.Fragmentation.cs b/Lidgren.Network/NetPeer.Fragmentation.cs new file mode 100644 index 0000000..87cc2b6 --- /dev/null +++ b/Lidgren.Network/NetPeer.Fragmentation.cs @@ -0,0 +1,167 @@ +using System; +using System.Threading; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + internal class ReceivedFragmentGroup + { + //public float LastReceived; + public byte[] Data; + public NetBitVector ReceivedChunks; + } + + public partial class NetPeer + { + private int m_lastUsedFragmentGroup; + + private Dictionary> m_receivedFragmentGroups; + + // on user thread + private NetSendResult SendFragmentedMessage(NetOutgoingMessage msg, IList recipients, NetDeliveryMethod method, int sequenceChannel) + { + // Note: this group id is PER SENDING/NetPeer; ie. same id is sent to all recipients; + // this should be ok however; as long as recipients differentiate between same id but different sender + int group = Interlocked.Increment(ref m_lastUsedFragmentGroup); + if (group >= NetConstants.MaxFragmentationGroups) + { + // @TODO: not thread safe; but in practice probably not an issue + m_lastUsedFragmentGroup = 1; + group = 1; + } + msg.m_fragmentGroup = group; + + // do not send msg; but set fragmentgroup in case user tries to recycle it immediately + + // create fragmentation specifics + int totalBytes = msg.LengthBytes; + + // determine minimum mtu for all recipients + int mtu = GetMTU(recipients); + int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu); + + int numChunks = totalBytes / bytesPerChunk; + if (numChunks * bytesPerChunk < totalBytes) + numChunks++; + + NetSendResult retval = NetSendResult.Sent; + + int bitsPerChunk = bytesPerChunk * 8; + int bitsLeft = msg.LengthBits; + for (int i = 0; i < numChunks; i++) + { + NetOutgoingMessage chunk = CreateMessage(0); + + chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft); + chunk.m_data = msg.m_data; + chunk.m_fragmentGroup = group; + chunk.m_fragmentGroupTotalBits = totalBytes * 8; + chunk.m_fragmentChunkByteSize = bytesPerChunk; + chunk.m_fragmentChunkNumber = i; + + NetException.Assert(chunk.m_bitLength != 0); + NetException.Assert(chunk.GetEncodedSize() < mtu); + + Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count); + + foreach (NetConnection recipient in recipients) + { + var res = recipient.EnqueueMessage(chunk, method, sequenceChannel); + if ((int)res > (int)retval) + retval = res; // return "worst" result + } + + bitsLeft -= bitsPerChunk; + } + + return retval; + } + + private void HandleReleasedFragment(NetIncomingMessage im) + { + VerifyNetworkThread(); + + // + // read fragmentation header and combine fragments + // + int group; + int totalBits; + int chunkByteSize; + int chunkNumber; + int ptr = NetFragmentationHelper.ReadHeader( + im.m_data, 0, + out group, + out totalBits, + out chunkByteSize, + out chunkNumber + ); + + NetException.Assert(im.LengthBytes > ptr); + + NetException.Assert(group > 0); + NetException.Assert(totalBits > 0); + NetException.Assert(chunkByteSize > 0); + + int totalBytes = NetUtility.BytesToHoldBits((int)totalBits); + int totalNumChunks = totalBytes / chunkByteSize; + if (totalNumChunks * chunkByteSize < totalBytes) + totalNumChunks++; + + NetException.Assert(chunkNumber < totalNumChunks); + + if (chunkNumber >= totalNumChunks) + { + LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")"); + return; + } + + Dictionary groups; + if (!m_receivedFragmentGroups.TryGetValue(im.SenderConnection, out groups)) + { + groups = new Dictionary(); + m_receivedFragmentGroups[im.SenderConnection] = groups; + } + + ReceivedFragmentGroup info; + if (!groups.TryGetValue(group, out info)) + { + info = new ReceivedFragmentGroup(); + info.Data = new byte[totalBytes]; + info.ReceivedChunks = new NetBitVector(totalNumChunks); + groups[group] = info; + } + + info.ReceivedChunks[chunkNumber] = true; + //info.LastReceived = (float)NetTime.Now; + + // copy to data + int offset = (chunkNumber * chunkByteSize); + Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr); + + int cnt = info.ReceivedChunks.Count(); + //LogVerbose("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")"); + + LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)"); + + if (info.ReceivedChunks.Count() == totalNumChunks) + { + // Done! Transform this incoming message + im.m_data = info.Data; + im.m_bitLength = (int)totalBits; + im.m_isFragment = false; + + LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)"); + groups.Remove(group); + + ReleaseMessage(im); + } + else + { + // data has been copied; recycle this incoming message + Recycle(im); + } + + return; + } + } +} diff --git a/Lidgren.Network/NetPeer.Internal.cs b/Lidgren.Network/NetPeer.Internal.cs new file mode 100644 index 0000000..9a74b80 --- /dev/null +++ b/Lidgren.Network/NetPeer.Internal.cs @@ -0,0 +1,762 @@ +#if !__ANDROID__ && !IOS && !UNITY_WEBPLAYER && !UNITY_ANDROID && !UNITY_IPHONE +#define IS_MAC_AVAILABLE +#endif + +using System; +using System.Net; +using System.Threading; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Net.Sockets; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + private NetPeerStatus m_status; + private Thread m_networkThread; + private Socket m_socket; + internal byte[] m_sendBuffer; + internal byte[] m_receiveBuffer; + internal NetIncomingMessage m_readHelperMessage; + private EndPoint m_senderRemote; + private object m_initializeLock = new object(); + private uint m_frameCounter; + private double m_lastHeartbeat; + private double m_lastSocketBind = float.MinValue; + private NetUPnP m_upnp; + + internal readonly NetPeerConfiguration m_configuration; + private readonly NetQueue m_releasedIncomingMessages; + internal readonly NetQueue> m_unsentUnconnectedMessages; + + internal Dictionary m_handshakes; + + internal readonly NetPeerStatistics m_statistics; + internal long m_uniqueIdentifier; + internal bool m_executeFlushSendQueue; + + private AutoResetEvent m_messageReceivedEvent; + private List> m_receiveCallbacks; + + /// + /// Gets the socket, if Start() has been called + /// + public Socket Socket { get { return m_socket; } } + + /// + /// Call this to register a callback for when a new message arrives + /// + public void RegisterReceivedCallback(SendOrPostCallback callback, SynchronizationContext syncContext = null) + { + if (syncContext == null) + syncContext = SynchronizationContext.Current; + if (syncContext == null) + throw new NetException("Need a SynchronizationContext to register callback on correct thread!"); + if (m_receiveCallbacks == null) + m_receiveCallbacks = new List>(); + m_receiveCallbacks.Add(new NetTuple(syncContext, callback)); + } + + /// + /// Call this to unregister a callback, but remember to do it in the same synchronization context! + /// + public void UnregisterReceivedCallback(SendOrPostCallback callback) + { + if (m_receiveCallbacks == null) + return; + + // remove all callbacks regardless of sync context + RestartRemoveCallbacks: + for (int i = 0; i < m_receiveCallbacks.Count; i++) + { + if (m_receiveCallbacks[i].Item2.Equals(callback)) + { + m_receiveCallbacks.RemoveAt(i); + goto RestartRemoveCallbacks; + } + } + if (m_receiveCallbacks.Count < 1) + m_receiveCallbacks = null; + } + + internal void ReleaseMessage(NetIncomingMessage msg) + { + NetException.Assert(msg.m_incomingMessageType != NetIncomingMessageType.Error); + + if (msg.m_isFragment) + { + HandleReleasedFragment(msg); + return; + } + + m_releasedIncomingMessages.Enqueue(msg); + + if (m_messageReceivedEvent != null) + m_messageReceivedEvent.Set(); + + if (m_receiveCallbacks != null) + { + foreach (var tuple in m_receiveCallbacks) + { + try + { + tuple.Item1.Post(tuple.Item2, this); + } + catch (Exception ex) + { + LogWarning("Receive callback exception:" + ex); + } + } + } + } + + private void BindSocket(bool reBind) + { + double now = NetTime.Now; + if (now - m_lastSocketBind < 1.0) + { + LogDebug("Suppressed socket rebind; last bound " + (now - m_lastSocketBind) + " seconds ago"); + return; // only allow rebind once every second + } + m_lastSocketBind = now; + + if (m_socket == null) + m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + if (reBind) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, (int)1); + + m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize; + m_socket.SendBufferSize = m_configuration.SendBufferSize; + m_socket.Blocking = false; + + var ep = (EndPoint)new IPEndPoint(m_configuration.LocalAddress, reBind ? m_listenPort : m_configuration.Port); + m_socket.Bind(ep); + + try + { + const uint IOC_IN = 0x80000000; + const uint IOC_VENDOR = 0x18000000; + uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; + m_socket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); + } + catch + { + // ignore; SIO_UDP_CONNRESET not supported on this platform + } + + IPEndPoint boundEp = m_socket.LocalEndPoint as IPEndPoint; + LogDebug("Socket bound to " + boundEp + ": " + m_socket.IsBound); + m_listenPort = boundEp.Port; + } + + private void InitializeNetwork() + { + lock (m_initializeLock) + { + m_configuration.Lock(); + + if (m_status == NetPeerStatus.Running) + return; + + if (m_configuration.m_enableUPnP) + m_upnp = new NetUPnP(this); + + InitializePools(); + + m_releasedIncomingMessages.Clear(); + m_unsentUnconnectedMessages.Clear(); + m_handshakes.Clear(); + + // bind to socket + BindSocket(false); + + m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize]; + m_sendBuffer = new byte[m_configuration.SendBufferSize]; + m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error); + m_readHelperMessage.m_data = m_receiveBuffer; + + byte[] macBytes = new byte[8]; + MWCRandom.Instance.NextBytes(macBytes); + +#if IS_MAC_AVAILABLE + try + { + System.Net.NetworkInformation.PhysicalAddress pa = NetUtility.GetMacAddress(); + if (pa != null) + { + macBytes = pa.GetAddressBytes(); + LogVerbose("Mac address is " + NetUtility.ToHexString(macBytes)); + } + else + { + LogWarning("Failed to get Mac address"); + } + } + catch (NotSupportedException) + { + // not supported; lets just keep the random bytes set above + } +#endif + IPEndPoint boundEp = m_socket.LocalEndPoint as IPEndPoint; + byte[] epBytes = BitConverter.GetBytes(boundEp.GetHashCode()); + byte[] combined = new byte[epBytes.Length + macBytes.Length]; + Array.Copy(epBytes, 0, combined, 0, epBytes.Length); + Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length); + m_uniqueIdentifier = BitConverter.ToInt64(NetUtility.CreateSHA1Hash(combined), 0); + + m_status = NetPeerStatus.Running; + } + } + + private void NetworkLoop() + { + VerifyNetworkThread(); + + LogDebug("Network thread started"); + + // + // Network loop + // + do + { + try + { + Heartbeat(); + } + catch (Exception ex) + { + LogWarning(ex.ToString()); + } + } while (m_status == NetPeerStatus.Running); + + // + // perform shutdown + // + ExecutePeerShutdown(); + } + + private void ExecutePeerShutdown() + { + VerifyNetworkThread(); + + LogDebug("Shutting down..."); + + // disconnect and make one final heartbeat + var list = new List(m_handshakes.Count + m_connections.Count); + lock (m_connections) + { + foreach (var conn in m_connections) + if (conn != null) + list.Add(conn); + + lock (m_handshakes) + { + foreach (var hs in m_handshakes.Values) + if (hs != null) + list.Add(hs); + + // shut down connections + foreach (NetConnection conn in list) + conn.Shutdown(m_shutdownReason); + } + } + + FlushDelayedPackets(); + + // one final heartbeat, will send stuff and do disconnect + Heartbeat(); + + Thread.Sleep(10); + + lock (m_initializeLock) + { + try + { + if (m_socket != null) + { + try + { + m_socket.Shutdown(SocketShutdown.Receive); + } + catch(Exception ex) + { + LogDebug("Socket.Shutdown exception: " + ex.ToString()); + } + + try + { + m_socket.Close(2); // 2 seconds timeout + } + catch (Exception ex) + { + LogDebug("Socket.Close exception: " + ex.ToString()); + } + } + } + finally + { + m_socket = null; + m_status = NetPeerStatus.NotRunning; + LogDebug("Shutdown complete"); + + // wake up any threads waiting for server shutdown + if (m_messageReceivedEvent != null) + m_messageReceivedEvent.Set(); + } + + m_lastSocketBind = float.MinValue; + m_receiveBuffer = null; + m_sendBuffer = null; + m_unsentUnconnectedMessages.Clear(); + m_connections.Clear(); + m_connectionLookup.Clear(); + m_handshakes.Clear(); + } + + return; + } + + private void Heartbeat() + { + VerifyNetworkThread(); + + double dnow = NetTime.Now; + float now = (float)dnow; + + double delta = dnow - m_lastHeartbeat; + + int maxCHBpS = 1250 - m_connections.Count; + if (maxCHBpS < 250) + maxCHBpS = 250; + if (delta > (1.0 / (double)maxCHBpS) || delta < 0.0) // max connection heartbeats/second max + { + m_frameCounter++; + m_lastHeartbeat = dnow; + + // do handshake heartbeats + if ((m_frameCounter % 3) == 0) + { + foreach (var kvp in m_handshakes) + { + NetConnection conn = kvp.Value as NetConnection; +#if DEBUG + // sanity check + if (kvp.Key != kvp.Key) + LogWarning("Sanity fail! Connection in handshake list under wrong key!"); +#endif + conn.UnconnectedHeartbeat(now); + if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Disconnected) + { +#if DEBUG + // sanity check + if (conn.m_status == NetConnectionStatus.Disconnected && m_handshakes.ContainsKey(conn.RemoteEndPoint)) + { + LogWarning("Sanity fail! Handshakes list contained disconnected connection!"); + m_handshakes.Remove(conn.RemoteEndPoint); + } +#endif + break; // collection has been modified + } + } + } + +#if DEBUG + SendDelayedPackets(); +#endif + + // update m_executeFlushSendQueue + if (m_configuration.m_autoFlushSendQueue) + m_executeFlushSendQueue = true; + + // do connection heartbeats + lock (m_connections) + { + foreach (NetConnection conn in m_connections) + { + conn.Heartbeat(now, m_frameCounter); + if (conn.m_status == NetConnectionStatus.Disconnected) + { + // + // remove connection + // + m_connections.Remove(conn); + m_connectionLookup.Remove(conn.RemoteEndPoint); + break; // can't continue iteration here + } + } + } + m_executeFlushSendQueue = false; + + // send unsent unconnected messages + NetTuple unsent; + while (m_unsentUnconnectedMessages.TryDequeue(out unsent)) + { + NetOutgoingMessage om = unsent.Item2; + + bool connReset; + int len = om.Encode(m_sendBuffer, 0, 0); + SendPacket(len, unsent.Item1, 1, out connReset); + + Interlocked.Decrement(ref om.m_recyclingCount); + if (om.m_recyclingCount <= 0) + Recycle(om); + } + } + + // + // read from socket + // + if (m_socket == null) + return; + + if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive + return; + + //if (m_socket == null || m_socket.Available < 1) + // return; + + // update now + dnow = NetTime.Now; + now = (float)dnow; + + do + { + int bytesReceived = 0; + try + { + bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote); + } + catch (SocketException sx) + { + switch (sx.SocketErrorCode) + { + case SocketError.ConnectionReset: + // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable" + // we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?! + // So, what to do? + LogWarning("ConnectionReset"); + return; + + case SocketError.NotConnected: + // socket is unbound; try to rebind it (happens on mobile when process goes to sleep) + BindSocket(true); + return; + + default: + LogWarning("Socket exception: " + sx.ToString()); + return; + } + } + + if (bytesReceived < NetConstants.HeaderByteSize) + return; + + //LogVerbose("Received " + bytesReceived + " bytes"); + + IPEndPoint ipsender = (IPEndPoint)m_senderRemote; + + if (m_upnp != null && now < m_upnp.m_discoveryResponseDeadline && bytesReceived > 32) + { + // is this an UPnP response? + string resp = System.Text.Encoding.ASCII.GetString(m_receiveBuffer, 0, bytesReceived); + if (resp.Contains("upnp:rootdevice") || resp.Contains("UPnP/1.0")) + { + try + { + resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9); + resp = resp.Substring(0, resp.IndexOf("\r")).Trim(); + m_upnp.ExtractServiceUrl(resp); + return; + } + catch (Exception ex) + { + LogDebug("Failed to parse UPnP response: " + ex.ToString()); + + // don't try to parse this packet further + return; + } + } + } + + NetConnection sender = null; + m_connectionLookup.TryGetValue(ipsender, out sender); + + // + // parse packet into messages + // + int numMessages = 0; + int numFragments = 0; + int ptr = 0; + while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize) + { + // decode header + // 8 bits - NetMessageType + // 1 bit - Fragment? + // 15 bits - Sequence number + // 16 bits - Payload length in bits + + numMessages++; + + NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++]; + + byte low = m_receiveBuffer[ptr++]; + byte high = m_receiveBuffer[ptr++]; + + bool isFragment = ((low & 1) == 1); + ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7)); + + numFragments++; + + ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8)); + int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength); + + if (bytesReceived - ptr < payloadByteLength) + { + LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr)); + return; + } + + if (tp >= NetMessageType.Unused1 && tp <= NetMessageType.Unused29) + { + ThrowOrLog("Unexpected NetMessageType: " + tp); + return; + } + + try + { + if (tp >= NetMessageType.LibraryError) + { + if (sender != null) + sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength); + else + ReceivedUnconnectedLibraryMessage(dnow, ipsender, tp, ptr, payloadByteLength); + } + else + { + if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData)) + return; // dropping unconnected message since it's not enabled + + NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength); + msg.m_isFragment = isFragment; + msg.m_receiveTime = dnow; + msg.m_sequenceNumber = sequenceNumber; + msg.m_receivedMessageType = tp; + msg.m_senderConnection = sender; + msg.m_senderEndPoint = ipsender; + msg.m_bitLength = payloadBitLength; + + Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength); + if (sender != null) + { + if (tp == NetMessageType.Unconnected) + { + // We're connected; but we can still send unconnected messages to this peer + msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData; + ReleaseMessage(msg); + } + else + { + // connected application (non-library) message + sender.ReceivedMessage(msg); + } + } + else + { + // at this point we know the message type is enabled + // unconnected application (non-library) message + msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData; + ReleaseMessage(msg); + } + } + } + catch (Exception ex) + { + LogError("Packet parsing error: " + ex.Message + " from " + ipsender); + } + ptr += payloadByteLength; + } + + m_statistics.PacketReceived(bytesReceived, numMessages, numFragments); + if (sender != null) + sender.m_statistics.PacketReceived(bytesReceived, numMessages, numFragments); + + } while (m_socket.Available > 0); + } + + /// + /// If NetPeerConfiguration.AutoFlushSendQueue() is false; you need to call this to send all messages queued using SendMessage() + /// + public void FlushSendQueue() + { + m_executeFlushSendQueue = true; + } + + internal void HandleIncomingDiscoveryRequest(double now, IPEndPoint senderEndPoint, int ptr, int payloadByteLength) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryRequest)) + { + NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadByteLength); + if (payloadByteLength > 0) + Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength); + dm.m_receiveTime = now; + dm.m_bitLength = payloadByteLength * 8; + dm.m_senderEndPoint = senderEndPoint; + ReleaseMessage(dm); + } + } + + internal void HandleIncomingDiscoveryResponse(double now, IPEndPoint senderEndPoint, int ptr, int payloadByteLength) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryResponse)) + { + NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadByteLength); + if (payloadByteLength > 0) + Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength); + dr.m_receiveTime = now; + dr.m_bitLength = payloadByteLength * 8; + dr.m_senderEndPoint = senderEndPoint; + ReleaseMessage(dr); + } + } + + private void ReceivedUnconnectedLibraryMessage(double now, IPEndPoint senderEndPoint, NetMessageType tp, int ptr, int payloadByteLength) + { + NetConnection shake; + if (m_handshakes.TryGetValue(senderEndPoint, out shake)) + { + shake.ReceivedHandshake(now, tp, ptr, payloadByteLength); + return; + } + + // + // Library message from a completely unknown sender; lets just accept Connect + // + switch (tp) + { + case NetMessageType.Discovery: + HandleIncomingDiscoveryRequest(now, senderEndPoint, ptr, payloadByteLength); + return; + case NetMessageType.DiscoveryResponse: + HandleIncomingDiscoveryResponse(now, senderEndPoint, ptr, payloadByteLength); + return; + case NetMessageType.NatIntroduction: + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess)) + HandleNatIntroduction(ptr); + return; + case NetMessageType.NatPunchMessage: + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess)) + HandleNatPunch(ptr, senderEndPoint); + return; + case NetMessageType.ConnectResponse: + + lock (m_handshakes) + { + foreach (var hs in m_handshakes) + { + if (hs.Key.Address.Equals(senderEndPoint.Address)) + { + if (hs.Value.m_connectionInitiator) + { + // + // We are currently trying to connection to XX.XX.XX.XX:Y + // ... but we just received a ConnectResponse from XX.XX.XX.XX:Z + // Lets just assume the router decided to use this port instead + // + var hsconn = hs.Value; + m_connectionLookup.Remove(hs.Key); + m_handshakes.Remove(hs.Key); + + LogDebug("Detected host port change; rerouting connection to " + senderEndPoint); + hsconn.MutateEndPoint(senderEndPoint); + + m_connectionLookup.Add(senderEndPoint, hsconn); + m_handshakes.Add(senderEndPoint, hsconn); + + hsconn.ReceivedHandshake(now, tp, ptr, payloadByteLength); + return; + } + } + } + } + + LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint); + return; + case NetMessageType.Connect: + if (m_configuration.AcceptIncomingConnections == false) + { + LogWarning("Received Connect, but we're not accepting incoming connections!"); + return; + } + // handle connect + // It's someone wanting to shake hands with us! + + int reservedSlots = m_handshakes.Count + m_connections.Count; + if (reservedSlots >= m_configuration.m_maximumConnections) + { + // server full + NetOutgoingMessage full = CreateMessage("Server full"); + full.m_messageType = NetMessageType.Disconnect; + SendLibrary(full, senderEndPoint); + return; + } + + // Ok, start handshake! + NetConnection conn = new NetConnection(this, senderEndPoint); + conn.m_status = NetConnectionStatus.ReceivedInitiation; + m_handshakes.Add(senderEndPoint, conn); + conn.ReceivedHandshake(now, tp, ptr, payloadByteLength); + return; + + case NetMessageType.Disconnect: + // this is probably ok + LogVerbose("Received Disconnect from unconnected source: " + senderEndPoint); + return; + default: + LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint); + return; + } + } + + internal void AcceptConnection(NetConnection conn) + { + // LogDebug("Accepted connection " + conn); + conn.InitExpandMTU(NetTime.Now); + + if (m_handshakes.Remove(conn.m_remoteEndPoint) == false) + LogWarning("AcceptConnection called but m_handshakes did not contain it!"); + + lock (m_connections) + { + if (m_connections.Contains(conn)) + { + LogWarning("AcceptConnection called but m_connection already contains it!"); + } + else + { + m_connections.Add(conn); + m_connectionLookup.Add(conn.m_remoteEndPoint, conn); + } + } + } + + [Conditional("DEBUG")] + internal void VerifyNetworkThread() + { + Thread ct = Thread.CurrentThread; + if (Thread.CurrentThread != m_networkThread) + throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")"); + } + + internal NetIncomingMessage SetupReadHelperMessage(int ptr, int payloadLength) + { + VerifyNetworkThread(); + + m_readHelperMessage.m_bitLength = (ptr + payloadLength) * 8; + m_readHelperMessage.m_readPosition = (ptr * 8); + return m_readHelperMessage; + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetPeer.LatencySimulation.cs b/Lidgren.Network/NetPeer.LatencySimulation.cs new file mode 100644 index 0000000..1f2f7d5 --- /dev/null +++ b/Lidgren.Network/NetPeer.LatencySimulation.cs @@ -0,0 +1,308 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +//#define USE_RELEASE_STATISTICS + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Diagnostics; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + +#if DEBUG + private readonly List m_delayedPackets = new List(); + + private class DelayedPacket + { + public byte[] Data; + public double DelayedUntil; + public IPEndPoint Target; + } + + internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset) + { + connectionReset = false; + + // simulate loss + float loss = m_configuration.m_loss; + if (loss > 0.0f) + { + if ((float)MWCRandom.Instance.NextDouble() < loss) + { + LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!"); + return; // packet "lost" + } + } + + m_statistics.PacketSent(numBytes, numMessages); + + // simulate latency + float m = m_configuration.m_minimumOneWayLatency; + float r = m_configuration.m_randomOneWayLatency; + if (m == 0.0f && r == 0.0f) + { + // no latency simulation + // LogVerbose("Sending packet " + numBytes + " bytes"); + bool wasSent = ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset); + // TODO: handle wasSent == false? + + if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextDouble() < m_configuration.m_duplicates) + ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset); // send it again! + + return; + } + + int num = 1; + if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextSingle() < m_configuration.m_duplicates) + num++; + + float delay = 0; + for (int i = 0; i < num; i++) + { + delay = m_configuration.m_minimumOneWayLatency + (MWCRandom.Instance.NextSingle() * m_configuration.m_randomOneWayLatency); + + // Enqueue delayed packet + DelayedPacket p = new DelayedPacket(); + p.Target = target; + p.Data = new byte[numBytes]; + Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes); + p.DelayedUntil = NetTime.Now + delay; + + m_delayedPackets.Add(p); + } + + // LogVerbose("Sending packet " + numBytes + " bytes - delayed " + NetTime.ToReadable(delay)); + } + + private void SendDelayedPackets() + { + if (m_delayedPackets.Count <= 0) + return; + + double now = NetTime.Now; + + bool connectionReset; + + RestartDelaySending: + foreach (DelayedPacket p in m_delayedPackets) + { + if (now > p.DelayedUntil) + { + ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset); + m_delayedPackets.Remove(p); + goto RestartDelaySending; + } + } + } + + private void FlushDelayedPackets() + { + try + { + bool connectionReset; + foreach (DelayedPacket p in m_delayedPackets) + ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset); + m_delayedPackets.Clear(); + } + catch { } + } + + internal bool ActuallySendPacket(byte[] data, int numBytes, IPEndPoint target, out bool connectionReset) + { + connectionReset = false; + try + { + // TODO: refactor this check outta here + if (target.Address == IPAddress.Broadcast) + { + // Some networks do not allow + // a global broadcast so we use the BroadcastAddress from the configuration + // this can be resolved to a local broadcast addresss e.g 192.168.x.255 + target.Address = m_configuration.BroadcastAddress; + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + } + + int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + + // LogDebug("Sent " + numBytes + " bytes"); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return false; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + { + // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable" + connectionReset = true; + return false; + } + LogError("Failed to send packet: " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + if (target.Address == IPAddress.Broadcast) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); + } + return true; + } + + internal bool SendMTUPacket(int numBytes, IPEndPoint target) + { + try + { + m_socket.DontFragment = true; + int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + + m_statistics.PacketSent(numBytes, 1); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.MessageSize) + return false; + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return true; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + return true; + LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + m_socket.DontFragment = false; + } + return true; + } +#else + internal bool SendMTUPacket(int numBytes, IPEndPoint target) + { + try + { + m_socket.DontFragment = true; + int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.MessageSize) + return false; + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return true; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + return true; + LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + m_socket.DontFragment = false; + } + return true; + } + + // + // Release - just send the packet straight away + // + internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset) + { +#if USE_RELEASE_STATISTICS + m_statistics.PacketSent(numBytes, numMessages); +#endif + connectionReset = false; + try + { + // TODO: refactor this check outta here + if (target.Address == IPAddress.Broadcast) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + + int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + { + // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable" + connectionReset = true; + return; + } + LogError("Failed to send packet: " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + if (target.Address == IPAddress.Broadcast) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); + } + return; + } + + private void FlushDelayedPackets() + { + } + + private void SendCallBack(IAsyncResult res) + { + NetException.Assert(res.IsCompleted == true); + m_socket.EndSendTo(res); + } +#endif + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetPeer.Logging.cs b/Lidgren.Network/NetPeer.Logging.cs new file mode 100644 index 0000000..006cdf5 --- /dev/null +++ b/Lidgren.Network/NetPeer.Logging.cs @@ -0,0 +1,63 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System.Diagnostics; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + [Conditional("DEBUG")] + internal void LogVerbose(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Verbose, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.VerboseDebugMessage, message)); + } + + [Conditional("DEBUG")] + internal void LogDebug(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Debug, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DebugMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.DebugMessage, message)); + } + + internal void LogWarning(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Warn, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.WarningMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.WarningMessage, message)); + } + + internal void LogError(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Error, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ErrorMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.ErrorMessage, message)); + } + } +} diff --git a/Lidgren.Network/NetPeer.MessagePools.cs b/Lidgren.Network/NetPeer.MessagePools.cs new file mode 100644 index 0000000..e2be9f8 --- /dev/null +++ b/Lidgren.Network/NetPeer.MessagePools.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + private List m_storagePool; // sorted smallest to largest + private NetQueue m_outgoingMessagesPool; + private NetQueue m_incomingMessagesPool; + + internal int m_storagePoolBytes; + + private void InitializePools() + { + if (m_configuration.UseMessageRecycling) + { + m_storagePool = new List(16); + m_outgoingMessagesPool = new NetQueue(4); + m_incomingMessagesPool = new NetQueue(4); + } + else + { + m_storagePool = null; + m_outgoingMessagesPool = null; + m_incomingMessagesPool = null; + } + } + + internal byte[] GetStorage(int minimumCapacityInBytes) + { + if (m_storagePool == null) + return new byte[minimumCapacityInBytes]; + + lock (m_storagePool) + { + for (int i = 0; i < m_storagePool.Count; i++) + { + byte[] retval = m_storagePool[i]; + if (retval != null && retval.Length >= minimumCapacityInBytes) + { + m_storagePool[i] = null; + m_storagePoolBytes -= retval.Length; + return retval; + } + } + } + m_statistics.m_bytesAllocated += minimumCapacityInBytes; + return new byte[minimumCapacityInBytes]; + } + + internal void Recycle(byte[] storage) + { + if (m_storagePool == null || storage == null) + return; + + lock (m_storagePool) + { + m_storagePoolBytes += storage.Length; + int cnt = m_storagePool.Count; + for (int i = 0; i < cnt; i++) + { + if (m_storagePool[i] == null) + { + m_storagePool[i] = storage; + return; + } + } + m_storagePool.Add(storage); + } + } + + /// + /// Creates a new message for sending + /// + public NetOutgoingMessage CreateMessage() + { + return CreateMessage(m_configuration.m_defaultOutgoingMessageCapacity); + } + + /// + /// Creates a new message for sending and writes the provided string to it + /// + public NetOutgoingMessage CreateMessage(string content) + { + var om = CreateMessage(2 + content.Length); // fair guess + om.Write(content); + return om; + } + + /// + /// Creates a new message for sending + /// + /// initial capacity in bytes + public NetOutgoingMessage CreateMessage(int initialCapacity) + { + NetOutgoingMessage retval; + if (m_outgoingMessagesPool == null || !m_outgoingMessagesPool.TryDequeue(out retval)) + retval = new NetOutgoingMessage(); + + if (initialCapacity > 0) + retval.m_data = GetStorage(initialCapacity); + + return retval; + } + + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] useStorageData) + { + NetIncomingMessage retval; + if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval)) + retval = new NetIncomingMessage(tp); + else + retval.m_incomingMessageType = tp; + retval.m_data = useStorageData; + return retval; + } + + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int minimumByteSize) + { + NetIncomingMessage retval; + if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval)) + retval = new NetIncomingMessage(tp); + else + retval.m_incomingMessageType = tp; + retval.m_data = GetStorage(minimumByteSize); + return retval; + } + + /// + /// Recycles a NetIncomingMessage instance for reuse; taking pressure off the garbage collector + /// + public void Recycle(NetIncomingMessage msg) + { + if (m_incomingMessagesPool == null) + return; + + NetException.Assert(m_incomingMessagesPool.Contains(msg) == false, "Recyling already recycled message! Thread race?"); + + byte[] storage = msg.m_data; + msg.m_data = null; + Recycle(storage); + msg.Reset(); + m_incomingMessagesPool.Enqueue(msg); + } + + /// + /// Recycles a list of NetIncomingMessage instances for reuse; taking pressure off the garbage collector + /// + public void Recycle(IEnumerable toRecycle) + { + if (m_incomingMessagesPool == null) + return; + + // first recycle the storage of each message + if (m_storagePool != null) + { + lock (m_storagePool) + { + foreach (var msg in toRecycle) + { + var storage = msg.m_data; + msg.m_data = null; + m_storagePoolBytes += storage.Length; + int cnt = m_storagePool.Count; + for (int i = 0; i < cnt; i++) + { + if (m_storagePool[i] == null) + { + m_storagePool[i] = storage; + return; + } + } + msg.Reset(); + m_storagePool.Add(storage); + } + } + } + + // then recycle the message objects + m_incomingMessagesPool.Enqueue(toRecycle); + } + + internal void Recycle(NetOutgoingMessage msg) + { + if (m_outgoingMessagesPool == null) + return; + + NetException.Assert(m_outgoingMessagesPool.Contains(msg) == false, "Recyling already recycled message! Thread race?"); + + byte[] storage = msg.m_data; + msg.m_data = null; + + // message fragments cannot be recycled + // TODO: find a way to recycle large message after all fragments has been acknowledged; or? possibly better just to garbage collect them + if (msg.m_fragmentGroup == 0) + Recycle(storage); + + msg.Reset(); + m_outgoingMessagesPool.Enqueue(msg); + } + + /// + /// Creates an incoming message with the required capacity for releasing to the application + /// + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string text) + { + NetIncomingMessage retval; + if (string.IsNullOrEmpty(text)) + { + retval = CreateIncomingMessage(tp, 1); + retval.Write(string.Empty); + return retval; + } + + int numBytes = System.Text.Encoding.UTF8.GetByteCount(text); + retval = CreateIncomingMessage(tp, numBytes + (numBytes > 127 ? 2 : 1)); + retval.Write(text); + + return retval; + } + } +} diff --git a/Lidgren.Network/NetPeer.Send.cs b/Lidgren.Network/NetPeer.Send.cs new file mode 100644 index 0000000..d693240 --- /dev/null +++ b/Lidgren.Network/NetPeer.Send.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Net; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + /// + /// Send a message to a specific connection + /// + /// The message to send + /// The recipient connection + /// How to deliver the message + public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method) + { + return SendMessage(msg, recipient, method, 0); + } + + /// + /// Send a message to a specific connection + /// + /// The message to send + /// The recipient connection + /// How to deliver the message + /// Sequence channel within the delivery method + public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method, int sequenceChannel) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipient == null) + throw new ArgumentNullException("recipient"); + if (sequenceChannel >= NetConstants.NetChannelsPerDeliveryMethod) + throw new ArgumentOutOfRangeException("sequenceChannel"); + + NetException.Assert( + ((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.ReliableUnordered) || + ((method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered) && sequenceChannel == 0)), + "Delivery method " + method + " cannot use sequence channels other than 0!" + ); + + NetException.Assert(method != NetDeliveryMethod.Unknown, "Bad delivery method!"); + + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + msg.m_isSent = true; + + bool suppressFragmentation = (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.UnreliableSequenced) && m_configuration.UnreliableSizeBehaviour != NetUnreliableSizeBehaviour.NormalFragmentation; + + int len = NetConstants.UnfragmentedMessageHeaderSize + msg.LengthBytes; // headers + length, faster than calling msg.GetEncodedSize + if (len <= recipient.m_currentMTU || suppressFragmentation) + { + Interlocked.Increment(ref msg.m_recyclingCount); + return recipient.EnqueueMessage(msg, method, sequenceChannel); + } + else + { + // message must be fragmented! + if (recipient.m_status != NetConnectionStatus.Connected) + return NetSendResult.FailedNotConnected; + return SendFragmentedMessage(msg, new NetConnection[] { recipient }, method, sequenceChannel); + } + } + + internal static int GetMTU(IList recipients) + { + int count = recipients.Count; + + int mtu = int.MaxValue; + if (count < 1) + { +#if DEBUG + throw new NetException("GetMTU called with no recipients"); +#else + // we don't have access to the particular peer, so just use default MTU + return NetPeerConfiguration.kDefaultMTU; +#endif + } + + for(int i=0;i + /// Send a message to a list of connections + /// + /// The message to send + /// The list of recipients to send to + /// How to deliver the message + /// Sequence channel within the delivery method + public void SendMessage(NetOutgoingMessage msg, List recipients, NetDeliveryMethod method, int sequenceChannel) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipients == null) + throw new ArgumentNullException("recipients"); + if (recipients.Count < 1) + throw new NetException("recipients must contain at least one item"); + if (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered) + NetException.Assert(sequenceChannel == 0, "Delivery method " + method + " cannot use sequence channels other than 0!"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + + int mtu = GetMTU(recipients); + + msg.m_isSent = true; + + int len = msg.GetEncodedSize(); + if (len <= mtu) + { + Interlocked.Add(ref msg.m_recyclingCount, recipients.Count); + foreach (NetConnection conn in recipients) + { + if (conn == null) + { + Interlocked.Decrement(ref msg.m_recyclingCount); + continue; + } + NetSendResult res = conn.EnqueueMessage(msg, method, sequenceChannel); + if (res != NetSendResult.Queued && res != NetSendResult.Sent) + Interlocked.Decrement(ref msg.m_recyclingCount); + } + } + else + { + // message must be fragmented! + SendFragmentedMessage(msg, recipients, method, sequenceChannel); + } + + return; + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (host == null) + throw new ArgumentNullException("host"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit) + throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")"); + + IPAddress adr = NetUtility.Resolve(host); + if (adr == null) + throw new NetException("Failed to resolve " + host); + + msg.m_messageType = NetMessageType.Unconnected; + msg.m_isSent = true; + + Interlocked.Increment(ref msg.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(new IPEndPoint(adr, port), msg)); + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipient == null) + throw new ArgumentNullException("recipient"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit) + throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")"); + + msg.m_messageType = NetMessageType.Unconnected; + msg.m_isSent = true; + + Interlocked.Increment(ref msg.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(recipient, msg)); + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, IList recipients) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipients == null) + throw new ArgumentNullException("recipients"); + if (recipients.Count < 1) + throw new NetException("recipients must contain at least one item"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit) + throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")"); + + msg.m_messageType = NetMessageType.Unconnected; + msg.m_isSent = true; + + Interlocked.Add(ref msg.m_recyclingCount, recipients.Count); + foreach(IPEndPoint ep in recipients) + m_unsentUnconnectedMessages.Enqueue(new NetTuple(ep, msg)); + } + + /// + /// Send a message to this exact same netpeer (loopback) + /// + public void SendUnconnectedToSelf(NetOutgoingMessage msg) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + + msg.m_messageType = NetMessageType.Unconnected; + msg.m_isSent = true; + + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData) == false) + return; // dropping unconnected message since it's not enabled for receiving + + NetIncomingMessage om = CreateIncomingMessage(NetIncomingMessageType.UnconnectedData, msg.LengthBytes); + om.Write(msg); + om.m_isFragment = false; + om.m_receiveTime = NetTime.Now; + om.m_senderConnection = null; + om.m_senderEndPoint = m_socket.LocalEndPoint as IPEndPoint; + NetException.Assert(om.m_bitLength == msg.LengthBits); + + ReleaseMessage(om); + } + } +} diff --git a/Lidgren.Network/NetPeer.cs b/Lidgren.Network/NetPeer.cs new file mode 100644 index 0000000..b70bd1d --- /dev/null +++ b/Lidgren.Network/NetPeer.cs @@ -0,0 +1,357 @@ +using System; +using System.Threading; +using System.Collections.Generic; +using System.Net; + +namespace Lidgren.Network +{ + /// + /// Represents a local peer capable of holding zero, one or more connections to remote peers + /// + public partial class NetPeer + { + private static int s_initializedPeersCount; + + private int m_listenPort; + private object m_tag; + private object m_messageReceivedEventCreationLock = new object(); + + internal readonly List m_connections; + private readonly Dictionary m_connectionLookup; + + private string m_shutdownReason; + + /// + /// Gets the NetPeerStatus of the NetPeer + /// + public NetPeerStatus Status { get { return m_status; } } + + /// + /// Signalling event which can be waited on to determine when a message is queued for reading. + /// Note that there is no guarantee that after the event is signaled the blocked thread will + /// find the message in the queue. Other user created threads could be preempted and dequeue + /// the message before the waiting thread wakes up. + /// + public AutoResetEvent MessageReceivedEvent + { + get + { + if (m_messageReceivedEvent == null) + { + lock (m_messageReceivedEventCreationLock) // make sure we don't create more than one event object + { + if (m_messageReceivedEvent == null) + m_messageReceivedEvent = new AutoResetEvent(false); + } + } + return m_messageReceivedEvent; + } + } + + /// + /// Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start() has been called! + /// + public long UniqueIdentifier { get { return m_uniqueIdentifier; } } + + /// + /// Gets the port number this NetPeer is listening and sending on, if Start() has been called + /// + public int Port { get { return m_listenPort; } } + + /// + /// Returns an UPnP object if enabled in the NetPeerConfiguration + /// + public NetUPnP UPnP { get { return m_upnp; } } + + /// + /// Gets or sets the application defined object containing data about the peer + /// + public object Tag + { + get { return m_tag; } + set { m_tag = value; } + } + + /// + /// Gets a copy of the list of connections + /// + public List Connections + { + get + { + lock (m_connections) + return new List(m_connections); + } + } + + /// + /// Gets the number of active connections + /// + public int ConnectionsCount + { + get { return m_connections.Count; } + } + + /// + /// Statistics on this NetPeer since it was initialized + /// + public NetPeerStatistics Statistics + { + get { return m_statistics; } + } + + /// + /// Gets the configuration used to instanciate this NetPeer + /// + public NetPeerConfiguration Configuration { get { return m_configuration; } } + + /// + /// NetPeer constructor + /// + public NetPeer(NetPeerConfiguration config) + { + m_configuration = config; + m_statistics = new NetPeerStatistics(this); + m_releasedIncomingMessages = new NetQueue(4); + m_unsentUnconnectedMessages = new NetQueue>(2); + m_connections = new List(); + m_connectionLookup = new Dictionary(); + m_handshakes = new Dictionary(); + m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0); + m_status = NetPeerStatus.NotRunning; + m_receivedFragmentGroups = new Dictionary>(); + } + + /// + /// Binds to socket and spawns the networking thread + /// + public void Start() + { + if (m_status != NetPeerStatus.NotRunning) + { + // already running! Just ignore... + LogWarning("Start() called on already running NetPeer - ignoring."); + return; + } + + m_status = NetPeerStatus.Starting; + + // fix network thread name + if (m_configuration.NetworkThreadName == "Lidgren network thread") + { + int pc = Interlocked.Increment(ref s_initializedPeersCount); + m_configuration.NetworkThreadName = "Lidgren network thread " + pc.ToString(); + } + + InitializeNetwork(); + + // start network thread + m_networkThread = new Thread(new ThreadStart(NetworkLoop)); + m_networkThread.Name = m_configuration.NetworkThreadName; + m_networkThread.IsBackground = true; + m_networkThread.Start(); + + // send upnp discovery + if (m_upnp != null) + m_upnp.Discover(this); + + // allow some time for network thread to start up in case they call Connect() or UPnP calls immediately + Thread.Sleep(50); + } + + /// + /// Get the connection, if any, for a certain remote endpoint + /// + public NetConnection GetConnection(IPEndPoint ep) + { + NetConnection retval; + + // this should not pose a threading problem, m_connectionLookup is never added to concurrently + // and TryGetValue will not throw an exception on fail, only yield null, which is acceptable + m_connectionLookup.TryGetValue(ep, out retval); + + return retval; + } + + /// + /// Read a pending message from any connection, blocking up to maxMillis if needed + /// + public NetIncomingMessage WaitMessage(int maxMillis) + { + var msg = ReadMessage(); + if (msg != null) + return msg; // no need to wait; we already have a message to deliver + var msgEvt = MessageReceivedEvent; + msgEvt.WaitOne(maxMillis); + return ReadMessage(); + } + + /// + /// Read a pending message from any connection, if any + /// + public NetIncomingMessage ReadMessage() + { + NetIncomingMessage retval; + if (m_releasedIncomingMessages.TryDequeue(out retval)) + { + if (retval.MessageType == NetIncomingMessageType.StatusChanged) + { + NetConnectionStatus status = (NetConnectionStatus)retval.PeekByte(); + retval.SenderConnection.m_visibleStatus = status; + } + } + return retval; + } + + /// + /// Read a pending message from any connection, if any + /// + public int ReadMessages(IList addTo) + { + int added = m_releasedIncomingMessages.TryDrain(addTo); + if (added > 0) + { + for (int i = 0; i < added; i++) + { + var index = addTo.Count - added + i; + var nim = addTo[index]; + if (nim.MessageType == NetIncomingMessageType.StatusChanged) + { + NetConnectionStatus status = (NetConnectionStatus)nim.PeekByte(); + nim.SenderConnection.m_visibleStatus = status; + } + } + } + return added; + } + + // send message immediately + internal void SendLibrary(NetOutgoingMessage msg, IPEndPoint recipient) + { + VerifyNetworkThread(); + NetException.Assert(msg.m_isSent == false); + + bool connReset; + int len = msg.Encode(m_sendBuffer, 0, 0); + SendPacket(len, recipient, 1, out connReset); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(string host, int port) + { + return Connect(new IPEndPoint(NetUtility.Resolve(host), port), null); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(string host, int port, NetOutgoingMessage hailMessage) + { + return Connect(new IPEndPoint(NetUtility.Resolve(host), port), hailMessage); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(IPEndPoint remoteEndPoint) + { + return Connect(remoteEndPoint, null); + } + + /// + /// Create a connection to a remote endpoint + /// + public virtual NetConnection Connect(IPEndPoint remoteEndPoint, NetOutgoingMessage hailMessage) + { + if (remoteEndPoint == null) + throw new ArgumentNullException("remoteEndPoint"); + + lock (m_connections) + { + if (m_status == NetPeerStatus.NotRunning) + throw new NetException("Must call Start() first"); + + if (m_connectionLookup.ContainsKey(remoteEndPoint)) + throw new NetException("Already connected to that endpoint!"); + + NetConnection hs; + if (m_handshakes.TryGetValue(remoteEndPoint, out hs)) + { + // already trying to connect to that endpoint; make another try + switch (hs.m_status) + { + case NetConnectionStatus.InitiatedConnect: + // send another connect + hs.m_connectRequested = true; + break; + case NetConnectionStatus.RespondedConnect: + // send another response + hs.SendConnectResponse((float)NetTime.Now, false); + break; + default: + // weird + LogWarning("Weird situation; Connect() already in progress to remote endpoint; but hs status is " + hs.m_status); + break; + } + return hs; + } + + NetConnection conn = new NetConnection(this, remoteEndPoint); + conn.m_status = NetConnectionStatus.InitiatedConnect; + conn.m_localHailMessage = hailMessage; + + // handle on network thread + conn.m_connectRequested = true; + conn.m_connectionInitiator = true; + + m_handshakes.Add(remoteEndPoint, conn); + + return conn; + } + } + + /// + /// Send raw bytes; only used for debugging + /// +#if DEBUG + public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination) +#else + public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination) +#endif + { + // wrong thread - this miiiight crash with network thread... but what's a boy to do. + Array.Copy(arr, offset, m_sendBuffer, 0, length); + bool unused; + SendPacket(length, destination, 1, out unused); + } + + /// + /// In DEBUG, throws an exception, in RELEASE logs an error message + /// + /// + internal void ThrowOrLog(string message) + { +#if DEBUG + throw new NetException(message); +#else + LogError(message); +#endif + } + + /// + /// Disconnects all active connections and closes the socket + /// + public void Shutdown(string bye) + { + // called on user thread + if (m_socket == null) + return; // already shut down + + LogDebug("Shutdown requested"); + m_shutdownReason = bye; + m_status = NetPeerStatus.ShutdownRequested; + } + } +} diff --git a/Lidgren.Network/NetPeerConfiguration.cs b/Lidgren.Network/NetPeerConfiguration.cs new file mode 100644 index 0000000..ebd8024 --- /dev/null +++ b/Lidgren.Network/NetPeerConfiguration.cs @@ -0,0 +1,509 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Net; + +namespace Lidgren.Network +{ + /// + /// Partly immutable after NetPeer has been initialized + /// + public sealed class NetPeerConfiguration + { + // Maximum transmission unit + // Ethernet can take 1500 bytes of payload, so lets stay below that. + // The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468) + // -20 bytes IP header + // -8 bytes UDP header + // -4 bytes to be on the safe side and align to 8-byte boundary + // Total 1408 bytes + // Note that lidgren headers (5 bytes) are not included here; since it's part of the "mtu payload" + + /// + /// Default MTU value in bytes + /// + public const int kDefaultMTU = 1408; + + private const string c_isLockedMessage = "You may not modify the NetPeerConfiguration after it has been used to initialize a NetPeer"; + + private bool m_isLocked; + private readonly string m_appIdentifier; + private string m_networkThreadName; + private IPAddress m_localAddress; + private IPAddress m_broadcastAddress; + internal bool m_acceptIncomingConnections; + internal int m_maximumConnections; + internal int m_defaultOutgoingMessageCapacity; + internal float m_pingInterval; + internal bool m_useMessageRecycling; + internal float m_connectionTimeout; + internal bool m_enableUPnP; + internal bool m_autoFlushSendQueue; + private NetUnreliableSizeBehaviour m_unreliableSizeBehaviour; + + internal NetIncomingMessageType m_disabledTypes; + internal int m_port; + internal int m_receiveBufferSize; + internal int m_sendBufferSize; + internal float m_resendHandshakeInterval; + internal int m_maximumHandshakeAttempts; + + // bad network simulation + internal float m_loss; + internal float m_duplicates; + internal float m_minimumOneWayLatency; + internal float m_randomOneWayLatency; + + // MTU + internal int m_maximumTransmissionUnit; + internal bool m_autoExpandMTU; + internal float m_expandMTUFrequency; + internal int m_expandMTUFailAttempts; + + /// + /// NetPeerConfiguration constructor + /// + public NetPeerConfiguration(string appIdentifier) + { + if (string.IsNullOrEmpty(appIdentifier)) + throw new NetException("App identifier must be at least one character long"); + m_appIdentifier = appIdentifier; + + // + // default values + // + m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage | NetIncomingMessageType.ConnectionLatencyUpdated | NetIncomingMessageType.NatIntroductionSuccess; + m_networkThreadName = "Lidgren network thread"; + m_localAddress = IPAddress.Any; + m_broadcastAddress = IPAddress.Broadcast; + var ip = NetUtility.GetBroadcastAddress(); + if (ip != null) + { + m_broadcastAddress = ip; + } + m_port = 0; + m_receiveBufferSize = 131071; + m_sendBufferSize = 131071; + m_acceptIncomingConnections = false; + m_maximumConnections = 32; + m_defaultOutgoingMessageCapacity = 16; + m_pingInterval = 4.0f; + m_connectionTimeout = 25.0f; + m_useMessageRecycling = true; + m_resendHandshakeInterval = 3.0f; + m_maximumHandshakeAttempts = 5; + m_autoFlushSendQueue = true; + + m_maximumTransmissionUnit = kDefaultMTU; + m_autoExpandMTU = false; + m_expandMTUFrequency = 2.0f; + m_expandMTUFailAttempts = 5; + m_unreliableSizeBehaviour = NetUnreliableSizeBehaviour.IgnoreMTU; + + m_loss = 0.0f; + m_minimumOneWayLatency = 0.0f; + m_randomOneWayLatency = 0.0f; + m_duplicates = 0.0f; + + m_isLocked = false; + } + + internal void Lock() + { + m_isLocked = true; + } + + /// + /// Gets the identifier of this application; the library can only connect to matching app identifier peers + /// + public string AppIdentifier + { + get { return m_appIdentifier; } + } + + /// + /// Enables receiving of the specified type of message + /// + public void EnableMessageType(NetIncomingMessageType type) + { + m_disabledTypes &= (~type); + } + + /// + /// Disables receiving of the specified type of message + /// + public void DisableMessageType(NetIncomingMessageType type) + { + m_disabledTypes |= type; + } + + /// + /// Enables or disables receiving of the specified type of message + /// + public void SetMessageTypeEnabled(NetIncomingMessageType type, bool enabled) + { + if (enabled) + m_disabledTypes &= (~type); + else + m_disabledTypes |= type; + } + + /// + /// Gets if receiving of the specified type of message is enabled + /// + public bool IsMessageTypeEnabled(NetIncomingMessageType type) + { + return !((m_disabledTypes & type) == type); + } + + /// + /// Gets or sets the behaviour of unreliable sends above MTU + /// + public NetUnreliableSizeBehaviour UnreliableSizeBehaviour + { + get { return m_unreliableSizeBehaviour; } + set { m_unreliableSizeBehaviour = value; } + } + + /// + /// Gets or sets the name of the library network thread. Cannot be changed once NetPeer is initialized. + /// + public string NetworkThreadName + { + get { return m_networkThreadName; } + set + { + if (m_isLocked) + throw new NetException("NetworkThreadName may not be set after the NetPeer which uses the configuration has been started"); + m_networkThreadName = value; + } + } + + /// + /// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized. + /// + public int MaximumConnections + { + get { return m_maximumConnections; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_maximumConnections = value; + } + } + + /// + /// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers. Cannot be changed once NetPeer is initialized. + /// + public int MaximumTransmissionUnit + { + get { return m_maximumTransmissionUnit; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + if (value < 1 || value >= ((ushort.MaxValue + 1) / 8)) + throw new NetException("MaximumTransmissionUnit must be between 1 and " + (((ushort.MaxValue + 1) / 8) - 1) + " bytes"); + m_maximumTransmissionUnit = value; + } + } + + /// + /// Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument + /// + public int DefaultOutgoingMessageCapacity + { + get { return m_defaultOutgoingMessageCapacity; } + set { m_defaultOutgoingMessageCapacity = value; } + } + + /// + /// Gets or sets the time between latency calculating pings + /// + public float PingInterval + { + get { return m_pingInterval; } + set { m_pingInterval = value; } + } + + /// + /// Gets or sets if the library should recycling messages to avoid excessive garbage collection. Cannot be changed once NetPeer is initialized. + /// + public bool UseMessageRecycling + { + get { return m_useMessageRecycling; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_useMessageRecycling = value; + } + } + + /// + /// Gets or sets the number of seconds timeout will be postponed on a successful ping/pong + /// + public float ConnectionTimeout + { + get { return m_connectionTimeout; } + set + { + if (value < m_pingInterval) + throw new NetException("Connection timeout cannot be lower than ping interval!"); + m_connectionTimeout = value; + } + } + + /// + /// Enables UPnP support; enabling port forwarding and getting external ip + /// + public bool EnableUPnP + { + get { return m_enableUPnP; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_enableUPnP = value; + } + } + + /// + /// Enables or disables automatic flushing of the send queue. If disabled, you must manully call NetPeer.FlushSendQueue() to flush sent messages to network. + /// + public bool AutoFlushSendQueue + { + get { return m_autoFlushSendQueue; } + set { m_autoFlushSendQueue = value; } + } + + /// + /// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized. + /// + public IPAddress LocalAddress + { + get { return m_localAddress; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_localAddress = value; + } + } + + /// + /// Gets or sets the local broadcast address to use when broadcasting + /// + public IPAddress BroadcastAddress + { + get { return m_broadcastAddress; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_broadcastAddress = value; + } + } + + /// + /// Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized. + /// + public int Port + { + get { return m_port; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_port = value; + } + } + + /// + /// Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. + /// + public int ReceiveBufferSize + { + get { return m_receiveBufferSize; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_receiveBufferSize = value; + } + } + + /// + /// Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. + /// + public int SendBufferSize + { + get { return m_sendBufferSize; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_sendBufferSize = value; + } + } + + /// + /// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient. + /// + public bool AcceptIncomingConnections + { + get { return m_acceptIncomingConnections; } + set { m_acceptIncomingConnections = value; } + } + + /// + /// Gets or sets the number of seconds between handshake attempts + /// + public float ResendHandshakeInterval + { + get { return m_resendHandshakeInterval; } + set { m_resendHandshakeInterval = value; } + } + + /// + /// Gets or sets the maximum number of handshake attempts before failing to connect + /// + public int MaximumHandshakeAttempts + { + get { return m_maximumHandshakeAttempts; } + set + { + if (value < 1) + throw new NetException("MaximumHandshakeAttempts must be at least 1"); + m_maximumHandshakeAttempts = value; + } + } + + /// + /// Gets or sets if the NetPeer should send large messages to try to expand the maximum transmission unit size + /// + public bool AutoExpandMTU + { + get { return m_autoExpandMTU; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_autoExpandMTU = value; + } + } + + /// + /// Gets or sets how often to send large messages to expand MTU if AutoExpandMTU is enabled + /// + public float ExpandMTUFrequency + { + get { return m_expandMTUFrequency; } + set { m_expandMTUFrequency = value; } + } + + /// + /// Gets or sets the number of failed expand mtu attempts to perform before setting final MTU + /// + public int ExpandMTUFailAttempts + { + get { return m_expandMTUFailAttempts; } + set { m_expandMTUFailAttempts = value; } + } + +#if DEBUG + /// + /// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f + /// + public float SimulatedLoss + { + get { return m_loss; } + set { m_loss = value; } + } + + /// + /// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds + /// + public float SimulatedMinimumLatency + { + get { return m_minimumOneWayLatency; } + set { m_minimumOneWayLatency = value; } + } + + /// + /// Gets or sets the simulated added random amount of one way latency for sent packets in seconds + /// + public float SimulatedRandomLatency + { + get { return m_randomOneWayLatency; } + set { m_randomOneWayLatency = value; } + } + + /// + /// Gets the average simulated one way latency in seconds + /// + public float SimulatedAverageLatency + { + get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); } + } + + /// + /// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f + /// + public float SimulatedDuplicatesChance + { + get { return m_duplicates; } + set { m_duplicates = value; } + } +#endif + + /// + /// Creates a memberwise shallow clone of this configuration + /// + public NetPeerConfiguration Clone() + { + NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration; + retval.m_isLocked = false; + return retval; + } + } + + /// + /// Behaviour of unreliable sends above MTU + /// + public enum NetUnreliableSizeBehaviour + { + /// + /// Sending an unreliable message will ignore MTU and send everything in a single packet; this is the new default + /// + IgnoreMTU = 0, + + /// + /// Old behaviour; use normal fragmentation for unreliable messages - if a fragment is dropped, memory for received fragments are never reclaimed! + /// + NormalFragmentation = 1, + + /// + /// Alternate behaviour; just drops unreliable messages above MTU + /// + DropAboveMTU = 2, + } +} diff --git a/Lidgren.Network/NetPeerStatistics.cs b/Lidgren.Network/NetPeerStatistics.cs new file mode 100644 index 0000000..feada4b --- /dev/null +++ b/Lidgren.Network/NetPeerStatistics.cs @@ -0,0 +1,164 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +// Uncomment the line below to get statistics in RELEASE builds +//#define USE_RELEASE_STATISTICS + +using System; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Statistics for a NetPeer instance + /// + public sealed class NetPeerStatistics + { + private readonly NetPeer m_peer; + + internal int m_sentPackets; + internal int m_receivedPackets; + + internal int m_sentMessages; + internal int m_receivedMessages; + internal int m_receivedFragments; + + internal int m_sentBytes; + internal int m_receivedBytes; + + internal long m_bytesAllocated; + + internal NetPeerStatistics(NetPeer peer) + { + m_peer = peer; + Reset(); + } + + internal void Reset() + { + m_sentPackets = 0; + m_receivedPackets = 0; + + m_sentMessages = 0; + m_receivedMessages = 0; + m_receivedFragments = 0; + + m_sentBytes = 0; + m_receivedBytes = 0; + + m_bytesAllocated = 0; + } + + /// + /// Gets the number of sent packets since the NetPeer was initialized + /// + public int SentPackets { get { return m_sentPackets; } } + + /// + /// Gets the number of received packets since the NetPeer was initialized + /// + public int ReceivedPackets { get { return m_receivedPackets; } } + + /// + /// Gets the number of sent messages since the NetPeer was initialized + /// + public int SentMessages { get { return m_sentMessages; } } + + /// + /// Gets the number of received messages since the NetPeer was initialized + /// + public int ReceivedMessages { get { return m_receivedMessages; } } + + /// + /// Gets the number of sent bytes since the NetPeer was initialized + /// + public int SentBytes { get { return m_sentBytes; } } + + /// + /// Gets the number of received bytes since the NetPeer was initialized + /// + public int ReceivedBytes { get { return m_receivedBytes; } } + + /// + /// Gets the number of bytes allocated (and possibly garbage collected) for message storage + /// + public long StorageBytesAllocated { get { return m_bytesAllocated; } } + + /// + /// Gets the number of bytes in the recycled pool + /// + public int BytesInRecyclePool { get { return m_peer.m_storagePoolBytes; } } + +#if USE_RELEASE_STATISTICS + internal void PacketSent(int numBytes, int numMessages) + { + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } +#else + [Conditional("DEBUG")] + internal void PacketSent(int numBytes, int numMessages) + { + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } +#endif + +#if USE_RELEASE_STATISTICS + internal void PacketReceived(int numBytes, int numMessages) + { + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + } +#else + [Conditional("DEBUG")] + internal void PacketReceived(int numBytes, int numMessages, int numFragments) + { + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + m_receivedFragments += numFragments; + } +#endif + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + StringBuilder bdr = new StringBuilder(); + bdr.AppendLine(m_peer.ConnectionsCount.ToString() + " connections"); +#if DEBUG || USE_RELEASE_STATISTICS + bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); + bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets"); +#else + bdr.AppendLine("Sent (n/a) bytes in (n/a) messages in (n/a) packets"); + bdr.AppendLine("Received (n/a) bytes in (n/a) messages in (n/a) packets"); +#endif + bdr.AppendLine("Storage allocated " + m_bytesAllocated + " bytes"); + bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes"); + return bdr.ToString(); + } + } +} diff --git a/Lidgren.Network/NetPeerStatus.cs b/Lidgren.Network/NetPeerStatus.cs new file mode 100644 index 0000000..3be8a1d --- /dev/null +++ b/Lidgren.Network/NetPeerStatus.cs @@ -0,0 +1,49 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; + +namespace Lidgren.Network +{ + /// + /// Status for a NetPeer instance + /// + public enum NetPeerStatus + { + /// + /// NetPeer is not running; socket is not bound + /// + NotRunning = 0, + + /// + /// NetPeer is in the process of starting up + /// + Starting = 1, + + /// + /// NetPeer is bound to socket and listening for packets + /// + Running = 2, + + /// + /// Shutdown has been requested and will be executed shortly + /// + ShutdownRequested = 3, + } +} diff --git a/Lidgren.Network/NetQueue.cs b/Lidgren.Network/NetQueue.cs new file mode 100644 index 0000000..38b94ba --- /dev/null +++ b/Lidgren.Network/NetQueue.cs @@ -0,0 +1,335 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst() + /// + [DebuggerDisplay("Count={Count} Capacity={Capacity}")] + public sealed class NetQueue + { + // Example: + // m_capacity = 8 + // m_size = 6 + // m_head = 4 + // + // [0] item + // [1] item (tail = ((head + size - 1) % capacity) + // [2] + // [3] + // [4] item (head) + // [5] item + // [6] item + // [7] item + // + private T[] m_items; + private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim(); + private int m_size; + private int m_head; + + /// + /// Gets the number of items in the queue + /// + public int Count { get { return m_size; } } + + /// + /// Gets the current capacity for the queue + /// + public int Capacity { get { return m_items.Length; } } + + /// + /// NetQueue constructor + /// + public NetQueue(int initialCapacity) + { + m_items = new T[initialCapacity]; + } + + /// + /// Adds an item last/tail of the queue + /// + public void Enqueue(T item) + { + m_lock.EnterWriteLock(); + try + { + if (m_size == m_items.Length) + SetCapacity(m_items.Length + 8); + + int slot = (m_head + m_size) % m_items.Length; + m_items[slot] = item; + m_size++; + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Adds an item last/tail of the queue + /// + public void Enqueue(IEnumerable items) + { + m_lock.EnterWriteLock(); + try + { + foreach (var item in items) + { + if (m_size == m_items.Length) + SetCapacity(m_items.Length + 8); // @TODO move this out of loop + + int slot = (m_head + m_size) % m_items.Length; + m_items[slot] = item; + m_size++; + } + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Places an item first, at the head of the queue + /// + public void EnqueueFirst(T item) + { + m_lock.EnterWriteLock(); + try + { + if (m_size >= m_items.Length) + SetCapacity(m_items.Length + 8); + + m_head--; + if (m_head < 0) + m_head = m_items.Length - 1; + m_items[m_head] = item; + m_size++; + } + finally + { + m_lock.ExitWriteLock(); + } + } + + // must be called from within a write locked m_lock! + private void SetCapacity(int newCapacity) + { + if (m_size == 0) + { + if (m_size == 0) + { + m_items = new T[newCapacity]; + m_head = 0; + return; + } + } + + T[] newItems = new T[newCapacity]; + + if (m_head + m_size - 1 < m_items.Length) + { + Array.Copy(m_items, m_head, newItems, 0, m_size); + } + else + { + Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head); + Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head))); + } + + m_items = newItems; + m_head = 0; + + } + + /// + /// Gets an item from the head of the queue, or returns default(T) if empty + /// + public bool TryDequeue(out T item) + { + if (m_size == 0) + { + item = default(T); + return false; + } + + m_lock.EnterWriteLock(); + try + { + if (m_size == 0) + { + item = default(T); + return false; + } + + item = m_items[m_head]; + m_items[m_head] = default(T); + + m_head = (m_head + 1) % m_items.Length; + m_size--; + + return true; + } + catch + { +#if DEBUG + throw; +#else + item = default(T); + return false; +#endif + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Gets all items from the head of the queue, or returns number of items popped + /// + public int TryDrain(IList addTo) + { + if (m_size == 0) + return 0; + + m_lock.EnterWriteLock(); + try + { + int added = m_size; + while (m_size > 0) + { + var item = m_items[m_head]; + addTo.Add(item); + + m_items[m_head] = default(T); + m_head = (m_head + 1) % m_items.Length; + m_size--; + } + return added; + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Returns default(T) if queue is empty + /// + public T TryPeek(int offset) + { + if (m_size == 0) + return default(T); + + m_lock.EnterReadLock(); + try + { + if (m_size == 0) + return default(T); + return m_items[(m_head + offset) % m_items.Length]; + } + finally + { + m_lock.ExitReadLock(); + } + } + + /// + /// Determines whether an item is in the queue + /// + public bool Contains(T item) + { + m_lock.EnterReadLock(); + try + { + int ptr = m_head; + for (int i = 0; i < m_size; i++) + { + if (m_items[ptr] == null) + { + if (item == null) + return true; + } + else + { + if (m_items[ptr].Equals(item)) + return true; + } + ptr = (ptr + 1) % m_items.Length; + } + return false; + } + finally + { + m_lock.ExitReadLock(); + } + } + + /// + /// Copies the queue items to a new array + /// + public T[] ToArray() + { + m_lock.EnterReadLock(); + try + { + T[] retval = new T[m_size]; + int ptr = m_head; + for (int i = 0; i < m_size; i++) + { + retval[i] = m_items[ptr++]; + if (ptr >= m_items.Length) + ptr = 0; + } + return retval; + } + finally + { + m_lock.ExitReadLock(); + } + } + + /// + /// Removes all objects from the queue + /// + public void Clear() + { + m_lock.EnterWriteLock(); + try + { + for (int i = 0; i < m_items.Length; i++) + m_items[i] = default(T); + m_head = 0; + m_size = 0; + } + finally + { + m_lock.ExitWriteLock(); + } + } + } +} diff --git a/Lidgren.Network/NetRandom.Implementations.cs b/Lidgren.Network/NetRandom.Implementations.cs new file mode 100644 index 0000000..1a7bff3 --- /dev/null +++ b/Lidgren.Network/NetRandom.Implementations.cs @@ -0,0 +1,281 @@ +using System; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + /// + /// Multiply With Carry random + /// + public class MWCRandom : NetRandom + { + /// + /// Get global instance of MWCRandom + /// + public static new readonly MWCRandom Instance = new MWCRandom(); + + private uint m_w, m_z; + + /// + /// Constructor with randomized seed + /// + public MWCRandom() + { + Initialize(NetRandomSeed.GetUInt64()); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + m_w = seed; + m_z = seed * 16777619; + } + + /// + /// (Re)initialize this instance with provided 64 bit seed + /// + [CLSCompliant(false)] + public void Initialize(ulong seed) + { + m_w = (uint)seed; + m_z = (uint)(seed >> 32); + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + m_z = 36969 * (m_z & 65535) + (m_z >> 16); + m_w = 18000 * (m_w & 65535) + (m_w >> 16); + return ((m_z << 16) + m_w); + } + } + + /// + /// Xor Shift based random + /// + public sealed class XorShiftRandom : NetRandom + { + /// + /// Get global instance of XorShiftRandom + /// + public static new readonly XorShiftRandom Instance = new XorShiftRandom(); + + private const uint c_x = 123456789; + private const uint c_y = 362436069; + private const uint c_z = 521288629; + private const uint c_w = 88675123; + + private uint m_x, m_y, m_z, m_w; + + /// + /// Constructor with randomized seed + /// + public XorShiftRandom() + { + Initialize(NetRandomSeed.GetUInt64()); + } + + /// + /// Constructor with provided 64 bit seed + /// + [CLSCompliant(false)] + public XorShiftRandom(ulong seed) + { + Initialize(seed); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + m_x = (uint)seed; + m_y = c_y; + m_z = c_z; + m_w = c_w; + } + + /// + /// (Re)initialize this instance with provided 64 bit seed + /// + [CLSCompliant(false)] + public void Initialize(ulong seed) + { + m_x = (uint)seed; + m_y = c_y; + m_z = (uint)(seed << 32); + m_w = c_w; + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + return (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))); + } + } + + /// + /// Mersenne Twister based random + /// + public sealed class MersenneTwisterRandom : NetRandom + { + /// + /// Get global instance of MersenneTwisterRandom + /// + public static new readonly MersenneTwisterRandom Instance = new MersenneTwisterRandom(); + + private const int N = 624; + private const int M = 397; + private const uint MATRIX_A = 0x9908b0dfU; + private const uint UPPER_MASK = 0x80000000U; + private const uint LOWER_MASK = 0x7fffffffU; + private const uint TEMPER1 = 0x9d2c5680U; + private const uint TEMPER2 = 0xefc60000U; + private const int TEMPER3 = 11; + private const int TEMPER4 = 7; + private const int TEMPER5 = 15; + private const int TEMPER6 = 18; + + private UInt32[] mt; + private int mti; + private UInt32[] mag01; + + private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0); + + /// + /// Constructor with randomized seed + /// + public MersenneTwisterRandom() + { + Initialize(NetRandomSeed.GetUInt32()); + } + + /// + /// Constructor with provided 32 bit seed + /// + [CLSCompliant(false)] + public MersenneTwisterRandom(uint seed) + { + Initialize(seed); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + mt = new UInt32[N]; + mti = N + 1; + mag01 = new UInt32[] { 0x0U, MATRIX_A }; + mt[0] = seed; + for (int i = 1; i < N; i++) + mt[i] = (UInt32)(1812433253 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i); + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + UInt32 y; + if (mti >= N) + { + GenRandAll(); + mti = 0; + } + y = mt[mti++]; + y ^= (y >> TEMPER3); + y ^= (y << TEMPER4) & TEMPER1; + y ^= (y << TEMPER5) & TEMPER2; + y ^= (y >> TEMPER6); + return y; + } + + private void GenRandAll() + { + int kk = 1; + UInt32 y; + UInt32 p; + y = mt[0] & UPPER_MASK; + do + { + p = mt[kk]; + mt[kk - 1] = mt[kk + (M - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1]; + y = p & UPPER_MASK; + } while (++kk < N - M + 1); + do + { + p = mt[kk]; + mt[kk - 1] = mt[kk + (M - N - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1]; + y = p & UPPER_MASK; + } while (++kk < N); + p = mt[0]; + mt[N - 1] = mt[M - 1] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1]; + } + } + + /// + /// RNGCryptoServiceProvider based random; very slow but cryptographically safe + /// + public class CryptoRandom : NetRandom + { + /// + /// Global instance of CryptoRandom + /// + public static new readonly CryptoRandom Instance = new CryptoRandom(); + + private RandomNumberGenerator m_rnd = new RNGCryptoServiceProvider(); + + /// + /// Seed in CryptoRandom does not create deterministic sequences + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + byte[] tmp = new byte[seed % 16]; + m_rnd.GetBytes(tmp); // just prime it + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + var bytes = new byte[4]; + m_rnd.GetBytes(bytes); + return (uint)bytes[0] | (((uint)bytes[1]) << 8) | (((uint)bytes[2]) << 16) | (((uint)bytes[3]) << 24); + } + + /// + /// Fill the specified buffer with random values + /// + public override void NextBytes(byte[] buffer) + { + m_rnd.GetBytes(buffer); + } + + /// + /// Fills all bytes from offset to offset + length in buffer with random values + /// + public override void NextBytes(byte[] buffer, int offset, int length) + { + var bytes = new byte[length]; + m_rnd.GetBytes(bytes); + Array.Copy(bytes, 0, buffer, offset, length); + } + } +} diff --git a/Lidgren.Network/NetRandom.cs b/Lidgren.Network/NetRandom.cs new file mode 100644 index 0000000..155b21c --- /dev/null +++ b/Lidgren.Network/NetRandom.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// NetRandom base class + /// + public abstract class NetRandom : Random + { + /// + /// Get global instance of NetRandom (uses MWCRandom) + /// + public static NetRandom Instance = new MWCRandom(); + + private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0); + + /// + /// Constructor with randomized seed + /// + public NetRandom() + { + Initialize(NetRandomSeed.GetUInt32()); + } + + /// + /// Constructor with provided 32 bit seed + /// + public NetRandom(int seed) + { + Initialize((uint)seed); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public virtual void Initialize(uint seed) + { + // should be abstract, but non-CLS compliant methods can't be abstract! + throw new NotImplementedException("Implement this in inherited classes"); + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public virtual uint NextUInt32() + { + // should be abstract, but non-CLS compliant methods can't be abstract! + throw new NotImplementedException("Implement this in inherited classes"); + } + + /// + /// Generates a random value that is greater or equal than 0 and less than Int32.MaxValue + /// + public override int Next() + { + var retval = (int)(0x7FFFFFFF & NextUInt32()); + if (retval == 0x7FFFFFFF) + return NextInt32(); + return retval; + } + + /// + /// Generates a random value greater or equal than 0 and less or equal than Int32.MaxValue (inclusively) + /// + public int NextInt32() + { + return (int)(0x7FFFFFFF & NextUInt32()); + } + + /// + /// Returns random value larger or equal to 0.0 and less than 1.0 + /// + public override double NextDouble() + { + return c_realUnitInt * NextInt32(); + } + + /// + /// Returns random value is greater or equal than 0.0 and less than 1.0 + /// + protected override double Sample() + { + return c_realUnitInt * NextInt32(); + } + + /// + /// Returns random value is greater or equal than 0.0f and less than 1.0f + /// + public float NextSingle() + { + var retval = (float)(c_realUnitInt * NextInt32()); + if (retval == 1.0f) + return NextSingle(); + return retval; + } + + /// + /// Returns a random value is greater or equal than 0 and less than maxValue + /// + public override int Next(int maxValue) + { + return (int)(NextDouble() * maxValue); + } + + /// + /// Returns a random value is greater or equal than minValue and less than maxValue + /// + public override int Next(int minValue, int maxValue) + { + return minValue + (int)(NextDouble() * (double)(maxValue - minValue)); + } + + /// + /// Generates a random value between UInt64.MinValue to UInt64.MaxValue + /// + [CLSCompliant(false)] + public ulong NextUInt64() + { + ulong retval = NextUInt32(); + retval |= NextUInt32() << 32; + return retval; + } + + private uint m_boolValues; + private int m_nextBoolIndex; + + /// + /// Returns true or false, randomly + /// + public bool NextBool() + { + if (m_nextBoolIndex >= 32) + { + m_boolValues = NextUInt32(); + m_nextBoolIndex = 1; + } + + var retval = ((m_boolValues >> m_nextBoolIndex) & 1) == 1; + m_nextBoolIndex++; + return retval; + } + + + /// + /// Fills all bytes from offset to offset + length in buffer with random values + /// + public virtual void NextBytes(byte[] buffer, int offset, int length) + { + int full = length / 4; + int ptr = offset; + for (int i = 0; i < full; i++) + { + uint r = NextUInt32(); + buffer[ptr++] = (byte)r; + buffer[ptr++] = (byte)(r >> 8); + buffer[ptr++] = (byte)(r >> 16); + buffer[ptr++] = (byte)(r >> 24); + } + + int rest = length - (full * 4); + for (int i = 0; i < rest; i++) + buffer[ptr++] = (byte)NextUInt32(); + } + + /// + /// Fill the specified buffer with random values + /// + public override void NextBytes(byte[] buffer) + { + NextBytes(buffer, 0, buffer.Length); + } + } +} diff --git a/Lidgren.Network/NetRandomSeed.cs b/Lidgren.Network/NetRandomSeed.cs new file mode 100644 index 0000000..0994d2d --- /dev/null +++ b/Lidgren.Network/NetRandomSeed.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Class for generating random seeds + /// + public static class NetRandomSeed + { + private static int m_seedIncrement = -1640531527; + + /// + /// Generates a 32 bit random seed + /// + [CLSCompliant(false)] + public static uint GetUInt32() + { + ulong seed = GetUInt64(); + uint low = (uint)seed; + uint high = (uint)(seed >> 32); + return low ^ high; + } + + /// + /// Generates a 64 bit random seed + /// + [CLSCompliant(false)] + public static ulong GetUInt64() + { +#if !__ANDROID__ && !IOS && !UNITY_WEBPLAYER && !UNITY_ANDROID && !UNITY_IPHONE + ulong seed = (ulong)System.Diagnostics.Stopwatch.GetTimestamp(); + seed ^= (ulong)Environment.WorkingSet; + ulong s2 = (ulong)Interlocked.Increment(ref m_seedIncrement); + s2 |= (((ulong)Guid.NewGuid().GetHashCode()) << 32); + seed ^= s2; +#else + ulong seed = (ulong)Environment.TickCount; + seed |= (((ulong)(new object().GetHashCode())) << 32); + ulong s2 = (ulong)Guid.NewGuid().GetHashCode(); + s2 |= (((ulong)Interlocked.Increment(ref m_seedIncrement)) << 32); + seed ^= s2; +#endif + return seed; + } + } +} diff --git a/Lidgren.Network/NetReceiverChannelBase.cs b/Lidgren.Network/NetReceiverChannelBase.cs new file mode 100644 index 0000000..e3f634e --- /dev/null +++ b/Lidgren.Network/NetReceiverChannelBase.cs @@ -0,0 +1,18 @@ +using System; + +namespace Lidgren.Network +{ + internal abstract class NetReceiverChannelBase + { + internal NetPeer m_peer; + internal NetConnection m_connection; + + public NetReceiverChannelBase(NetConnection connection) + { + m_connection = connection; + m_peer = connection.m_peer; + } + + internal abstract void ReceiveMessage(NetIncomingMessage msg); + } +} diff --git a/Lidgren.Network/NetReliableOrderedReceiver.cs b/Lidgren.Network/NetReliableOrderedReceiver.cs new file mode 100644 index 0000000..2991448 --- /dev/null +++ b/Lidgren.Network/NetReliableOrderedReceiver.cs @@ -0,0 +1,87 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetReliableOrderedReceiver : NetReceiverChannelBase + { + private int m_windowStart; + private int m_windowSize; + private NetBitVector m_earlyReceived; + internal NetIncomingMessage[] m_withheldMessages; + + public NetReliableOrderedReceiver(NetConnection connection, int windowSize) + : base(connection) + { + m_windowSize = windowSize; + m_withheldMessages = new NetIncomingMessage[windowSize]; + m_earlyReceived = new NetBitVector(windowSize); + } + + private void AdvanceWindow() + { + m_earlyReceived.Set(m_windowStart % m_windowSize, false); + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + + internal override void ReceiveMessage(NetIncomingMessage message) + { + int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart); + + // ack no matter what + m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber); + + if (relate == 0) + { + // Log("Received message #" + message.SequenceNumber + " right on time"); + + // + // excellent, right on time + // + //m_peer.LogVerbose("Received RIGHT-ON-TIME " + message); + + AdvanceWindow(); + m_peer.ReleaseMessage(message); + + // release withheld messages + int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers; + + while (m_earlyReceived[nextSeqNr % m_windowSize]) + { + message = m_withheldMessages[nextSeqNr % m_windowSize]; + NetException.Assert(message != null); + + // remove it from withheld messages + m_withheldMessages[nextSeqNr % m_windowSize] = null; + + m_peer.LogVerbose("Releasing withheld message #" + message); + + m_peer.ReleaseMessage(message); + + AdvanceWindow(); + nextSeqNr++; + } + + return; + } + + if (relate < 0) + { + m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE"); + // duplicate + return; + } + + // relate > 0 = early message + if (relate > m_windowSize) + { + // too early message! + m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart); + return; + } + + m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true); + m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart); + m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message; + } + } +} diff --git a/Lidgren.Network/NetReliableSenderChannel.cs b/Lidgren.Network/NetReliableSenderChannel.cs new file mode 100644 index 0000000..792e1da --- /dev/null +++ b/Lidgren.Network/NetReliableSenderChannel.cs @@ -0,0 +1,257 @@ +using System; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Sender part of Selective repeat ARQ for a particular NetChannel + /// + internal sealed class NetReliableSenderChannel : NetSenderChannelBase + { + private NetConnection m_connection; + private int m_windowStart; + private int m_windowSize; + private int m_sendStart; + + private NetBitVector m_receivedAcks; + internal NetStoredReliableMessage[] m_storedMessages; + + internal float m_resendDelay; + + internal override int WindowSize { get { return m_windowSize; } } + + internal NetReliableSenderChannel(NetConnection connection, int windowSize) + { + m_connection = connection; + m_windowSize = windowSize; + m_windowStart = 0; + m_sendStart = 0; + m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers); + m_storedMessages = new NetStoredReliableMessage[m_windowSize]; + m_queuedSends = new NetQueue(8); + m_resendDelay = m_connection.GetResendDelay(); + } + + internal override int GetAllowedSends() + { + int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers; + NetException.Assert(retval >= 0 && retval <= m_windowSize); + return retval; + } + + internal override void Reset() + { + m_receivedAcks.Clear(); + for (int i = 0; i < m_storedMessages.Length; i++) + m_storedMessages[i].Reset(); + m_queuedSends.Clear(); + m_windowStart = 0; + m_sendStart = 0; + } + + internal override NetSendResult Enqueue(NetOutgoingMessage message) + { + m_queuedSends.Enqueue(message); + if (m_queuedSends.Count <= GetAllowedSends()) + return NetSendResult.Sent; + return NetSendResult.Queued; + } + + // call this regularely + internal override void SendQueuedMessages(float now) + { + // + // resends + // + for (int i = 0; i < m_storedMessages.Length; i++) + { + NetOutgoingMessage om = m_storedMessages[i].Message; + if (om == null) + continue; + + float t = m_storedMessages[i].LastSent; + if (t > 0 && (now - t) > m_resendDelay) + { + // deduce sequence number + /* + int startSlot = m_windowStart % m_windowSize; + int seqNr = m_windowStart; + while (startSlot != i) + { + startSlot--; + if (startSlot < 0) + startSlot = m_windowSize - 1; + seqNr--; + } + */ + + //m_connection.m_peer.LogVerbose("Resending due to delay #" + m_storedMessages[i].SequenceNumber + " " + om.ToString()); + m_connection.m_statistics.MessageResent(MessageResendReason.Delay); + + m_connection.QueueSendMessage(om, m_storedMessages[i].SequenceNumber); + + m_storedMessages[i].LastSent = now; + m_storedMessages[i].NumSent++; + } + } + + int num = GetAllowedSends(); + if (num < 1) + return; + + // queued sends + while (m_queuedSends.Count > 0 && num > 0) + { + NetOutgoingMessage om; + if (m_queuedSends.TryDequeue(out om)) + ExecuteSend(now, om); + num--; + NetException.Assert(num == GetAllowedSends()); + } + } + + private void ExecuteSend(float now, NetOutgoingMessage message) + { + int seqNr = m_sendStart; + m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers; + + m_connection.QueueSendMessage(message, seqNr); + + int storeIndex = seqNr % m_windowSize; + NetException.Assert(m_storedMessages[storeIndex].Message == null); + + m_storedMessages[storeIndex].NumSent++; + m_storedMessages[storeIndex].Message = message; + m_storedMessages[storeIndex].LastSent = now; + m_storedMessages[storeIndex].SequenceNumber = seqNr; + + return; + } + + private void DestoreMessage(int storeIndex) + { + NetOutgoingMessage storedMessage = m_storedMessages[storeIndex].Message; +#if DEBUG + if (storedMessage == null) + throw new NetException("m_storedMessages[" + storeIndex + "].Message is null; sent " + m_storedMessages[storeIndex].NumSent + " times, last time " + (NetTime.Now - m_storedMessages[storeIndex].LastSent) + " seconds ago"); +#else + if (storedMessage != null) + { +#endif + if (Interlocked.Decrement(ref storedMessage.m_recyclingCount) <= 0) + m_connection.m_peer.Recycle(storedMessage); + +#if !DEBUG + } +#endif + m_storedMessages[storeIndex] = new NetStoredReliableMessage(); + } + + // remoteWindowStart is remote expected sequence number; everything below this has arrived properly + // seqNr is the actual nr received + internal override void ReceiveAcknowledge(float now, int seqNr) + { + // late (dupe), on time or early ack? + int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart); + + if (relate < 0) + { + //m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr); + return; // late/duplicate ack + } + + if (relate == 0) + { + //m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr); + + // ack arrived right on time + NetException.Assert(seqNr == m_windowStart); + + m_receivedAcks[m_windowStart] = false; + DestoreMessage(m_windowStart % m_windowSize); + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + + // advance window if we already have early acks + while (m_receivedAcks.Get(m_windowStart)) + { + //m_connection.m_peer.LogDebug("Using early ack for #" + m_windowStart + "..."); + m_receivedAcks[m_windowStart] = false; + DestoreMessage(m_windowStart % m_windowSize); + + NetException.Assert(m_storedMessages[m_windowStart % m_windowSize].Message == null); // should already be destored + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + //m_connection.m_peer.LogDebug("Advancing window to #" + m_windowStart); + } + + return; + } + + // + // early ack... (if it has been sent!) + // + // If it has been sent either the m_windowStart message was lost + // ... or the ack for that message was lost + // + + //m_connection.m_peer.LogDebug("Received early ack for #" + seqNr); + + int sendRelate = NetUtility.RelativeSequenceNumber(seqNr, m_sendStart); + if (sendRelate <= 0) + { + // yes, we've sent this message - it's an early (but valid) ack + if (m_receivedAcks[seqNr]) + { + // we've already destored/been acked for this message + } + else + { + m_receivedAcks[seqNr] = true; + } + } + else if (sendRelate > 0) + { + // uh... we haven't sent this message yet? Weird, dupe or error... + NetException.Assert(false, "Got ack for message not yet sent?"); + return; + } + + // Ok, lets resend all missing acks + int rnr = seqNr; + do + { + rnr--; + if (rnr < 0) + rnr = NetConstants.NumSequenceNumbers - 1; + + if (m_receivedAcks[rnr]) + { + // m_connection.m_peer.LogDebug("Not resending #" + rnr + " (since we got ack)"); + } + else + { + int slot = rnr % m_windowSize; + NetException.Assert(m_storedMessages[slot].Message != null); + if (m_storedMessages[slot].NumSent == 1) + { + // just sent once; resend immediately since we found gap in ack sequence + NetOutgoingMessage rmsg = m_storedMessages[slot].Message; + //m_connection.m_peer.LogVerbose("Resending #" + rnr + " (" + rmsg + ")"); + + if (now - m_storedMessages[slot].LastSent < (m_resendDelay * 0.35f)) + { + // already resent recently + } + else + { + m_storedMessages[slot].LastSent = now; + m_storedMessages[slot].NumSent++; + m_connection.m_statistics.MessageResent(MessageResendReason.HoleInSequence); + m_connection.QueueSendMessage(rmsg, rnr); + } + } + } + + } while (rnr != m_windowStart); + } + } +} diff --git a/Lidgren.Network/NetReliableSequencedReceiver.cs b/Lidgren.Network/NetReliableSequencedReceiver.cs new file mode 100644 index 0000000..0068dfb --- /dev/null +++ b/Lidgren.Network/NetReliableSequencedReceiver.cs @@ -0,0 +1,63 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetReliableSequencedReceiver : NetReceiverChannelBase + { + private int m_windowStart; + private int m_windowSize; + + public NetReliableSequencedReceiver(NetConnection connection, int windowSize) + : base(connection) + { + m_windowSize = windowSize; + } + + private void AdvanceWindow() + { + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + + internal override void ReceiveMessage(NetIncomingMessage message) + { + int nr = message.m_sequenceNumber; + + int relate = NetUtility.RelativeSequenceNumber(nr, m_windowStart); + + // ack no matter what + m_connection.QueueAck(message.m_receivedMessageType, nr); + + if (relate == 0) + { + // Log("Received message #" + message.SequenceNumber + " right on time"); + + // + // excellent, right on time + // + + AdvanceWindow(); + m_peer.ReleaseMessage(message); + return; + } + + if (relate < 0) + { + m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING LATE or DUPE"); + return; + } + + // relate > 0 = early message + if (relate > m_windowSize) + { + // too early message! + m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart); + return; + } + + // ok + m_windowStart = (m_windowStart + relate) % NetConstants.NumSequenceNumbers; + m_peer.ReleaseMessage(message); + return; + } + } +} diff --git a/Lidgren.Network/NetReliableUnorderedReceiver.cs b/Lidgren.Network/NetReliableUnorderedReceiver.cs new file mode 100644 index 0000000..364e6d8 --- /dev/null +++ b/Lidgren.Network/NetReliableUnorderedReceiver.cs @@ -0,0 +1,87 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetReliableUnorderedReceiver : NetReceiverChannelBase + { + private int m_windowStart; + private int m_windowSize; + private NetBitVector m_earlyReceived; + + public NetReliableUnorderedReceiver(NetConnection connection, int windowSize) + : base(connection) + { + m_windowSize = windowSize; + m_earlyReceived = new NetBitVector(windowSize); + } + + private void AdvanceWindow() + { + m_earlyReceived.Set(m_windowStart % m_windowSize, false); + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + + internal override void ReceiveMessage(NetIncomingMessage message) + { + int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart); + + // ack no matter what + m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber); + + if (relate == 0) + { + // Log("Received message #" + message.SequenceNumber + " right on time"); + + // + // excellent, right on time + // + //m_peer.LogVerbose("Received RIGHT-ON-TIME " + message); + + AdvanceWindow(); + m_peer.ReleaseMessage(message); + + // release withheld messages + int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers; + + while (m_earlyReceived[nextSeqNr % m_windowSize]) + { + //message = m_withheldMessages[nextSeqNr % m_windowSize]; + //NetException.Assert(message != null); + + // remove it from withheld messages + //m_withheldMessages[nextSeqNr % m_windowSize] = null; + + //m_peer.LogVerbose("Releasing withheld message #" + message); + + //m_peer.ReleaseMessage(message); + + AdvanceWindow(); + nextSeqNr++; + } + + return; + } + + if (relate < 0) + { + // duplicate + m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE"); + return; + } + + // relate > 0 = early message + if (relate > m_windowSize) + { + // too early message! + m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart); + return; + } + + m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true); + //m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart); + //m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message; + + m_peer.ReleaseMessage(message); + } + } +} diff --git a/Lidgren.Network/NetSRP.cs b/Lidgren.Network/NetSRP.cs new file mode 100644 index 0000000..bb8b9f7 --- /dev/null +++ b/Lidgren.Network/NetSRP.cs @@ -0,0 +1,204 @@ +#define USE_SHA256 + +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Helper methods for implementing SRP authentication + /// + public static class NetSRP + { + private static readonly NetBigInteger N = new NetBigInteger("0115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3", 16); + private static readonly NetBigInteger g = NetBigInteger.Two; + private static readonly NetBigInteger k = ComputeMultiplier(); + + private static HashAlgorithm GetHashAlgorithm() + { +#if USE_SHA256 + // this does not seem to work as of yet + return SHA256.Create(); +#else + return SHA1.Create(); +#endif + } + + /// + /// Compute multiplier (k) + /// + private static NetBigInteger ComputeMultiplier() + { + string one = NetUtility.ToHexString(N.ToByteArrayUnsigned()); + string two = NetUtility.ToHexString(g.ToByteArrayUnsigned()); + + string ccstr = one + two.PadLeft(one.Length, '0'); + byte[] cc = NetUtility.ToByteArray(ccstr); + + var sha = GetHashAlgorithm(); + var ccHashed = sha.ComputeHash(cc); + + return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16); + } + + /// + /// Create 16 bytes of random salt + /// + public static byte[] CreateRandomSalt() + { + byte[] retval = new byte[16]; + CryptoRandom.Instance.NextBytes(retval); + return retval; + } + + /// + /// Create 32 bytes of random ephemeral value + /// + public static byte[] CreateRandomEphemeral() + { + byte[] retval = new byte[32]; + CryptoRandom.Instance.NextBytes(retval); + return retval; + } + + /// + /// Computer private key (x) + /// + public static byte[] ComputePrivateKey(string username, string password, byte[] salt) + { + var sha = GetHashAlgorithm(); + + byte[] tmp = Encoding.UTF8.GetBytes(username + ":" + password); + byte[] innerHash = sha.ComputeHash(tmp); + + byte[] total = new byte[innerHash.Length + salt.Length]; + Buffer.BlockCopy(salt, 0, total, 0, salt.Length); + Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length); + + // x ie. H(salt || H(username || ":" || password)) + return new NetBigInteger(NetUtility.ToHexString(sha.ComputeHash(total)), 16).ToByteArrayUnsigned(); + } + + /// + /// Creates a verifier that the server can later use to authenticate users later on (v) + /// + public static byte[] ComputeServerVerifier(byte[] privateKey) + { + NetBigInteger x = new NetBigInteger(NetUtility.ToHexString(privateKey), 16); + + // Verifier (v) = g^x (mod N) + var serverVerifier = g.ModPow(x, N); + + return serverVerifier.ToByteArrayUnsigned(); + } + + /// + /// SHA hash data + /// + public static byte[] Hash(byte[] data) + { + var sha = GetHashAlgorithm(); + return sha.ComputeHash(data); + } + + /// + /// Compute client public ephemeral value (A) + /// + public static byte[] ComputeClientEphemeral(byte[] clientPrivateEphemeral) // a + { + // A= g^a (mod N) + NetBigInteger a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16); + NetBigInteger retval = g.ModPow(a, N); + + return retval.ToByteArrayUnsigned(); + } + + /// + /// Compute server ephemeral value (B) + /// + public static byte[] ComputeServerEphemeral(byte[] serverPrivateEphemeral, byte[] verifier) // b + { + var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16); + var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16); + + // B = kv + g^b (mod N) + var bb = g.ModPow(b, N); + var kv = v.Multiply(k); + var B = (kv.Add(bb)).Mod(N); + + return B.ToByteArrayUnsigned(); + } + + /// + /// Compute intermediate value (u) + /// + public static byte[] ComputeU(byte[] clientPublicEphemeral, byte[] serverPublicEphemeral) + { + // u = SHA-1(A || B) + string one = NetUtility.ToHexString(clientPublicEphemeral); + string two = NetUtility.ToHexString(serverPublicEphemeral); + + int len = 66; // Math.Max(one.Length, two.Length); + string ccstr = one.PadLeft(len, '0') + two.PadLeft(len, '0'); + + byte[] cc = NetUtility.ToByteArray(ccstr); + + var sha = GetHashAlgorithm(); + var ccHashed = sha.ComputeHash(cc); + + return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16).ToByteArrayUnsigned(); + } + + /// + /// Computes the server session value + /// + public static byte[] ComputeServerSessionValue(byte[] clientPublicEphemeral, byte[] verifier, byte[] udata, byte[] serverPrivateEphemeral) + { + // S = (Av^u) ^ b (mod N) + var A = new NetBigInteger(NetUtility.ToHexString(clientPublicEphemeral), 16); + var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16); + var u = new NetBigInteger(NetUtility.ToHexString(udata), 16); + var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16); + + NetBigInteger retval = v.ModPow(u, N).Multiply(A).Mod(N).ModPow(b, N).Mod(N); + + return retval.ToByteArrayUnsigned(); + } + + /// + /// Computes the client session value + /// + public static byte[] ComputeClientSessionValue(byte[] serverPublicEphemeral, byte[] xdata, byte[] udata, byte[] clientPrivateEphemeral) + { + // (B - kg^x) ^ (a + ux) (mod N) + var B = new NetBigInteger(NetUtility.ToHexString(serverPublicEphemeral), 16); + var x = new NetBigInteger(NetUtility.ToHexString(xdata), 16); + var u = new NetBigInteger(NetUtility.ToHexString(udata), 16); + var a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16); + + var bx = g.ModPow(x, N); + var btmp = B.Add(N.Multiply(k)).Subtract(bx.Multiply(k)).Mod(N); + return btmp.ModPow(x.Multiply(u).Add(a), N).ToByteArrayUnsigned(); + } + + /// + /// Create XTEA symmetrical encryption object from sessionValue + /// + public static NetXtea CreateEncryption(NetPeer peer, byte[] sessionValue) + { + var sha = GetHashAlgorithm(); + var hash = sha.ComputeHash(sessionValue); + + var key = new byte[16]; + for(int i=0;i<16;i++) + { + key[i] = hash[i]; + for (int j = 1; j < hash.Length / 16; j++) + key[i] ^= hash[i + (j * 16)]; + } + + return new NetXtea(peer, key); + } + } +} diff --git a/Lidgren.Network/NetSendResult.cs b/Lidgren.Network/NetSendResult.cs new file mode 100644 index 0000000..9ceb5ba --- /dev/null +++ b/Lidgren.Network/NetSendResult.cs @@ -0,0 +1,30 @@ +using System; + +namespace Lidgren.Network +{ + /// + /// Result of a SendMessage call + /// + public enum NetSendResult + { + /// + /// Message failed to enqueue because there is no connection + /// + FailedNotConnected = 0, + + /// + /// Message was immediately sent + /// + Sent = 1, + + /// + /// Message was queued for delivery + /// + Queued = 2, + + /// + /// Message was dropped immediately since too many message were queued + /// + Dropped = 3 + } +} diff --git a/Lidgren.Network/NetSenderChannelBase.cs b/Lidgren.Network/NetSenderChannelBase.cs new file mode 100644 index 0000000..e9562b6 --- /dev/null +++ b/Lidgren.Network/NetSenderChannelBase.cs @@ -0,0 +1,19 @@ +using System; + +namespace Lidgren.Network +{ + internal abstract class NetSenderChannelBase + { + // access this directly to queue things in this channel + internal NetQueue m_queuedSends; + + internal abstract int WindowSize { get; } + + internal abstract int GetAllowedSends(); + + internal abstract NetSendResult Enqueue(NetOutgoingMessage message); + internal abstract void SendQueuedMessages(float now); + internal abstract void Reset(); + internal abstract void ReceiveAcknowledge(float now, int sequenceNumber); + } +} diff --git a/Lidgren.Network/NetServer.cs b/Lidgren.Network/NetServer.cs new file mode 100644 index 0000000..22e2eff --- /dev/null +++ b/Lidgren.Network/NetServer.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + /// + /// Specialized version of NetPeer used for "server" peers + /// + public class NetServer : NetPeer + { + /// + /// NetServer constructor + /// + public NetServer(NetPeerConfiguration config) + : base(config) + { + config.AcceptIncomingConnections = true; + } + + /// + /// Send a message to all connections + /// + /// The message to send + /// How to deliver the message + public void SendToAll(NetOutgoingMessage msg, NetDeliveryMethod method) + { + var all = this.Connections; + if (all.Count <= 0) + return; + + SendMessage(msg, all, method, 0); + } + + /// + /// Send a message to all connections except one + /// + /// The message to send + /// How to deliver the message + /// Don't send to this particular connection + /// Which sequence channel to use for the message + public void SendToAll(NetOutgoingMessage msg, NetConnection except, NetDeliveryMethod method, int sequenceChannel) + { + var all = this.Connections; + if (all.Count <= 0) + return; + + if (except == null) + { + SendMessage(msg, all, method, sequenceChannel); + return; + } + + List recipients = new List(all.Count - 1); + foreach (var conn in all) + if (conn != except) + recipients.Add(conn); + + if (recipients.Count > 0) + SendMessage(msg, recipients, method, sequenceChannel); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetServer " + ConnectionsCount + " connections]"; + } + } +} diff --git a/Lidgren.Network/NetStoredReliableMessage.cs b/Lidgren.Network/NetStoredReliableMessage.cs new file mode 100644 index 0000000..bb2b2fc --- /dev/null +++ b/Lidgren.Network/NetStoredReliableMessage.cs @@ -0,0 +1,19 @@ +using System; + +namespace Lidgren.Network +{ + internal struct NetStoredReliableMessage + { + public int NumSent; + public float LastSent; + public NetOutgoingMessage Message; + public int SequenceNumber; + + public void Reset() + { + NumSent = 0; + LastSent = 0; + Message = null; + } + } +} diff --git a/Lidgren.Network/NetTime.cs b/Lidgren.Network/NetTime.cs new file mode 100644 index 0000000..e8d27ba --- /dev/null +++ b/Lidgren.Network/NetTime.cs @@ -0,0 +1,61 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +#define IS_STOPWATCH_AVAILABLE + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Time service + /// + public static class NetTime + { +#if IS_STOPWATCH_AVAILABLE + private static readonly long s_timeInitialized = Stopwatch.GetTimestamp(); + private static readonly double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency; + + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } } +#else + private static readonly uint s_timeInitialized = (uint)Environment.TickCount; + + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } } +#endif + + /// + /// Given seconds it will output a human friendly readable string (milliseconds if less than 60 seconds) + /// + public static string ToReadable(double seconds) + { + if (seconds > 60) + return TimeSpan.FromSeconds(seconds).ToString(); + return (seconds * 1000.0).ToString("N2") + " ms"; + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetTuple.cs b/Lidgren.Network/NetTuple.cs new file mode 100644 index 0000000..fe3c276 --- /dev/null +++ b/Lidgren.Network/NetTuple.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + // replace with BCL 4.0 Tuple<> when appropriate + internal struct NetTuple + { + public A Item1; + public B Item2; + + public NetTuple(A item1, B item2) + { + Item1 = item1; + Item2 = item2; + } + } +} diff --git a/Lidgren.Network/NetUPnP.cs b/Lidgren.Network/NetUPnP.cs new file mode 100644 index 0000000..2670dd7 --- /dev/null +++ b/Lidgren.Network/NetUPnP.cs @@ -0,0 +1,266 @@ +using System; +using System.IO; +using System.Xml; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Status of the UPnP capabilities + /// + public enum UPnPStatus + { + /// + /// Still discovering UPnP capabilities + /// + Discovering, + + /// + /// UPnP is not available + /// + NotAvailable, + + /// + /// UPnP is available and ready to use + /// + Available + } + + /// + /// UPnP support class + /// + public class NetUPnP + { + private const int c_discoveryTimeOutMillis = 1000; + + private string m_serviceUrl; + private string m_serviceName = ""; + private NetPeer m_peer; + private ManualResetEvent m_discoveryComplete = new ManualResetEvent(false); + + internal float m_discoveryResponseDeadline; + + private UPnPStatus m_status; + + /// + /// Status of the UPnP capabilities of this NetPeer + /// + public UPnPStatus Status { get { return m_status; } } + + /// + /// NetUPnP constructor + /// + public NetUPnP(NetPeer peer) + { + m_peer = peer; + m_discoveryResponseDeadline = float.MinValue; + } + + internal void Discover(NetPeer peer) + { + string str = +"M-SEARCH * HTTP/1.1\r\n" + +"HOST: 239.255.255.250:1900\r\n" + +"ST:upnp:rootdevice\r\n" + +"MAN:\"ssdp:discover\"\r\n" + +"MX:3\r\n\r\n"; + + m_status = UPnPStatus.Discovering; + + byte[] arr = System.Text.Encoding.UTF8.GetBytes(str); + + m_peer.LogDebug("Attempting UPnP discovery"); + peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + peer.RawSend(arr, 0, arr.Length, new IPEndPoint(IPAddress.Broadcast, 1900)); + peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); + + // allow some extra time for router to respond + // System.Threading.Thread.Sleep(50); + + m_discoveryResponseDeadline = (float)NetTime.Now + 6.0f; // arbitrarily chosen number, router gets 6 seconds to respond + m_status = UPnPStatus.Discovering; + } + + internal void ExtractServiceUrl(string resp) + { +#if !DEBUG + try + { +#endif + XmlDocument desc = new XmlDocument(); + desc.Load(WebRequest.Create(resp).GetResponse().GetResponseStream()); + XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable); + nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); + XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr); + if (!typen.Value.Contains("InternetGatewayDevice")) + return; + + m_serviceName = "WANIPConnection"; + XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr); + if (node == null) + { + //try another service name + m_serviceName = "WANPPPConnection"; + node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr); + if (node == null) + return; + } + + m_serviceUrl = CombineUrls(resp, node.Value); + m_peer.LogDebug("UPnP service ready"); + m_status = UPnPStatus.Available; + m_discoveryComplete.Set(); +#if !DEBUG + } + catch + { + m_peer.LogVerbose("Exception ignored trying to parse UPnP XML response"); + return; + } +#endif + } + + private static string CombineUrls(string gatewayURL, string subURL) + { + // Is Control URL an absolute URL? + if ((subURL.Contains("http:")) || (subURL.Contains("."))) + return subURL; + + gatewayURL = gatewayURL.Replace("http://", ""); // strip any protocol + int n = gatewayURL.IndexOf("/"); + if (n != -1) + gatewayURL = gatewayURL.Substring(0, n); // Use first portion of URL + return "http://" + gatewayURL + subURL; + } + + private bool CheckAvailability() + { + switch (m_status) + { + case UPnPStatus.NotAvailable: + return false; + case UPnPStatus.Available: + return true; + case UPnPStatus.Discovering: + if (m_discoveryComplete.WaitOne(c_discoveryTimeOutMillis)) + return true; + if (NetTime.Now > m_discoveryResponseDeadline) + m_status = UPnPStatus.NotAvailable; + return false; + } + return false; + } + + /// + /// Add a forwarding rule to the router using UPnP + /// + public bool ForwardPort(int port, string description) + { + if (!CheckAvailability()) + return false; + + IPAddress mask; + var client = NetUtility.GetMyAddress(out mask); + if (client == null) + return false; + + try + { + SOAPRequest(m_serviceUrl, + "" + + "" + + "" + port.ToString() + "" + + "" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "" + + "" + port.ToString() + "" + + "" + client.ToString() + "" + + "1" + + "" + description + "" + + "0" + + "", + "AddPortMapping"); + + m_peer.LogDebug("Sent UPnP port forward request"); + System.Threading.Thread.Sleep(50); + } + catch (Exception ex) + { + m_peer.LogWarning("UPnP port forward failed: " + ex.Message); + return false; + } + return true; + } + + /// + /// Delete a forwarding rule from the router using UPnP + /// + public bool DeleteForwardingRule(int port) + { + if (!CheckAvailability()) + return false; + + try + { + SOAPRequest(m_serviceUrl, + "" + + "" + + "" + + "" + port + "" + + "" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "" + + "", "DeletePortMapping"); + return true; + } + catch (Exception ex) + { + m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message); + return false; + } + } + + /// + /// Retrieve the extern ip using UPnP + /// + public IPAddress GetExternalIP() + { + if (!CheckAvailability()) + return null; + try + { + XmlDocument xdoc = SOAPRequest(m_serviceUrl, "" + + "", "GetExternalIPAddress"); + XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable); + nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); + string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value; + return IPAddress.Parse(IP); + } + catch (Exception ex) + { + m_peer.LogWarning("Failed to get external IP: " + ex.Message); + return null; + } + } + + private XmlDocument SOAPRequest(string url, string soap, string function) + { + string req = "" + + "" + + "" + + soap + + "" + + ""; + WebRequest r = HttpWebRequest.Create(url); + r.Method = "POST"; + byte[] b = System.Text.Encoding.UTF8.GetBytes(req); + r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:" + m_serviceName + ":1#" + function + "\""); + r.ContentType = "text/xml; charset=\"utf-8\""; + r.ContentLength = b.Length; + r.GetRequestStream().Write(b, 0, b.Length); + XmlDocument resp = new XmlDocument(); + WebResponse wres = r.GetResponse(); + Stream ress = wres.GetResponseStream(); + resp.Load(ress); + return resp; + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetUnreliableSenderChannel.cs b/Lidgren.Network/NetUnreliableSenderChannel.cs new file mode 100644 index 0000000..01bb8e2 --- /dev/null +++ b/Lidgren.Network/NetUnreliableSenderChannel.cs @@ -0,0 +1,128 @@ +using System; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Sender part of Selective repeat ARQ for a particular NetChannel + /// + internal sealed class NetUnreliableSenderChannel : NetSenderChannelBase + { + private NetConnection m_connection; + private int m_windowStart; + private int m_windowSize; + private int m_sendStart; + + private NetBitVector m_receivedAcks; + + internal override int WindowSize { get { return m_windowSize; } } + + internal NetUnreliableSenderChannel(NetConnection connection, int windowSize) + { + m_connection = connection; + m_windowSize = windowSize; + m_windowStart = 0; + m_sendStart = 0; + m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers); + m_queuedSends = new NetQueue(8); + } + + internal override int GetAllowedSends() + { + int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % m_windowSize; + NetException.Assert(retval >= 0 && retval <= m_windowSize); + return retval; + } + + internal override void Reset() + { + m_receivedAcks.Clear(); + m_queuedSends.Clear(); + m_windowStart = 0; + m_sendStart = 0; + } + + internal override NetSendResult Enqueue(NetOutgoingMessage message) + { + int queueLen = m_queuedSends.Count + 1; + int left = GetAllowedSends(); + if (queueLen > left || (message.LengthBytes > m_connection.m_currentMTU && m_connection.m_peerConfiguration.UnreliableSizeBehaviour == NetUnreliableSizeBehaviour.DropAboveMTU)) + { + m_connection.Peer.Recycle(message); + return NetSendResult.Dropped; + } + + m_queuedSends.Enqueue(message); + return NetSendResult.Sent; + } + + // call this regularely + internal override void SendQueuedMessages(float now) + { + int num = GetAllowedSends(); + if (num < 1) + return; + + // queued sends + while (m_queuedSends.Count > 0 && num > 0) + { + NetOutgoingMessage om; + if (m_queuedSends.TryDequeue(out om)) + ExecuteSend(om); + num--; + } + } + + private void ExecuteSend(NetOutgoingMessage message) + { + m_connection.m_peer.VerifyNetworkThread(); + + int seqNr = m_sendStart; + m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers; + + m_connection.QueueSendMessage(message, seqNr); + + Interlocked.Decrement(ref message.m_recyclingCount); + if (message.m_recyclingCount <= 0) + m_connection.m_peer.Recycle(message); + + return; + } + + // remoteWindowStart is remote expected sequence number; everything below this has arrived properly + // seqNr is the actual nr received + internal override void ReceiveAcknowledge(float now, int seqNr) + { + // late (dupe), on time or early ack? + int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart); + + if (relate < 0) + { + //m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr); + return; // late/duplicate ack + } + + if (relate == 0) + { + //m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr); + + // ack arrived right on time + NetException.Assert(seqNr == m_windowStart); + + m_receivedAcks[m_windowStart] = false; + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + + return; + } + + // Advance window to this position + m_receivedAcks[seqNr] = true; + + while (m_windowStart != seqNr) + { + m_receivedAcks[m_windowStart] = false; + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + } + } +} diff --git a/Lidgren.Network/NetUnreliableSequencedReceiver.cs b/Lidgren.Network/NetUnreliableSequencedReceiver.cs new file mode 100644 index 0000000..e28a49f --- /dev/null +++ b/Lidgren.Network/NetUnreliableSequencedReceiver.cs @@ -0,0 +1,29 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetUnreliableSequencedReceiver : NetReceiverChannelBase + { + private int m_lastReceivedSequenceNumber = -1; + + public NetUnreliableSequencedReceiver(NetConnection connection) + : base(connection) + { + } + + internal override void ReceiveMessage(NetIncomingMessage msg) + { + int nr = msg.m_sequenceNumber; + + // ack no matter what + m_connection.QueueAck(msg.m_receivedMessageType, nr); + + int relate = NetUtility.RelativeSequenceNumber(nr, m_lastReceivedSequenceNumber + 1); + if (relate < 0) + return; // drop if late + + m_lastReceivedSequenceNumber = nr; + m_peer.ReleaseMessage(msg); + } + } +} diff --git a/Lidgren.Network/NetUnreliableUnorderedReceiver.cs b/Lidgren.Network/NetUnreliableUnorderedReceiver.cs new file mode 100644 index 0000000..b61bb1d --- /dev/null +++ b/Lidgren.Network/NetUnreliableUnorderedReceiver.cs @@ -0,0 +1,20 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetUnreliableUnorderedReceiver : NetReceiverChannelBase + { + public NetUnreliableUnorderedReceiver(NetConnection connection) + : base(connection) + { + } + + internal override void ReceiveMessage(NetIncomingMessage msg) + { + // ack no matter what + m_connection.QueueAck(msg.m_receivedMessageType, msg.m_sequenceNumber); + + m_peer.ReleaseMessage(msg); + } + } +} diff --git a/Lidgren.Network/NetUtility.cs b/Lidgren.Network/NetUtility.cs new file mode 100644 index 0000000..fff41f2 --- /dev/null +++ b/Lidgren.Network/NetUtility.cs @@ -0,0 +1,632 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#if !__ANDROID__ && !IOS && !UNITY_WEBPLAYER && !UNITY_ANDROID && !UNITY_IPHONE +#define IS_FULL_NET_AVAILABLE +#endif + +using System; +using System.Net; + +#if IS_FULL_NET_AVAILABLE +using System.Net.NetworkInformation; +#endif + +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + /// + /// Utility methods + /// + public static class NetUtility + { + /// + /// Resolve endpoint callback + /// + public delegate void ResolveEndPointCallback(IPEndPoint endPoint); + + /// + /// Resolve address callback + /// + public delegate void ResolveAddressCallback(IPAddress adr); + + /// + /// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number (asynchronous version) + /// + public static void ResolveAsync(string ipOrHost, int port, ResolveEndPointCallback callback) + { + ResolveAsync(ipOrHost, delegate(IPAddress adr) + { + if (adr == null) + { + callback(null); + } + else + { + callback(new IPEndPoint(adr, port)); + } + }); + } + + /// + /// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number + /// + public static IPEndPoint Resolve(string ipOrHost, int port) + { + IPAddress adr = Resolve(ipOrHost); + return new IPEndPoint(adr, port); + } + + /// + /// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname (asynchronous version) + /// + public static void ResolveAsync(string ipOrHost, ResolveAddressCallback callback) + { + if (string.IsNullOrEmpty(ipOrHost)) + throw new ArgumentException("Supplied string must not be empty", "ipOrHost"); + + ipOrHost = ipOrHost.Trim(); + + IPAddress ipAddress = null; + if (IPAddress.TryParse(ipOrHost, out ipAddress)) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + { + callback(ipAddress); + return; + } + throw new ArgumentException("This method will not currently resolve other than ipv4 addresses"); + } + + // ok must be a host name + IPHostEntry entry; + try + { + Dns.BeginGetHostEntry(ipOrHost, delegate(IAsyncResult result) + { + try + { + entry = Dns.EndGetHostEntry(result); + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostNotFound) + { + //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost)); + callback(null); + return; + } + else + { + throw; + } + } + + if (entry == null) + { + callback(null); + return; + } + + // check each entry for a valid IP address + foreach (IPAddress ipCurrent in entry.AddressList) + { + if (ipCurrent.AddressFamily == AddressFamily.InterNetwork) + { + callback(ipCurrent); + return; + } + } + + callback(null); + }, null); + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostNotFound) + { + //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost)); + callback(null); + } + else + { + throw; + } + } + } + + /// + /// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname + /// + public static IPAddress Resolve(string ipOrHost) + { + if (string.IsNullOrEmpty(ipOrHost)) + throw new ArgumentException("Supplied string must not be empty", "ipOrHost"); + + ipOrHost = ipOrHost.Trim(); + + IPAddress ipAddress = null; + if (IPAddress.TryParse(ipOrHost, out ipAddress)) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + return ipAddress; + throw new ArgumentException("This method will not currently resolve other than ipv4 addresses"); + } + + // ok must be a host name + try + { + var addresses = Dns.GetHostAddresses(ipOrHost); + if (addresses == null) + return null; + foreach (var address in addresses) + { + if (address.AddressFamily == AddressFamily.InterNetwork) + return address; + } + return null; + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostNotFound) + { + //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost)); + return null; + } + else + { + throw; + } + } + } + +#if IS_FULL_NET_AVAILABLE + + private static NetworkInterface GetNetworkInterface() + { + IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties(); + if (computerProperties == null) + return null; + + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + if (nics == null || nics.Length < 1) + return null; + + NetworkInterface best = null; + foreach (NetworkInterface adapter in nics) + { + if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback || adapter.NetworkInterfaceType == NetworkInterfaceType.Unknown) + continue; + if (!adapter.Supports(NetworkInterfaceComponent.IPv4)) + continue; + if (best == null) + best = adapter; + if (adapter.OperationalStatus != OperationalStatus.Up) + continue; + + // make sure this adapter has any ipv4 addresses + IPInterfaceProperties properties = adapter.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses) + { + if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) + { + // Yes it does, return this network interface. + return adapter; + } + } + } + return best; + } + + /// + /// Returns the physical (MAC) address for the first usable network interface + /// + public static PhysicalAddress GetMacAddress() + { + NetworkInterface ni = GetNetworkInterface(); + if (ni == null) + return null; + return ni.GetPhysicalAddress(); + } +#endif + + /// + /// Create a hex string from an Int64 value + /// + public static string ToHexString(long data) + { + return ToHexString(BitConverter.GetBytes(data)); + } + + /// + /// Create a hex string from an array of bytes + /// + public static string ToHexString(byte[] data) + { + return ToHexString(data, 0, data.Length); + } + + /// + /// Create a hex string from an array of bytes + /// + public static string ToHexString(byte[] data, int offset, int length) + { + char[] c = new char[length * 2]; + byte b; + for (int i = 0; i < length; ++i) + { + b = ((byte)(data[offset + i] >> 4)); + c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30); + b = ((byte)(data[offset + i] & 0xF)); + c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30); + } + return new string(c); + } + + /// + /// Gets the local broadcast address + /// + public static IPAddress GetBroadcastAddress() + { +#if __ANDROID__ + try{ + Android.Net.Wifi.WifiManager wifi = (Android.Net.Wifi.WifiManager)Android.App.Application.Context.GetSystemService(Android.App.Activity.WifiService); + if (wifi.IsWifiEnabled) + { + var dhcp = wifi.DhcpInfo; + + int broadcast = (dhcp.IpAddress & dhcp.Netmask) | ~dhcp.Netmask; + byte[] quads = new byte[4]; + for (int k = 0; k < 4; k++) + { + quads[k] = (byte) ((broadcast >> k * 8) & 0xFF); + } + return new IPAddress(quads); + } + } + catch // Catch Access Denied Errors + { + return IPAddress.Broadcast; + } +#endif +#if IS_FULL_NET_AVAILABLE + try + { + NetworkInterface ni = GetNetworkInterface(); + if (ni == null) + { + return null; + } + + IPInterfaceProperties properties = ni.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses) + { + if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) + { + var mask = unicastAddress.IPv4Mask; + byte[] ipAdressBytes = unicastAddress.Address.GetAddressBytes(); + byte[] subnetMaskBytes = mask.GetAddressBytes(); + + if (ipAdressBytes.Length != subnetMaskBytes.Length) + throw new ArgumentException("Lengths of IP address and subnet mask do not match."); + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255)); + } + return new IPAddress(broadcastAddress); + } + } + } + catch // Catch any errors + { + return IPAddress.Broadcast; + } +#endif + return IPAddress.Broadcast; + } + + /// + /// Gets my local IPv4 address (not necessarily external) and subnet mask + /// + public static IPAddress GetMyAddress(out IPAddress mask) + { + mask = null; +#if __ANDROID__ + try + { + Android.Net.Wifi.WifiManager wifi = (Android.Net.Wifi.WifiManager)Android.App.Application.Context.GetSystemService(Android.App.Activity.WifiService); + if (!wifi.IsWifiEnabled) return null; + var dhcp = wifi.DhcpInfo; + + int addr = dhcp.IpAddress; + byte[] quads = new byte[4]; + for (int k = 0; k < 4; k++) + { + quads[k] = (byte) ((addr >> k * 8) & 0xFF); + } + return new IPAddress(quads); + } + catch // Catch Access Denied errors + { + return null; + } + +#endif +#if IS_FULL_NET_AVAILABLE + NetworkInterface ni = GetNetworkInterface(); + if (ni == null) + { + mask = null; + return null; + } + + IPInterfaceProperties properties = ni.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses) + { + if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) + { + mask = unicastAddress.IPv4Mask; + return unicastAddress.Address; + } + } +#endif + return null; + } + + /// + /// Returns true if the IPEndPoint supplied is on the same subnet as this host + /// + public static bool IsLocal(IPEndPoint endPoint) + { + if (endPoint == null) + return false; + return IsLocal(endPoint.Address); + } + + /// + /// Returns true if the IPAddress supplied is on the same subnet as this host + /// + public static bool IsLocal(IPAddress remote) + { + IPAddress mask; + IPAddress local = GetMyAddress(out mask); + + if (mask == null) + return false; + + uint maskBits = BitConverter.ToUInt32(mask.GetAddressBytes(), 0); + uint remoteBits = BitConverter.ToUInt32(remote.GetAddressBytes(), 0); + uint localBits = BitConverter.ToUInt32(local.GetAddressBytes(), 0); + + // compare network portions + return ((remoteBits & maskBits) == (localBits & maskBits)); + } + + /// + /// Returns how many bits are necessary to hold a certain number + /// + [CLSCompliant(false)] + public static int BitsToHoldUInt(uint value) + { + int bits = 1; + while ((value >>= 1) != 0) + bits++; + return bits; + } + + /// + /// Returns how many bytes are required to hold a certain number of bits + /// + public static int BytesToHoldBits(int numBits) + { + return (numBits + 7) / 8; + } + + internal static UInt32 SwapByteOrder(UInt32 value) + { + return + ((value & 0xff000000) >> 24) | + ((value & 0x00ff0000) >> 8) | + ((value & 0x0000ff00) << 8) | + ((value & 0x000000ff) << 24); + } + + internal static UInt64 SwapByteOrder(UInt64 value) + { + return + ((value & 0xff00000000000000L) >> 56) | + ((value & 0x00ff000000000000L) >> 40) | + ((value & 0x0000ff0000000000L) >> 24) | + ((value & 0x000000ff00000000L) >> 8) | + ((value & 0x00000000ff000000L) << 8) | + ((value & 0x0000000000ff0000L) << 24) | + ((value & 0x000000000000ff00L) << 40) | + ((value & 0x00000000000000ffL) << 56); + } + + internal static bool CompareElements(byte[] one, byte[] two) + { + if (one.Length != two.Length) + return false; + for (int i = 0; i < one.Length; i++) + if (one[i] != two[i]) + return false; + return true; + } + + /// + /// Convert a hexadecimal string to a byte array + /// + public static byte[] ToByteArray(String hexString) + { + byte[] retval = new byte[hexString.Length / 2]; + for (int i = 0; i < hexString.Length; i += 2) + retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + return retval; + } + + /// + /// Converts a number of bytes to a shorter, more readable string representation + /// + public static string ToHumanReadable(long bytes) + { + if (bytes < 4000) // 1-4 kb is printed in bytes + return bytes + " bytes"; + if (bytes < 1000 * 1000) // 4-999 kb is printed in kb + return Math.Round(((double)bytes / 1000.0), 2) + " kilobytes"; + return Math.Round(((double)bytes / (1000.0 * 1000.0)), 2) + " megabytes"; // else megabytes + } + + internal static int RelativeSequenceNumber(int nr, int expected) + { + return (nr - expected + NetConstants.NumSequenceNumbers + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers - (NetConstants.NumSequenceNumbers / 2); + + // old impl: + //int retval = ((nr + NetConstants.NumSequenceNumbers) - expected) % NetConstants.NumSequenceNumbers; + //if (retval > (NetConstants.NumSequenceNumbers / 2)) + // retval -= NetConstants.NumSequenceNumbers; + //return retval; + } + + /// + /// Gets the window size used internally in the library for a certain delivery method + /// + public static int GetWindowSize(NetDeliveryMethod method) + { + switch (method) + { + case NetDeliveryMethod.Unknown: + return 0; + + case NetDeliveryMethod.Unreliable: + case NetDeliveryMethod.UnreliableSequenced: + return NetConstants.UnreliableWindowSize; + + case NetDeliveryMethod.ReliableOrdered: + return NetConstants.ReliableOrderedWindowSize; + + case NetDeliveryMethod.ReliableSequenced: + case NetDeliveryMethod.ReliableUnordered: + default: + return NetConstants.DefaultWindowSize; + } + } + + // shell sort + internal static void SortMembersList(System.Reflection.MemberInfo[] list) + { + int h; + int j; + System.Reflection.MemberInfo tmp; + + h = 1; + while (h * 3 + 1 <= list.Length) + h = 3 * h + 1; + + while (h > 0) + { + for (int i = h - 1; i < list.Length; i++) + { + tmp = list[i]; + j = i; + while (true) + { + if (j >= h) + { + if (string.Compare(list[j - h].Name, tmp.Name, StringComparison.InvariantCulture) > 0) + { + list[j] = list[j - h]; + j -= h; + } + else + break; + } + else + break; + } + + list[j] = tmp; + } + h /= 3; + } + } + + internal static NetDeliveryMethod GetDeliveryMethod(NetMessageType mtp) + { + if (mtp >= NetMessageType.UserReliableOrdered1) + return NetDeliveryMethod.ReliableOrdered; + else if (mtp >= NetMessageType.UserReliableSequenced1) + return NetDeliveryMethod.ReliableSequenced; + else if (mtp >= NetMessageType.UserReliableUnordered) + return NetDeliveryMethod.ReliableUnordered; + else if (mtp >= NetMessageType.UserSequenced1) + return NetDeliveryMethod.UnreliableSequenced; + return NetDeliveryMethod.Unreliable; + } + + /// + /// Creates a comma delimited string from a lite of items + /// + public static string MakeCommaDelimitedList(IList list) + { + var cnt = list.Count; + StringBuilder bdr = new StringBuilder(cnt * 5); // educated guess + for(int i=0;i + /// Create a SHA1 digest from a string + /// + public static byte[] CreateSHA1Hash(string key) + { + using (var sha = new SHA1CryptoServiceProvider()) + return sha.ComputeHash(Encoding.UTF8.GetBytes(key)); + } + + /// + /// Create a SHA1 digest from a byte buffer + /// + public static byte[] CreateSHA1Hash(byte[] data) + { + using (var sha = new SHA1CryptoServiceProvider()) + return sha.ComputeHash(data); + } + + /// + /// Create a SHA1 digest from a byte buffer + /// + public static byte[] CreateSHA1Hash(byte[] data, int offset, int count) + { + using (var sha = new SHA1CryptoServiceProvider()) + return sha.ComputeHash(data, offset, count); + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/Properties/AssemblyInfo.cs b/Lidgren.Network/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d700560 --- /dev/null +++ b/Lidgren.Network/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Lidgren.Network")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Lidgren.Network")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("78f2a988-46e1-4fc4-b7b4-dd2cbe46da6a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2012.1.7.0")] +[assembly: AssemblyFileVersion("2012.1.7.0")] +[assembly: System.CLSCompliant(true)] \ No newline at end of file diff --git a/Neoforce/ArchiveManager.cs b/Neoforce/ArchiveManager.cs new file mode 100644 index 0000000..1bb86d5 --- /dev/null +++ b/Neoforce/ArchiveManager.cs @@ -0,0 +1,286 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ArchiveManager.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Xna.Framework.Content; +using TomShane.Neoforce.External.Zip; +using System.Globalization; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public class ArchiveManager : ContentManager + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private string archivePath = null; + private ZipFile archive = null; + private bool useArchive = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + /// + public virtual string ArchivePath + { + get { return archivePath; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public bool UseArchive + { + get { return useArchive; } + set { useArchive = value; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + /// + public ArchiveManager(IServiceProvider serviceProvider) : this(serviceProvider, null) { } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public ArchiveManager(IServiceProvider serviceProvider, string archive): base(serviceProvider) + { + if (archive != null) + { + this.archive = ZipFile.Read(archive); + archivePath = archive; + useArchive = true; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + /// + protected override Stream OpenStream(string assetName) + { + if (useArchive && archive != null) + { + assetName = assetName.Replace("\\", "/"); + if (assetName.StartsWith("/")) assetName = assetName.Remove(0, 1); + + string fullAssetName = (assetName + ".xnb").ToLower(); + + foreach (ZipEntry entry in archive) + { + ZipDirEntry ze = new ZipDirEntry(entry); + + string entryName = entry.FileName.ToLower(); + + if (entryName == fullAssetName) + { + return entry.GetStream(); + } + } + throw new Exception("Cannot find asset \"" + assetName + "\" in the archive."); + } + else + { + return base.OpenStream(assetName); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public string[] GetAssetNames() + { + if (useArchive && archive != null) + { + List filenames = new List(); + + foreach (ZipEntry entry in archive) + { + string name = entry.FileName; + if (name.EndsWith(".xnb")) + { + name = name.Remove(name.Length - 4, 4); + filenames.Add(name); + } + } + return filenames.ToArray(); + } + else + { + return null; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public string[] GetAssetNames(string path) + { + if (useArchive && archive != null) + { + if (path != null && path != "" && path != "\\" && path != "/") + { + List filenames = new List(); + + foreach (ZipEntry entry in archive) + { + string name = entry.FileName; + if (name.EndsWith(".xnb")) + { + name = name.Remove(name.Length - 4, 4); + } + + string[] parts = name.Split('/'); + string dir = ""; + for (int i = 0; i < parts.Length - 1; i++) + { + dir += parts[i] + '/'; + } + + path = path.Replace("\\", "/"); + if (path.StartsWith("/")) path = path.Remove(0, 1); + if (!path.EndsWith("/")) path += '/'; + + if (dir.ToLower() == path.ToLower() && !name.EndsWith("/")) + { + filenames.Add(name); + } + } + return filenames.ToArray(); + } + else + { + return GetAssetNames(); + } + } + else + { + return null; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public Stream GetFileStream(string filename) + { + if (useArchive && archive != null) + { + filename = filename.Replace("\\", "/").ToLower(); + if (filename.StartsWith("/")) filename = filename.Remove(0, 1); + + foreach (ZipEntry entry in archive) + { + string entryName = entry.FileName.ToLower(); + + if (entryName.Equals(filename)) + return entry.GetStream(); + } + + throw new Exception("Cannot find file \"" + filename + "\" in the archive."); + } + else + { + return null; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public string[] GetDirectories(string path) + { + if (useArchive && archive != null) + { + if (path != null && path != "" && path != "\\" && path != "/") + { + List dirs = new List(); + + path = path.Replace("\\", "/"); + if (path.StartsWith("/")) path = path.Remove(0, 1); + if (!path.EndsWith("/")) path += '/'; + + foreach (ZipEntry entry in archive) + { + string name = entry.FileName; + if (name.ToLower().StartsWith(path.ToLower())) + { + int i = name.IndexOf("/", path.Length); + string item = name.Substring(path.Length, i - path.Length) + "\\"; + if (!dirs.Contains(item)) + { + dirs.Add(item); + } + } + } + return dirs.ToArray(); + } + else + { + return GetAssetNames(); + } + } + else if (Directory.Exists(path)) + { + string[] dirs = Directory.GetDirectories(path); + + for (int i = 0; i < dirs.Length; i++) + { + string[] parts = dirs[i].Split('\\'); + dirs[i] = parts[parts.Length - 1] + '\\'; + } + + return dirs; + } + else return null; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} diff --git a/Neoforce/Bevel.cs b/Neoforce/Bevel.cs new file mode 100644 index 0000000..a7995db --- /dev/null +++ b/Neoforce/Bevel.cs @@ -0,0 +1,306 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Bevel.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + public enum BevelStyle + { + None, + Flat, + Etched, + Bumped, + Lowered, + Raised + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum BevelBorder + { + None, + Left, + Top, + Right, + Bottom, + All + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + public class Bevel: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private BevelBorder border = BevelBorder.All; + private BevelStyle style = BevelStyle.Etched; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public BevelBorder Border + { + get { return border; } + set + { + if (border != value) + { + border = value; + if (!Suspended) OnBorderChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public BevelStyle Style + { + get { return style; } + set + { + if (style != value) + { + style = value; + if (!Suspended) OnStyleChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler BorderChanged; + public event EventHandler StyleChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public Bevel(Manager manager): base(manager) + { + CanFocus = false; + Passive = true; + Width = 64; + Height = 64; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + if (Border != BevelBorder.None && Style != BevelStyle.None) + { + if (Border != BevelBorder.All) + { + DrawPart(renderer, rect, Border, Style, false); + } + else + { + DrawPart(renderer, rect, BevelBorder.Left, Style, true); + DrawPart(renderer, rect, BevelBorder.Top, Style, true); + DrawPart(renderer, rect, BevelBorder.Right, Style, true); + DrawPart(renderer, rect, BevelBorder.Bottom, Style, true); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DrawPart(Renderer renderer, Rectangle rect, BevelBorder pos, BevelStyle style, bool all) + { + SkinLayer layer = Skin.Layers["Control"]; + Color c1 = Utilities.ParseColor(layer.Attributes["LightColor"].Value); + Color c2 = Utilities.ParseColor(layer.Attributes["DarkColor"].Value); + Color c3 = Utilities.ParseColor(layer.Attributes["FlatColor"].Value); + + if (Color != UndefinedColor) c3 = Color; + + Texture2D img = Skin.Layers["Control"].Image.Resource; + + int x1 = 0; int y1 = 0; int w1 = 0; int h1 = 0; + int x2 = 0; int y2 = 0; int w2 = 0; int h2 = 0; + + if (style == BevelStyle.Bumped || style == BevelStyle.Etched) + { + if (all && (pos == BevelBorder.Top || pos == BevelBorder.Bottom)) + { + rect = new Rectangle(rect.Left + 1, rect.Top, rect.Width - 2, rect.Height); + } + else if (all && (pos == BevelBorder.Left)) + { + rect = new Rectangle(rect.Left, rect.Top, rect.Width, rect.Height - 1); + } + switch (pos) + { + case BevelBorder.Left: + { + x1 = rect.Left; y1 = rect.Top; w1 = 1; h1 = rect.Height; + x2 = x1 + 1; y2 = y1; w2 = w1; h2 = h1; + break; + } + case BevelBorder.Top: + { + x1 = rect.Left; y1 = rect.Top; w1 = rect.Width; h1 = 1; + x2 = x1; y2 = y1 + 1; w2 = w1; h2 = h1; + break; + } + case BevelBorder.Right: + { + x1 = rect.Left + rect.Width - 2; y1 = rect.Top; w1 = 1; h1 = rect.Height; + x2 = x1 + 1; y2 = y1; w2 = w1; h2 = h1; + break; + } + case BevelBorder.Bottom: + { + x1 = rect.Left; y1 = rect.Top + rect.Height - 2; w1 = rect.Width; h1 = 1; + x2 = x1; y2 = y1 + 1; w2 = w1; h2 = h1; + break; + } + } + } + else + { + switch (pos) + { + case BevelBorder.Left: + { + x1 = rect.Left; y1 = rect.Top; w1 = 1; h1 = rect.Height; + break; + } + case BevelBorder.Top: + { + x1 = rect.Left; y1 = rect.Top; w1 = rect.Width; h1 = 1; + break; + } + case BevelBorder.Right: + { + x1 = rect.Left + rect.Width - 1; y1 = rect.Top; w1 = 1; h1 = rect.Height; + break; + } + case BevelBorder.Bottom: + { + x1 = rect.Left; y1 = rect.Top + rect.Height - 1; w1 = rect.Width; h1 = 1; + break; + } + } + } + + switch (Style) + { + case BevelStyle.Bumped: + { + renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c1); + renderer.Draw(img, new Rectangle(x2, y2, w2, h2), c2); + break; + } + case BevelStyle.Etched: + { + renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c2); + renderer.Draw(img, new Rectangle(x2, y2, w2, h2), c1); + break; + } + case BevelStyle.Raised: + { + Color c = c1; + if (pos == BevelBorder.Left || pos == BevelBorder.Top) c = c1; + else c = c2; + + renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c); + break; + } + case BevelStyle.Lowered: + { + Color c = c1; + if (pos == BevelBorder.Left || pos == BevelBorder.Top) c = c2; + else c = c1; + + renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c); + break; + } + default: + { + renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c3); + break; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnBorderChanged(EventArgs e) + { + if (BorderChanged != null) BorderChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnStyleChanged(EventArgs e) + { + if (StyleChanged != null) StyleChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + + } + +} diff --git a/Neoforce/Button.cs b/Neoforce/Button.cs new file mode 100644 index 0000000..0850113 --- /dev/null +++ b/Neoforce/Button.cs @@ -0,0 +1,294 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Button.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public enum SizeMode + { + Normal, + Auto, + Centered, + Stretched, + /// + /// Only Supported by ImageBox + /// + Tiled + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public enum ButtonMode + { + Normal, + PushButton + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public class Glyph + { + //////////////////////////////////////////////////////////////////////////// + public Texture2D Image = null; + public SizeMode SizeMode = SizeMode.Stretched; + public Color Color = Color.White; + public Point Offset = Point.Zero; + public Rectangle SourceRect = Rectangle.Empty; + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public Glyph(Texture2D image) + { + Image = image; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public Glyph(Texture2D image, Rectangle sourceRect): this(image) + { + SourceRect = sourceRect; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public class Button: ButtonBase + { + + #region //// Consts //////////// + + //////////////////////////////////////////////////////////////////////////// + private const string skButton = "Button"; + private const string lrButton = "Control"; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Glyph glyph = null; + private ModalResult modalResult = ModalResult.None; + private ButtonMode mode = ButtonMode.Normal; + private bool pushed = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public Glyph Glyph + { + get { return glyph; } + set + { + glyph = value; + if (!Suspended) OnGlyphChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public ModalResult ModalResult + { + get { return modalResult; } + set { modalResult = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public ButtonMode Mode + { + get { return mode; } + set { mode = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public bool Pushed + { + get { return pushed; } + set + { + pushed = value; + Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler GlyphChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public Button(Manager manager): base(manager) + { + SetDefaultSize(72, 24); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls[skButton]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + + if (mode == ButtonMode.PushButton && pushed) + { + SkinLayer l = Skin.Layers[lrButton]; + renderer.DrawLayer(l, rect, l.States.Pressed.Color, l.States.Pressed.Index); + if (l.States.Pressed.Overlay) + { + renderer.DrawLayer(l, rect, l.Overlays.Pressed.Color, l.Overlays.Pressed.Index); + } + } + else + { + base.DrawControl(renderer, rect, gameTime); + } + + SkinLayer layer = Skin.Layers[lrButton]; + SpriteFont font = (layer.Text != null && layer.Text.Font != null) ? layer.Text.Font.Resource : null; + Color col = Color.White; + int ox = 0; int oy = 0; + + if (ControlState == ControlState.Pressed) + { + if (layer.Text != null) col = layer.Text.Colors.Pressed; + ox = 1; oy = 1; + } + if (glyph != null) + { + Margins cont = layer.ContentMargins; + Rectangle r = new Rectangle(rect.Left + cont.Left, + rect.Top + cont.Top, + rect.Width - cont.Horizontal, + rect.Height - cont.Vertical); + renderer.DrawGlyph(glyph, r); + } + else + { + renderer.DrawString(this, layer, Text, rect, true, ox, oy); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void OnGlyphChanged(EventArgs e) + { + if (GlyphChanged != null) GlyphChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnClick(EventArgs e) + { + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + + if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None) + { + pushed = !pushed; + } + + base.OnClick(e); + + if ((ex.Button == MouseButton.Left || ex.Button == MouseButton.None) && Root != null) + { + if (Root is Window) + { + Window wnd = (Window)Root; + if (ModalResult != ModalResult.None) + { + wnd.Close(ModalResult); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + + #endregion + +} diff --git a/Neoforce/ButtonBase.cs b/Neoforce/ButtonBase.cs new file mode 100644 index 0000000..5de4bb3 --- /dev/null +++ b/Neoforce/ButtonBase.cs @@ -0,0 +1,117 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ButtonBase.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public abstract class ButtonBase: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public override ControlState ControlState + { + get + { + if (DesignMode) return ControlState.Enabled; + else if (Suspended) return ControlState.Disabled; + else + { + if (!Enabled) return ControlState.Disabled; + + if ((Pressed[(int)MouseButton.Left] && Inside) || (Focused && (Pressed[(int)GamePadActions.Press] || Pressed[(int)MouseButton.None]))) return ControlState.Pressed; + else if (Hovered && Inside) return ControlState.Hovered; + else if ((Focused && !Inside) || (Hovered && !Inside) || (Focused && !Hovered && Inside)) return ControlState.Focused; + else return ControlState.Enabled; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + protected ButtonBase(Manager manager) + : base(manager) + { + SetDefaultSize(72, 24); + DoubleClicks = false; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnClick(EventArgs e) + { + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None) + { + base.OnClick(e); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} diff --git a/Neoforce/CheckBox.cs b/Neoforce/CheckBox.cs new file mode 100644 index 0000000..0b1cb16 --- /dev/null +++ b/Neoforce/CheckBox.cs @@ -0,0 +1,158 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: CheckBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class CheckBox: ButtonBase + { + + #region //// Consts //////////// + + //////////////////////////////////////////////////////////////////////////// + private const string skCheckBox = "CheckBox"; + private const string lrCheckBox = "Control"; + private const string lrChecked = "Checked"; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private bool state = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool Checked + { + get + { + return state; + } + set + { + state = value; + Invalidate(); + if (!Suspended) OnCheckedChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler CheckedChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public CheckBox(Manager manager): base(manager) + { + CheckLayer(Skin, lrChecked); + + Width = 64; + Height = 16; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls[skCheckBox]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + SkinLayer layer = Skin.Layers[lrChecked]; + SkinText font = Skin.Layers[lrChecked].Text; + + if (!state) + { + layer = Skin.Layers[lrCheckBox]; + font = Skin.Layers[lrCheckBox].Text; + } + + rect.Width = layer.Width; + rect.Height = layer.Height; + Rectangle rc = new Rectangle(rect.Left + rect.Width + 4, rect.Y, Width - (layer.Width + 4), rect.Height); + + renderer.DrawLayer(this, layer, rect); + renderer.DrawString(this, layer, Text, rc, false, 0, 0); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnClick(EventArgs e) + { + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + + if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None) + { + Checked = !Checked; + } + base.OnClick(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnCheckedChanged(EventArgs e) + { + if (CheckedChanged != null) CheckedChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + + } + +} diff --git a/Neoforce/ClipBox.cs b/Neoforce/ClipBox.cs new file mode 100644 index 0000000..ca4716c --- /dev/null +++ b/Neoforce/ClipBox.cs @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ClipBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + + + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ClipBox: Control + { + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public ClipBox(Manager manager): base(manager) + { + Color = Color.Transparent; + BackColor = Color.Transparent; + CanFocus = false; + Passive = true; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} \ No newline at end of file diff --git a/Neoforce/ClipControl.cs b/Neoforce/ClipControl.cs new file mode 100644 index 0000000..2ad5ffb --- /dev/null +++ b/Neoforce/ClipControl.cs @@ -0,0 +1,181 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Control.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ClipControl: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private ClipBox clientArea; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ClipBox ClientArea + { + get { return clientArea; } + set { clientArea = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override Margins ClientMargins + { + get + { + return base.ClientMargins; + } + set + { + base.ClientMargins = value; + if (clientArea != null) + { + clientArea.Left = ClientLeft; + clientArea.Top = ClientTop; + clientArea.Width = ClientWidth; + clientArea.Height = ClientHeight; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public ClipControl(Manager manager): base(manager) + { + clientArea = new ClipBox(manager); + + clientArea.Init(); + clientArea.MinimumWidth = 0; + clientArea.MinimumHeight = 0; + clientArea.Left = ClientLeft; + clientArea.Top = ClientTop; + clientArea.Width = ClientWidth; + clientArea.Height = ClientHeight; + + base.Add(clientArea); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Add(Control control, bool client) + { + if (client) + { + clientArea.Add(control); + } + else + { + base.Add(control); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Add(Control control) + { + Add(control, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Remove(Control control) + { + base.Remove(control); + clientArea.Remove(control); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + + if (clientArea != null) + { + clientArea.Left = ClientLeft; + clientArea.Top = ClientTop; + clientArea.Width = ClientWidth; + clientArea.Height = ClientHeight; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void AdjustMargins() + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + +} diff --git a/Neoforce/ComboBox.cs b/Neoforce/ComboBox.cs new file mode 100644 index 0000000..a74a2be --- /dev/null +++ b/Neoforce/ComboBox.cs @@ -0,0 +1,418 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ComboBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using System.Collections.Generic; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using System; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ComboBox: TextBox + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Button btnDown = null; + private List items = new List(); + private ListBox lstCombo = null; + private int maxItems = 5; + private bool drawSelection = true; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public override bool ReadOnly + { + get { return base.ReadOnly; } + set + { + base.ReadOnly = value; + CaretVisible = !value; + if (value) + { + #if (!XBOX && !XBOX_FAKE) + Cursor = Manager.Skin.Cursors["Default"].Resource; + #endif + } + else + { + #if (!XBOX && !XBOX_FAKE) + Cursor = Manager.Skin.Cursors["Text"].Resource; + #endif + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public bool DrawSelection + { + get { return drawSelection; } + set { drawSelection = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override string Text + { + get + { + return base.Text; + } + set + { + base.Text = value; + //if (!items.Contains(value)) --- bug + if (!items.ConvertAll(item => item.ToString()).Contains(value)) + { + ItemIndex = -1; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual List Items + { + get { return items; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public int MaxItems + { + get { return maxItems; } + set + { + if (maxItems != value) + { + maxItems = value; + if (!Suspended) OnMaxItemsChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public int ItemIndex + { + get { return lstCombo.ItemIndex; } + set + { + if (lstCombo != null) + { + if (value >= 0 && value < items.Count) + { + lstCombo.ItemIndex = value; + Text = lstCombo.Items[value].ToString(); + } + else + { + lstCombo.ItemIndex = -1; + } + } + if (!Suspended) OnItemIndexChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler MaxItemsChanged; + public event EventHandler ItemIndexChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ComboBox(Manager manager): base(manager) + { + Height = 20; + Width = 64; + ReadOnly = true; + + btnDown = new Button(Manager); + btnDown.Init(); + btnDown.Skin = new SkinControl(Manager.Skin.Controls["ComboBox.Button"]); + btnDown.CanFocus = false; + btnDown.Click += new EventHandler(btnDown_Click); + Add(btnDown, false); + + lstCombo = new ListBox(Manager); + lstCombo.Init(); + lstCombo.HotTrack = true; + lstCombo.Detached = true; + lstCombo.Visible = false; + lstCombo.Click += new EventHandler(lstCombo_Click); + lstCombo.FocusLost += new EventHandler(lstCombo_FocusLost); + lstCombo.Items = items; + manager.Input.MouseDown += new MouseEventHandler(Input_MouseDown); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + // We added the listbox to another parent than this control, so we dispose it manually + if (lstCombo != null) + { + lstCombo.Dispose(); + lstCombo = null; + } + Manager.Input.MouseDown -= Input_MouseDown; + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + lstCombo.Skin = new SkinControl(Manager.Skin.Controls["ComboBox.ListBox"]); + + btnDown.Glyph = new Glyph(Manager.Skin.Images["Shared.ArrowDown"].Resource); + btnDown.Glyph.Color = Manager.Skin.Controls["ComboBox.Button"].Layers["Control"].Text.Colors.Enabled; + btnDown.Glyph.SizeMode = SizeMode.Centered; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["ComboBox"]); + AdjustMargins(); + ReadOnly = ReadOnly; // To init the right cursor + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + + if (ReadOnly && (Focused || lstCombo.Focused) && drawSelection) + { + SkinLayer lr = Skin.Layers[0]; + Rectangle rc = new Rectangle(rect.Left + lr.ContentMargins.Left, + rect.Top + lr.ContentMargins.Top, + Width - lr.ContentMargins.Horizontal - btnDown.Width, + Height - lr.ContentMargins.Vertical); + renderer.Draw(Manager.Skin.Images["ListBox.Selection"].Resource, rc , Color.FromNonPremultiplied(255, 255, 255, 128)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + + if (btnDown != null) + { + btnDown.Width = 16; + btnDown.Height = Height - Skin.Layers[0].ContentMargins.Vertical; + btnDown.Top = Skin.Layers[0].ContentMargins.Top; + btnDown.Left = Width - btnDown.Width - 2; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void btnDown_Click(object sender, EventArgs e) + { + if (items != null && items.Count > 0) + { + if (this.Root != null && this.Root is Container) + { + (this.Root as Container).Add(lstCombo, false); + lstCombo.Alpha = Root.Alpha; + lstCombo.Left = AbsoluteLeft - Root.Left; + lstCombo.Top = AbsoluteTop - Root.Top + Height + 1; + } + else + { + Manager.Add(lstCombo); + lstCombo.Alpha = Alpha; + lstCombo.Left = AbsoluteLeft; + lstCombo.Top = AbsoluteTop + Height + 1; + } + + lstCombo.AutoHeight(maxItems); + if (lstCombo.AbsoluteTop + lstCombo.Height > Manager.TargetHeight) + { + lstCombo.Top = lstCombo.Top - Height - lstCombo.Height - 2; + } + + lstCombo.Visible = !lstCombo.Visible; + lstCombo.Focused = true; + lstCombo.Width = Width; + lstCombo.AutoHeight(maxItems); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void Input_MouseDown(object sender, MouseEventArgs e) + { + if (ReadOnly && + (e.Position.X >= AbsoluteLeft && + e.Position.X <= AbsoluteLeft + Width && + e.Position.Y >= AbsoluteTop && + e.Position.Y <= AbsoluteTop + Height)) return; + + if (lstCombo.Visible && + (e.Position.X < lstCombo.AbsoluteLeft || + e.Position.X > lstCombo.AbsoluteLeft + lstCombo.Width || + e.Position.Y < lstCombo.AbsoluteTop || + e.Position.Y > lstCombo.AbsoluteTop + lstCombo.Height) && + (e.Position.X < btnDown.AbsoluteLeft || + e.Position.X > btnDown.AbsoluteLeft + btnDown.Width || + e.Position.Y < btnDown.AbsoluteTop || + e.Position.Y > btnDown.AbsoluteTop + btnDown.Height)) + { + //lstCombo.Visible = false; + btnDown_Click(sender, e); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void lstCombo_Click(object sender, EventArgs e) + { + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + + if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None) + { + lstCombo.Visible = false; + if (lstCombo.ItemIndex >= 0) + { + Text = lstCombo.Items[lstCombo.ItemIndex].ToString(); + Focused = true; + ItemIndex = lstCombo.ItemIndex; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Keys.Down) + { + e.Handled = true; + btnDown_Click(this, new MouseEventArgs()); + } + base.OnKeyDown(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnGamePadDown(GamePadEventArgs e) + { + if (!e.Handled) + { + if (e.Button == GamePadActions.Click || e.Button == GamePadActions.Press || e.Button == GamePadActions.Down) + { + e.Handled = true; + btnDown_Click(this, new MouseEventArgs()); + } + } + base.OnGamePadDown(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (ReadOnly && e.Button == MouseButton.Left) + { + btnDown_Click(this, new MouseEventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMaxItemsChanged(EventArgs e) + { + if (MaxItemsChanged != null) MaxItemsChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnItemIndexChanged(EventArgs e) + { + if (ItemIndexChanged != null) ItemIndexChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void lstCombo_FocusLost(object sender, EventArgs e) + { + //lstCombo.Visible = false; + Invalidate(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void AdjustMargins() + { + base.AdjustMargins(); + ClientMargins = new Margins(ClientMargins.Left, ClientMargins.Top, ClientMargins.Right + 16, ClientMargins.Bottom); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Component.cs b/Neoforce/Component.cs new file mode 100644 index 0000000..20a571e --- /dev/null +++ b/Neoforce/Component.cs @@ -0,0 +1,105 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Component.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class Component: Disposable + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Manager manager = null; + private bool initialized = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Manager Manager { get { return manager; } set { manager = value; } } + public virtual bool Initialized { get { return initialized; } } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public Component(Manager manager) + { + if (manager != null) + { + this.manager = manager; + } + else + { + throw new Exception("Component cannot be created. Manager instance is needed."); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Init() + { + initialized = true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal virtual void Update(GameTime gameTime) + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Console.cs b/Neoforce/Console.cs new file mode 100644 index 0000000..596c274 --- /dev/null +++ b/Neoforce/Console.cs @@ -0,0 +1,595 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Console.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections; +using System.Collections.Generic; +using Microsoft.Xna.Framework.GamerServices; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public struct ConsoleMessage + { + public string Text; + public byte Channel; + public DateTime Time; + public string Sender; + + public ConsoleMessage(string sender, string text, byte channel) + { + this.Text = text; + this.Channel = channel; + this.Time = DateTime.Now; + this.Sender = sender; + } + } + + public class ChannelList : EventedList + { + + #region //// Indexers ////////// + + //////////////////////////////////////////////////////////////////////////// + public ConsoleChannel this[string name] + { + get + { + for (int i = 0; i < this.Count; i++) + { + ConsoleChannel s = (ConsoleChannel)this[i]; + if (s.Name.ToLower() == name.ToLower()) + { + return s; + } + } + return default(ConsoleChannel); + } + + set + { + for (int i = 0; i < this.Count; i++) + { + ConsoleChannel s = (ConsoleChannel)this[i]; + if (s.Name.ToLower() == name.ToLower()) + { + this[i] = value; + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public ConsoleChannel this[byte index] + { + get + { + for (int i = 0; i < this.Count; i++) + { + ConsoleChannel s = (ConsoleChannel)this[i]; + if (s.Index == index) + { + return s; + } + } + return default(ConsoleChannel); + } + + set + { + for (int i = 0; i < this.Count; i++) + { + ConsoleChannel s = (ConsoleChannel)this[i]; + if (s.Index == index) + { + this[i] = value; + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + + public class ConsoleChannel + { + private string name; + private byte index; + private Color color; + + public ConsoleChannel(byte index, string name, Color color) + { + this.name = name; + this.index = index; + this.color = color; + } + + public virtual byte Index + { + get { return index; } + set { index = value; } + } + + public virtual Color Color + { + get { return color; } + set { color = value; } + } + + public virtual string Name + { + get { return name; } + set { name = value; } + } + } + + [Flags] + public enum ConsoleMessageFormats + { + None = 0x00, + ChannelName = 0x01, + TimeStamp = 0x02, + Sender = 0x03, + All = Sender | ChannelName | TimeStamp + } + + public class Console : Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private TextBox txtMain = null; + private ComboBox cmbMain; + private EventedList buffer = new EventedList(); + private ChannelList channels = new ChannelList(); + private List filter = new List(); + private ConsoleMessageFormats messageFormat = ConsoleMessageFormats.None; + private bool channelsVisible = true; + private bool textBoxVisible = true; + private string sender; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + public string Sender + { + get { return sender; } + set { sender = value; } + } + + //////////////////////////////////////////////////////////////////////////// + public virtual EventedList MessageBuffer + { + get { return buffer; } + set + { + buffer.ItemAdded -= new EventHandler(buffer_ItemAdded); + buffer = value; + buffer.ItemAdded += new EventHandler(buffer_ItemAdded); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ChannelList Channels + { + get { return channels; } + set + { + channels.ItemAdded -= new EventHandler(channels_ItemAdded); + channels = value; + channels.ItemAdded += new EventHandler(channels_ItemAdded); + channels_ItemAdded(null, null); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual List ChannelFilter + { + get { return filter; } + set { filter = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual byte SelectedChannel + { + set { cmbMain.Text = channels[value].Name; } + get { return channels[cmbMain.Text].Index; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ConsoleMessageFormats MessageFormat + { + get { return messageFormat; } + set { messageFormat = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool ChannelsVisible + { + get { return channelsVisible; } + set + { + cmbMain.Visible = channelsVisible = value; + if (value && !textBoxVisible) TextBoxVisible = false; + PositionControls(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool TextBoxVisible + { + get { return textBoxVisible; } + set + { + txtMain.Visible = textBoxVisible = value; + txtMain.Focused = true; + if (!value && channelsVisible) ChannelsVisible = false; + PositionControls(); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event ConsoleMessageEventHandler MessageSent; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public Console(Manager manager) + : base(manager) + { + Width = 320; + Height = 160; + MinimumHeight = 64; + MinimumWidth = 64; + CanFocus = false; + Resizable = false; + Movable = false; + + cmbMain = new ComboBox(manager); + cmbMain.Init(); + cmbMain.Top = Height - cmbMain.Height; + cmbMain.Left = 0; + cmbMain.Width = 128; + cmbMain.Anchor = Anchors.Left | Anchors.Bottom; + cmbMain.Detached = false; + cmbMain.DrawSelection = false; + cmbMain.Visible = channelsVisible; + Add(cmbMain, false); + + txtMain = new TextBox(manager); + txtMain.Init(); + txtMain.Top = Height - txtMain.Height; + txtMain.Left = cmbMain.Width + 1; + txtMain.Anchor = Anchors.Left | Anchors.Bottom | Anchors.Right; + txtMain.Detached = false; + txtMain.Visible = textBoxVisible; + txtMain.KeyDown += new KeyEventHandler(txtMain_KeyDown); + txtMain.GamePadDown += new GamePadEventHandler(txtMain_GamePadDown); + txtMain.FocusGained += new EventHandler(txtMain_FocusGained); + Add(txtMain, false); + + VerticalScrollBar.Top = 2; + VerticalScrollBar.Left = Width - 18; + VerticalScrollBar.Range = 1; + VerticalScrollBar.PageSize = 1; + VerticalScrollBar.ValueChanged += new EventHandler(VerticalScrollBar_ValueChanged); + VerticalScrollBar.Visible = true; + + ClientArea.Draw += new DrawEventHandler(ClientArea_Draw); + + buffer.ItemAdded += new EventHandler(buffer_ItemAdded); + channels.ItemAdded += new EventHandler(channels_ItemAdded); + channels.ItemRemoved += new EventHandler(channels_ItemRemoved); + + PositionControls(); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + private void PositionControls() + { + if (txtMain != null) + { + txtMain.Left = channelsVisible ? cmbMain.Width + 1 : 0; + txtMain.Width = channelsVisible ? Width - cmbMain.Width - 1 : Width; + + if (textBoxVisible) + { + ClientMargins = new Margins(Skin.ClientMargins.Left, Skin.ClientMargins.Top + 4, VerticalScrollBar.Width + 6, txtMain.Height + 4); + VerticalScrollBar.Height = Height - txtMain.Height - 5; + } + else + { + ClientMargins = new Margins(Skin.ClientMargins.Left, Skin.ClientMargins.Top + 4, VerticalScrollBar.Width + 6, 2); + VerticalScrollBar.Height = Height - 4; + } + Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["Console"]); + + PositionControls(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void ClientArea_Draw(object sender, DrawEventArgs e) + { + SpriteFont font = Skin.Layers[0].Text.Font.Resource; + Rectangle r = new Rectangle(e.Rectangle.Left, e.Rectangle.Top, e.Rectangle.Width, e.Rectangle.Height); + int pos = 0; + + if (buffer.Count > 0) + { + EventedList b = GetFilteredBuffer(filter); + int c = b.Count; + int s = (VerticalScrollBar.Value + VerticalScrollBar.PageSize); + int f = s - VerticalScrollBar.PageSize; + + if (b.Count > 0) + { + for (int i = s - 1; i >= f; i--) + { + { + int x = 4; + int y = r.Bottom - (pos + 1) * ((int)font.LineSpacing + 0); + + string msg = ((ConsoleMessage)b[i]).Text; + string pre = ""; + ConsoleChannel ch = (channels[((ConsoleMessage)b[i]).Channel] as ConsoleChannel); + + if ((messageFormat & ConsoleMessageFormats.ChannelName) == ConsoleMessageFormats.ChannelName) + { + pre += string.Format("[{0}]", channels[((ConsoleMessage)b[i]).Channel].Name); + } + if ((messageFormat & ConsoleMessageFormats.Sender) == ConsoleMessageFormats.Sender) + { + pre += string.Format("[{0}]", ((ConsoleMessage)b[i]).Sender); + } + if ((messageFormat & ConsoleMessageFormats.TimeStamp) == ConsoleMessageFormats.TimeStamp) + { + pre = string.Format("[{0}]", ((ConsoleMessage)b[i]).Time.ToLongTimeString()) + pre; + } + + if (pre != "") msg = pre + ": " + msg; + + e.Renderer.DrawString(font, + msg, + x, y, + ch.Color); + pos += 1; + } + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + int h = txtMain.Visible ? (txtMain.Height + 1) : 0; + Rectangle r = new Rectangle(rect.Left, rect.Top, rect.Width, rect.Height - h); + base.DrawControl(renderer, r, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void txtMain_FocusGained(object sender, EventArgs e) + { + ConsoleChannel ch = channels[cmbMain.Text]; + if (ch != null) txtMain.TextColor = ch.Color; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void txtMain_KeyDown(object sender, KeyEventArgs e) + { + SendMessage(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void txtMain_GamePadDown(object sender, GamePadEventArgs e) + { + SendMessage(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void SendMessage(EventArgs x) + { + if (Manager.UseGuide && Guide.IsVisible) return; + + KeyEventArgs k = new KeyEventArgs(); + GamePadEventArgs g = new GamePadEventArgs(PlayerIndex.One); + + if (x is KeyEventArgs) k = x as KeyEventArgs; + else if (x is GamePadEventArgs) g = x as GamePadEventArgs; + + ConsoleChannel ch = channels[cmbMain.Text]; + if (ch != null) + { + txtMain.TextColor = ch.Color; + + string message = txtMain.Text; + if ((k.Key == Microsoft.Xna.Framework.Input.Keys.Enter || g.Button == GamePadActions.Press) && message != null && message != "") + { + x.Handled = true; + + ConsoleMessageEventArgs me = new ConsoleMessageEventArgs(new ConsoleMessage(sender, message, ch.Index)); + OnMessageSent(me); + + buffer.Add(new ConsoleMessage(sender, me.Message.Text, me.Message.Channel)); + + txtMain.Text = ""; + ClientArea.Invalidate(); + + CalcScrolling(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMessageSent(ConsoleMessageEventArgs e) + { + if (MessageSent != null) MessageSent.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void channels_ItemAdded(object sender, EventArgs e) + { + cmbMain.Items.Clear(); + for (int i = 0; i < channels.Count; i++) + { + cmbMain.Items.Add((channels[i] as ConsoleChannel).Name); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void channels_ItemRemoved(object sender, EventArgs e) + { + cmbMain.Items.Clear(); + for (int i = 0; i < channels.Count; i++) + { + cmbMain.Items.Add((channels[i] as ConsoleChannel).Name); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void buffer_ItemAdded(object sender, EventArgs e) + { + CalcScrolling(); + ClientArea.Invalidate(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void CalcScrolling() + { + if (VerticalScrollBar != null) + { + int line = Skin.Layers[0].Text.Font.Resource.LineSpacing; + int c = GetFilteredBuffer(filter).Count; + int p = (int)Math.Ceiling(ClientArea.ClientHeight / (float)line); + + VerticalScrollBar.Range = c == 0 ? 1 : c; + VerticalScrollBar.PageSize = c == 0 ? 1 : p; + VerticalScrollBar.Value = VerticalScrollBar.Range; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void VerticalScrollBar_ValueChanged(object sender, EventArgs e) + { + ClientArea.Invalidate(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + CalcScrolling(); + base.OnResize(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private EventedList GetFilteredBuffer(List filter) + { + EventedList ret = new EventedList(); + + if (filter.Count > 0) + { + for (int i = 0; i < buffer.Count; i++) + { + if (filter.Contains(((ConsoleMessage)buffer[i]).Channel)) + { + ret.Add(buffer[i]); + } + } + return ret; + } + else return buffer; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Container.cs b/Neoforce/Container.cs new file mode 100644 index 0000000..33f85f1 --- /dev/null +++ b/Neoforce/Container.cs @@ -0,0 +1,554 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Container.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public struct ScrollBarValue + { + public int Vertical; + public int Horizontal; + } + + public class Container: ClipControl + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private ScrollBar sbVert; + private ScrollBar sbHorz; + private MainMenu mainMenu; + private ToolBarPanel toolBarPanel; + private StatusBar statusBar; + private bool autoScroll = false; + private Control defaultControl = null; + + /// + /// Scroll by PageSize (true) or StepSize (false) + /// + private bool scrollAlot = true; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ScrollBarValue ScrollBarValue + { + get + { + ScrollBarValue scb = new ScrollBarValue(); + scb.Vertical = (sbVert != null ? sbVert.Value : 0); + scb.Horizontal = (sbHorz != null ? sbHorz.Value : 0); + return scb; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override bool Visible + { + get + { + return base.Visible; + } + set + { + if (value) + { + if (DefaultControl != null) + { + DefaultControl.Focused = true; + } + } + base.Visible = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Control DefaultControl + { + get { return defaultControl; } + set { defaultControl = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool AutoScroll + { + get { return autoScroll; } + set { autoScroll = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual MainMenu MainMenu + { + get { return mainMenu; } + set + { + if (mainMenu != null) + { + mainMenu.Resize -= Bars_Resize; + Remove(mainMenu); + } + mainMenu = value; + + if (mainMenu != null) + { + Add(mainMenu, false); + mainMenu.Resize += Bars_Resize; + } + AdjustMargins(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ToolBarPanel ToolBarPanel + { + get + { + return toolBarPanel; + } + set + { + if (toolBarPanel != null) + { + toolBarPanel.Resize -= Bars_Resize; + Remove(toolBarPanel); + } + toolBarPanel = value; + + if (toolBarPanel != null) + { + Add(toolBarPanel, false); + toolBarPanel.Resize += Bars_Resize; + } + AdjustMargins(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual StatusBar StatusBar + { + get + { + return statusBar; + } + set + { + if (statusBar != null) + { + statusBar.Resize -= Bars_Resize; + Remove(statusBar); + } + statusBar = value; + + if (statusBar != null) + { + Add(statusBar, false); + statusBar.Resize += Bars_Resize; + } + AdjustMargins(); + } + } + //////////////////////////////////////////////////////////////////////////// + + /// + /// Scroll by PageSize (true) or StepSize (false) + /// + public virtual bool ScrollAlot + { + get { return this.scrollAlot; } + set { this.scrollAlot = value; } + } + + /// + /// Gets the container's vertical scroll bar. + /// + protected virtual ScrollBar VerticalScrollBar + { + get { return this.sbVert; } + } + + /// + /// Gets the container's horizontal scroll bar. + /// + protected virtual ScrollBar HorizontalScrollBar + { + get { return this.sbHorz; } + } + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public Container(Manager manager): base(manager) + { + sbVert = new ScrollBar(manager, Orientation.Vertical); + sbVert.Init(); + sbVert.Detached = false; + sbVert.Anchor = Anchors.Top | Anchors.Right | Anchors.Bottom; + sbVert.ValueChanged += new EventHandler(ScrollBarValueChanged); + sbVert.Range = 0; + sbVert.PageSize = 0; + sbVert.Value = 0; + sbVert.Visible = false; + + sbHorz = new ScrollBar(manager, Orientation.Horizontal); + sbHorz.Init(); + sbHorz.Detached = false; + sbHorz.Anchor = Anchors.Right | Anchors.Left | Anchors.Bottom; + sbHorz.ValueChanged += new EventHandler(ScrollBarValueChanged); + sbHorz.Range = 0; + sbHorz.PageSize = 0; + sbHorz.Value = 0; + sbHorz.Visible = false; + + Add(sbVert, false); + Add(sbHorz, false); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void Bars_Resize(object sender, ResizeEventArgs e) + { + AdjustMargins(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void AdjustMargins() + { + Margins m = Skin.ClientMargins; + + if (this.GetType() != typeof(Container)) + { + m = ClientMargins; + } + + if (mainMenu != null && mainMenu.Visible) + { + if (!mainMenu.Initialized) mainMenu.Init(); + mainMenu.Left = m.Left; + mainMenu.Top = m.Top; + mainMenu.Width = Width - m.Horizontal; + mainMenu.Anchor = Anchors.Left | Anchors.Top | Anchors.Right; + + m.Top += mainMenu.Height; + } + if (toolBarPanel != null && toolBarPanel.Visible) + { + if (!toolBarPanel.Initialized) toolBarPanel.Init(); + toolBarPanel.Left = m.Left; + toolBarPanel.Top = m.Top; + toolBarPanel.Width = Width - m.Horizontal; + toolBarPanel.Anchor = Anchors.Left | Anchors.Top | Anchors.Right; + + m.Top += toolBarPanel.Height; + } + if (statusBar != null && statusBar.Visible) + { + if (!statusBar.Initialized) statusBar.Init(); + statusBar.Left = m.Left; + statusBar.Top = Height - m.Bottom - statusBar.Height; + statusBar.Width = Width - m.Horizontal; + statusBar.Anchor = Anchors.Left | Anchors.Bottom | Anchors.Right; + + m.Bottom += statusBar.Height; + } + if (sbVert != null && sbVert.Visible) + { + m.Right += (sbVert.Width + 2); + } + if (sbHorz != null && sbHorz.Visible) + { + m.Bottom += (sbHorz.Height + 2); + } + + ClientMargins = m; + + PositionScrollBars(); + + base.AdjustMargins(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Add(Control control, bool client) + { + base.Add(control, client); + CalcScrolling(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void OnSkinChanged(EventArgs e) + { + base.OnSkinChanged(e); + if (sbVert != null && sbHorz != null) + { + sbVert.Visible = false; + sbHorz.Visible = false; + CalcScrolling(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void PositionScrollBars() + { + if (sbVert != null) + { + sbVert.Left = ClientLeft + ClientWidth + 1; + sbVert.Top = ClientTop + 1; + int m = (sbHorz != null && sbHorz.Visible) ? 0 : 2; + sbVert.Height = ClientArea.Height - m; + sbVert.Range = ClientArea.VirtualHeight; + sbVert.PageSize = ClientArea.ClientHeight; + } + + if (sbHorz != null) + { + sbHorz.Left = ClientLeft + 1; + sbHorz.Top = ClientTop + ClientHeight + 1; + int m = (sbVert != null && sbVert.Visible) ? 0 : 2; + sbHorz.Width = ClientArea.Width - m; + sbHorz.Range = ClientArea.VirtualWidth; + sbHorz.PageSize = ClientArea.ClientWidth; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void CalcScrolling() + { + if (sbVert != null && autoScroll) + { + bool vis = sbVert.Visible; + sbVert.Visible = ClientArea.VirtualHeight > ClientArea.ClientHeight; + if (ClientArea.VirtualHeight <= ClientArea.ClientHeight) sbVert.Value = 0; + + if (vis != sbVert.Visible) + { + if (!sbVert.Visible) + { + foreach (Control c in ClientArea.Controls) + { + c.TopModifier = 0; + c.Invalidate(); + } + } + AdjustMargins(); + } + + PositionScrollBars(); + foreach (Control c in ClientArea.Controls) + { + c.TopModifier = -sbVert.Value; + c.Invalidate(); + } + } + + if (sbHorz != null && autoScroll) + { + bool vis = sbHorz.Visible; + sbHorz.Visible = ClientArea.VirtualWidth > ClientArea.ClientWidth; + if (ClientArea.VirtualWidth <= ClientArea.ClientWidth) sbHorz.Value = 0; + + if (vis != sbHorz.Visible) + { + if (!sbHorz.Visible) + { + foreach (Control c in ClientArea.Controls) + { + c.LeftModifier = 0; + sbVert.Refresh(); + c.Invalidate(); + } + } + AdjustMargins(); + } + + PositionScrollBars(); + foreach (Control c in ClientArea.Controls) + { + c.LeftModifier = -sbHorz.Value; + sbHorz.Refresh(); + c.Invalidate(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void ScrollTo(int x, int y) + { + sbVert.Value = y; + sbHorz.Value = x; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void ScrollTo(Control control) + { + if (control != null && ClientArea != null && ClientArea.Contains(control, true)) + { + if (control.AbsoluteTop + control.Height > ClientArea.AbsoluteTop + ClientArea.Height) + { + sbVert.Value = sbVert.Value + control.AbsoluteTop - ClientArea.AbsoluteTop - sbVert.PageSize + control.Height; + } + else if (control.AbsoluteTop < ClientArea.AbsoluteTop) + { + sbVert.Value = sbVert.Value + control.AbsoluteTop - ClientArea.AbsoluteTop; + } + if (control.AbsoluteLeft + control.Width > ClientArea.AbsoluteLeft + ClientArea.Width) + { + sbHorz.Value = sbHorz.Value + control.AbsoluteLeft - ClientArea.AbsoluteLeft - sbHorz.PageSize + control.Width; + } + else if (control.AbsoluteLeft < ClientArea.AbsoluteLeft) + { + sbHorz.Value = sbHorz.Value + control.AbsoluteLeft - ClientArea.AbsoluteLeft; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void ScrollBarValueChanged(object sender, EventArgs e) + { + CalcScrolling(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + CalcScrolling(); + + // Crappy fix to certain scrolling issue + //if (sbVert != null) sbVert.Value -= 1; + //if (sbHorz != null) sbHorz.Value -= 1; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Invalidate() + { + base.Invalidate(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnClick(EventArgs e) + { + MouseEventArgs ex = e as MouseEventArgs; + ex.Position = new Point(ex.Position.X + sbHorz.Value, ex.Position.Y + sbVert.Value); + + base.OnClick(e); + } + //////////////////////////////////////////////////////////////////////////// + + protected override void OnMouseScroll(MouseEventArgs e) + { + if (!ClientArea.Enabled) + return; + + // If current control doesn't scroll, scroll the parent control + if (sbVert.Range - sbVert.PageSize < 1) + { + Control c = this; + + while (c != null) + { + var p = c.Parent as Container; + + if (p != null && p.Enabled) + { + p.OnMouseScroll(e); + + break; + } + + c = c.Parent; + } + + return; + } + + if (e.ScrollDirection == MouseScrollDirection.Down) + sbVert.ScrollDown(ScrollAlot); + else + sbVert.ScrollUp(ScrollAlot); + + base.OnMouseScroll(e); + } + #endregion + } + +} diff --git a/Neoforce/ContentReaders.cs b/Neoforce/ContentReaders.cs new file mode 100644 index 0000000..1c6b33f --- /dev/null +++ b/Neoforce/ContentReaders.cs @@ -0,0 +1,155 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ContentReaders.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +////////////////////////////////////////////////////////////////////////////// +using System; +using System.IO; +using System.Xml; +using Microsoft.Xna.Framework.Content; + +#if (!XBOX && !XBOX_FAKE) +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; +#endif +////////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + //////////////////////////////////////////////////////////////////////////// + public class LayoutXmlDocument : XmlDocument { } + public class SkinXmlDocument : XmlDocument { } + //////////////////////////////////////////////////////////////////////////// + + + public class SkinReader : ContentTypeReader + { + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected override SkinXmlDocument Read(ContentReader input, SkinXmlDocument existingInstance) + { + if (existingInstance == null) + { + SkinXmlDocument doc = new SkinXmlDocument(); + doc.LoadXml(input.ReadString()); + return doc; + } + else + { + existingInstance.LoadXml(input.ReadString()); + } + + return existingInstance; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + + public class LayoutReader : ContentTypeReader + { + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected override LayoutXmlDocument Read(ContentReader input, LayoutXmlDocument existingInstance) + { + if (existingInstance == null) + { + LayoutXmlDocument doc = new LayoutXmlDocument(); + doc.LoadXml(input.ReadString()); + return doc; + } + else + { + existingInstance.LoadXml(input.ReadString()); + } + + return existingInstance; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +#if (!XBOX && !XBOX_FAKE) + + public class CursorReader : ContentTypeReader + { + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected override Cursor Read(ContentReader input, Cursor existingInstance) + { + if (existingInstance == null) + { + int count = input.ReadInt32(); + byte[] data = input.ReadBytes(count); + + string path = Path.GetTempFileName(); + File.WriteAllBytes(path, data); + string tPath = Path.GetTempFileName(); + using(System.Drawing.Icon i = System.Drawing.Icon.ExtractAssociatedIcon(path)) + { + using (System.Drawing.Bitmap b = i.ToBitmap()) + { + + b.Save(tPath, System.Drawing.Imaging.ImageFormat.Png); + b.Dispose(); + } + + i.Dispose(); + } + //TODO: Replace with xml based solution for getting hotspot and size instead + IntPtr handle = NativeMethods.LoadCursor(path); + System.Windows.Forms.Cursor c = new System.Windows.Forms.Cursor(handle); + Vector2 hs = new Vector2(c.HotSpot.X, c.HotSpot.Y); + int w = c.Size.Width; + int h = c.Size.Height; + c.Dispose(); + File.Delete(path); + + return new Cursor(tPath, hs, w, h); + } + else + { + } + + return existingInstance; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +#endif + +} + diff --git a/Neoforce/ContextMenu.cs b/Neoforce/ContextMenu.cs new file mode 100644 index 0000000..9265dbc --- /dev/null +++ b/Neoforce/ContextMenu.cs @@ -0,0 +1,575 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ContextMenu.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ContextMenu: MenuBase + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private long timer = 0; + private Control sender = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + protected internal Control Sender { get { return sender; } set { sender = value; } } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ContextMenu(Manager manager): base(manager) + { + Visible = false; + Detached = true; + StayOnBack = true; + + Manager.Input.MouseDown += new MouseEventHandler(Input_MouseDown); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + Manager.Input.MouseDown -= Input_MouseDown; + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["ContextMenu"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + + SkinLayer l1 = Skin.Layers["Control"]; + SkinLayer l2 = Skin.Layers["Selection"]; + + int vsize = LineHeight(); + Color col = Color.White; + + for (int i = 0; i < Items.Count; i++) + { + int mod = i > 0 ? 2 : 0; + int left = rect.Left + l1.ContentMargins.Left + vsize; + int h = vsize - mod - (i < (Items.Count - 1) ? 1 : 0); + int top = rect.Top + l1.ContentMargins.Top + (i * vsize) + mod; + + + if (Items[i].Separated && i > 0) + { + Rectangle r = new Rectangle(left, rect.Top + l1.ContentMargins.Top + (i * vsize), LineWidth() - vsize + 4, 1); + renderer.Draw(Manager.Skin.Controls["Control"].Layers[0].Image.Resource, r, l1.Text.Colors.Enabled); + } + if (ItemIndex != i) + { + if (Items[i].Enabled) + { + Rectangle r = new Rectangle(left, top, LineWidth() - vsize, h); + renderer.DrawString(this, l1, Items[i].Text, r, false); + col = l1.Text.Colors.Enabled; + } + else + { + Rectangle r = new Rectangle(left + l1.Text.OffsetX, + top + l1.Text.OffsetY, + LineWidth() - vsize, h); + renderer.DrawString(l1.Text.Font.Resource, Items[i].Text, r, l1.Text.Colors.Disabled, l1.Text.Alignment); + col = l1.Text.Colors.Disabled; + } + } + else + { + if (Items[i].Enabled) + { + Rectangle rs = new Rectangle(rect.Left + l1.ContentMargins.Left, + top, + Width - (l1.ContentMargins.Horizontal - Skin.OriginMargins.Horizontal), + h); + renderer.DrawLayer(this, l2, rs); + + Rectangle r = new Rectangle(left, + top, LineWidth() - vsize, h); + + renderer.DrawString(this, l2, Items[i].Text, r, false); + col = l2.Text.Colors.Enabled; + } + else + { + Rectangle rs = new Rectangle(rect.Left + l1.ContentMargins.Left, + top, + Width - (l1.ContentMargins.Horizontal - Skin.OriginMargins.Horizontal), + vsize); + renderer.DrawLayer(l2, rs, l2.States.Disabled.Color, l2.States.Disabled.Index); + + Rectangle r = new Rectangle(left + l1.Text.OffsetX, + top + l1.Text.OffsetY, + LineWidth() - vsize, h); + renderer.DrawString(l2.Text.Font.Resource, Items[i].Text, r, l2.Text.Colors.Disabled, l2.Text.Alignment); + col = l2.Text.Colors.Disabled; + } + + } + + if (Items[i].Image != null) + { + Rectangle r = new Rectangle(rect.Left + l1.ContentMargins.Left + 3, + rect.Top + top + 3, + LineHeight() - 6, + LineHeight() - 6); + renderer.Draw(Items[i].Image, r, Color.White); + } + + if (Items[i].Items != null && Items[i].Items.Count > 0) + { + renderer.Draw(Manager.Skin.Images["Shared.ArrowRight"].Resource, rect.Left + LineWidth() - 4, rect.Top + l1.ContentMargins.Top + (i * vsize) + 8, col); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int LineHeight() + { + int h = 0; + if (Items.Count > 0) + { + SkinLayer l = Skin.Layers["Control"]; + h = (int)l.Text.Font.Resource.LineSpacing + 9; + } + return h; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int LineWidth() + { + int w = 0; + SkinFont font = Skin.Layers["Control"].Text.Font; + if (Items.Count > 0) + { + for (int i = 0; i < Items.Count; i++) + { + int wx = (int)font.Resource.MeasureString(Items[i].Text).X + 16; + if (wx > w) w = wx; + } + } + + w += 4 + LineHeight(); + + return w; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void AutoSize() + { + SkinText font = Skin.Layers["Control"].Text; + if (Items != null && Items.Count > 0) + { + Height = (LineHeight() * Items.Count) + (Skin.Layers["Control"].ContentMargins.Vertical - Skin.OriginMargins.Vertical); + Width = LineWidth() + (Skin.Layers["Control"].ContentMargins.Horizontal - Skin.OriginMargins.Horizontal) + font.OffsetX; + } + else + { + Height = 16; + Width = 16; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void TrackItem(int x, int y) + { + if (Items != null && Items.Count > 0) + { + SkinText font = Skin.Layers["Control"].Text; + int h = LineHeight(); + y -= Skin.Layers["Control"].ContentMargins.Top; + int i = (int)((float)y / h); + if (i < Items.Count) + { + if (i != ItemIndex && Items[i].Enabled) + { + if (ChildMenu != null) + { + this.HideMenu(false); + } + + if (i >= 0 && i != ItemIndex) + { + Items[i].SelectedInvoke(new EventArgs()); + } + + Focused = true; + ItemIndex = i; + timer = (long)TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds; + } + else if (!Items[i].Enabled && ChildMenu == null) + { + ItemIndex = -1; + } + } + Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + TrackItem(e.Position.X, e.Position.Y); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + + AutoSize(); + + long time = (long)TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds; + + if (timer != 0 && time - timer >= Manager.MenuDelay && ItemIndex >= 0 && Items[ItemIndex].Items.Count > 0 && ChildMenu == null) + { + OnClick(new MouseEventArgs(new MouseState(), MouseButton.Left, Point.Zero)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseOut(MouseEventArgs e) + { + base.OnMouseOut(e); + + if (!CheckArea(e.State.X, e.State.Y) && ChildMenu == null) + { + ItemIndex = -1; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnClick(EventArgs e) + { + if (sender != null && !(sender is MenuBase)) sender.Focused = true; + base.OnClick(e); + timer = 0; + + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + + if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None) + { + if (ItemIndex >= 0 && Items[ItemIndex].Enabled) + { + if (ItemIndex >= 0 && Items[ItemIndex].Items != null && Items[ItemIndex].Items.Count > 0) + { + if (ChildMenu == null) + { + ChildMenu = new ContextMenu(Manager); + (ChildMenu as ContextMenu).RootMenu = this.RootMenu; + (ChildMenu as ContextMenu).ParentMenu = this; + (ChildMenu as ContextMenu).sender = sender; + ChildMenu.Items.AddRange(Items[ItemIndex].Items); + (ChildMenu as ContextMenu).AutoSize(); + } + int y = AbsoluteTop + Skin.Layers["Control"].ContentMargins.Top + (ItemIndex * LineHeight()); + (ChildMenu as ContextMenu).Show(sender, AbsoluteLeft + Width - 1, y); + if (ex.Button == MouseButton.None) (ChildMenu as ContextMenu).ItemIndex = 0; + } + else + { + if (ItemIndex >= 0) + { + Items[ItemIndex].ClickInvoke(ex); + } + if (RootMenu is ContextMenu) (RootMenu as ContextMenu).HideMenu(true); + else if (RootMenu is MainMenu) + { + (RootMenu as MainMenu).HideMenu(); + } + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnKeyPress(KeyEventArgs e) + { + base.OnKeyPress(e); + + timer = 0; + + if (e.Key == Keys.Down || (e.Key == Keys.Tab && !e.Shift)) + { + e.Handled = true; + ItemIndex += 1; + } + + if (e.Key == Keys.Up || (e.Key == Keys.Tab && e.Shift)) + { + e.Handled = true; + ItemIndex -=1; + } + + if (ItemIndex > Items.Count - 1) ItemIndex = 0; + if (ItemIndex < 0) ItemIndex = Items.Count - 1; + + if (e.Key == Keys.Right && Items[ItemIndex].Items.Count > 0) + { + e.Handled = true; + OnClick(new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + if (e.Key == Keys.Left) + { + e.Handled = true; + if (ParentMenu != null && ParentMenu is ContextMenu) + { + (ParentMenu as ContextMenu).Focused = true; + (ParentMenu as ContextMenu).HideMenu(false); + } + } + if (e.Key == Keys.Escape) + { + e.Handled = true; + if (ParentMenu != null) ParentMenu.Focused = true; + HideMenu(true); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnGamePadPress(GamePadEventArgs e) + { + timer = 0; + + if (e.Button == GamePadButton.None) return; + + if (e.Button == GamePadActions.Down || e.Button == GamePadActions.NextControl) + { + e.Handled = true; + ItemIndex += 1; + } + else if (e.Button == GamePadActions.Up || e.Button == GamePadActions.PrevControl) + { + e.Handled = true; + ItemIndex -= 1; + } + + if (ItemIndex > Items.Count - 1) ItemIndex = 0; + if (ItemIndex < 0) ItemIndex = Items.Count - 1; + + if (e.Button == GamePadActions.Right && Items[ItemIndex].Items.Count > 0) + { + e.Handled = true; + OnClick(new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + if (e.Button == GamePadActions.Left) + { + e.Handled = true; + if (ParentMenu != null && ParentMenu is ContextMenu) + { + (ParentMenu as ContextMenu).Focused = true; + (ParentMenu as ContextMenu).HideMenu(false); + } + } + + base.OnGamePadPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void HideMenu(bool hideCurrent) + { + if (hideCurrent) + { + Visible = false; + ItemIndex = -1; + } + if (ChildMenu != null) + { + (ChildMenu as ContextMenu).HideMenu(true); + ChildMenu.Dispose(); + ChildMenu = null; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Show() + { + Show(null, Left, Top); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Show(Control sender, int x, int y) + { + AutoSize(); + base.Show(); + if (!Initialized) Init(); + if (sender != null && sender.Root != null && sender.Root is Container) + { + (sender.Root as Container).Add(this, false); + } + else + { + Manager.Add(this); + } + + this.sender = sender; + + if (sender != null && sender.Root != null && sender.Root is Container) + { + Left = x - Root.AbsoluteLeft; + Top = y - Root.AbsoluteTop; + } + else + { + Left = x; + Top = y; + } + + if (AbsoluteLeft + Width > Manager.TargetWidth) + { + Left = Left - Width; + if (ParentMenu != null && ParentMenu is ContextMenu) + { + Left = Left - ParentMenu.Width + 2; + } + else if (ParentMenu != null) + { + Left = Manager.TargetWidth - (Parent != null ? Parent.AbsoluteLeft : 0) - Width - 2; + } + } + if (AbsoluteTop + Height > Manager.TargetHeight) + { + Top = Top - Height; + if (ParentMenu != null && ParentMenu is ContextMenu) + { + Top = Top + LineHeight(); + } + else if (ParentMenu != null) + { + Top = ParentMenu.Top - Height - 1; + } + } + + Focused = true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void Input_MouseDown(object sender, MouseEventArgs e) + { + if ((RootMenu is ContextMenu) && !(RootMenu as ContextMenu).CheckArea(e.Position.X, e.Position.Y) && Visible) + { + HideMenu(true); + } + else if ((RootMenu is MainMenu) && RootMenu.ChildMenu != null && !(RootMenu.ChildMenu as ContextMenu).CheckArea(e.Position.X, e.Position.Y) && Visible) + { + (RootMenu as MainMenu).HideMenu(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckArea(int x, int y) + { + if (Visible) + { + if (x <= AbsoluteLeft || + x >= AbsoluteLeft + Width || + y <= AbsoluteTop || + y >= AbsoluteTop + Height) + { + bool ret = false; + if (ChildMenu != null) + { + ret = (ChildMenu as ContextMenu).CheckArea(x, y); + } + return ret; + } + else + { + return true; + } + } + else + { + return false; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Control.cs b/Neoforce/Control.cs new file mode 100644 index 0000000..d36e206 --- /dev/null +++ b/Neoforce/Control.cs @@ -0,0 +1,3045 @@ +///////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Control.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +#if (!XBOX && !XBOX_FAKE) + using System.Media; +using System.Collections; +using System.ComponentModel; +#endif +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Classes /////////// + + + //////////////////////////////////////////////////////////////////////////// + /// + /// Defines the gamepad actions mapping. + /// + public class GamePadActions + { + public GamePadButton Click = GamePadButton.A; + public GamePadButton Press = GamePadButton.Y; + public GamePadButton Left = GamePadButton.LeftStickLeft; + public GamePadButton Right = GamePadButton.LeftStickRight; + public GamePadButton Up = GamePadButton.LeftStickUp; + public GamePadButton Down = GamePadButton.LeftStickDown; + public GamePadButton NextControl = GamePadButton.RightShoulder; + public GamePadButton PrevControl = GamePadButton.LeftShoulder; + public GamePadButton ContextMenu = GamePadButton.X; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Defines type used as a controls collection. + /// + public class ControlsList: EventedList + { + public ControlsList(): base() {} + public ControlsList(int capacity): base(capacity) {} + public ControlsList(IEnumerable collection): base(collection) {} + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Defines the base class for all controls. + /// + public class Control: Component + { + + #region //// Consts///////////// + + //////////////////////////////////////////////////////////////////////////// + public static readonly Color UndefinedColor = new Color(255, 255, 255, 0); + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + internal static ControlsList Stack = new ControlsList(); + //////////////////////////////////////////////////////////////////////////// + + #if (!XBOX && !XBOX_FAKE) + //////////////////////////////////////////////////////////////////////////// + private Cursor cursor = null; + //////////////////////////////////////////////////////////////////////////// + #endif + + //////////////////////////////////////////////////////////////////////////// + private Color color = UndefinedColor; + private Color textColor = UndefinedColor; + private Color backColor = Color.Transparent; + private byte alpha = 255; + private Anchors anchor = Anchors.Left | Anchors.Top; + private Anchors resizeEdge = Anchors.All; + private string text = "Control"; + private bool visible = true; + private bool enabled = true; + private SkinControl skin = null; + private Control parent = null; + private Control root = null; + private int left = 0; + private int top = 0; + private int width = 64; + private int height = 64; + private bool suspended = false; + private ContextMenu contextMenu = null; + private long tooltipTimer = 0; + private long doubleClickTimer = 0; + private MouseButton doubleClickButton = MouseButton.None; + private Type toolTipType = typeof(ToolTip); + private ToolTip toolTip = null; + private bool doubleClicks = true; + private bool outlineResizing = false; + private bool outlineMoving = false; + private string name = "Control"; + private object tag = null; + private GamePadActions gamePadActions = new GamePadActions(); + private bool designMode = false; + private bool partialOutline = true; + private Rectangle drawingRect = Rectangle.Empty; + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private ControlsList controls = new ControlsList(); + private Rectangle movableArea = Rectangle.Empty; + private bool passive = false; + private bool detached = false; + private bool movable = false; + private bool resizable = false; + private bool invalidated = true; + private bool canFocus = true; + private int resizerSize = 4; + private int minimumWidth = 0; + private int maximumWidth = 4096; + private int minimumHeight = 0; + private int maximumHeight = 4096; + private int topModifier = 0; + private int leftModifier = 0; + private int virtualHeight = 64; + private int virtualWidth = 64; + private bool stayOnBack = false; + private bool stayOnTop = false; + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private RenderTarget2D target; + private Point pressSpot = Point.Zero; + private int[] pressDiff = new int[4]; + private Alignment resizeArea = Alignment.None; + private bool hovered = false; + private bool inside = false; + private bool[] pressed = new bool[32]; + private bool isMoving = false; + private bool isResizing = false; + private Margins margins = new Margins(4, 4, 4, 4); + private Margins anchorMargins = new Margins(); + private Margins clientMargins = new Margins(); + private Rectangle outlineRect = Rectangle.Empty; + /// + /// Tracks the position of the mouse scroll wheel + /// + private int scrollWheel = 0; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + #if (!XBOX && !XBOX_FAKE) + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the cursor displaying over the control. + /// + public Cursor Cursor + { + get { return cursor; } + set { cursor = value; } + } + //////////////////////////////////////////////////////////////////////////// + #endif + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets a list of all child controls. + /// + public virtual IEnumerable Controls { get { return controls; } } + + /// + /// Gets or sets a rectangular area that reacts on moving the control with the mouse. + /// + public virtual Rectangle MovableArea { get { return movableArea; } set { movableArea = value; } } + + /// + /// Gets a value indicating whether this control is a child control. + /// + public virtual bool IsChild { get { return (parent != null); } } + + /// + /// Gets a value indicating whether this control is a parent control. + /// + public virtual bool IsParent { get { return (controls != null && controls.Count > 0); } } + + /// + /// Gets a value indicating whether this control is a root control. + /// + public virtual bool IsRoot { get { return (root == this); } } + + /// + /// Gets or sets a value indicating whether this control can receive focus. + /// + public virtual bool CanFocus { get { return canFocus; } set { canFocus = value; } } + + /// + /// Gets or sets a value indicating whether this control is rendered off the parents texture. + /// + public virtual bool Detached { get { return detached; } set { detached = value; } } + + /// + /// Gets or sets a value indicating whether this controls can receive user input events. + /// + public virtual bool Passive { get { return passive; } set { passive = value; } } + + /// + /// Gets or sets a value indicating whether this control can be moved by the mouse. + /// + public virtual bool Movable { get { return movable; } set { movable = value; } } + + /// + /// Gets or sets a value indicating whether this control can be resized by the mouse. + /// + public virtual bool Resizable { get { return resizable; } set { resizable = value; } } + + /// + /// Gets or sets the size of the rectangular borders around the control used for resizing by the mouse. + /// + public virtual int ResizerSize { get { return resizerSize; } set { resizerSize = value; } } + + /// + /// Gets or sets the ContextMenu associated with this control. + /// + public virtual ContextMenu ContextMenu { get { return contextMenu; } set { contextMenu = value; } } + + /// + /// Gets or sets a value indicating whether this control should process mouse double-clicks. + /// + public virtual bool DoubleClicks { get { return doubleClicks; } set { doubleClicks = value; } } + + /// + /// Gets or sets a value indicating whether this control should use ouline resizing. + /// + public virtual bool OutlineResizing { get { return outlineResizing; } set { outlineResizing = value; } } + + /// + /// Gets or sets a value indicating whether this control should use outline moving. + /// + public virtual bool OutlineMoving { get { return outlineMoving; } set { outlineMoving = value; } } + + /// + /// Gets or sets the object that contains data about the control. + /// + public virtual object Tag { get { return tag; } set { tag = value; } } + + /// + /// Gets or sets the value indicating the distance from another control. Usable with StackPanel control. + /// + public virtual Margins Margins { get { return margins; } set { margins = value; } } + + /// + /// Gets or sets the value indicating wheter control is in design mode. + /// + public virtual bool DesignMode { get { return designMode; } set { designMode = value; } } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets gamepad actions for the control. + /// + public virtual GamePadActions GamePadActions + { + get { return gamePadActions; } + set { gamePadActions = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the value indicating whether the control outline is displayed only for certain edges. + /// + public virtual bool PartialOutline + { + get { return partialOutline; } + set { partialOutline = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the value indicating whether the control is allowed to be brought in the front. + /// + public virtual bool StayOnBack + { + get { return stayOnBack; } + set + { + if (value && stayOnTop) stayOnTop = false; + stayOnBack = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the value indicating that the control should stay on top of other controls. + /// + public virtual bool StayOnTop + { + get { return stayOnTop; } + set + { + if (value && stayOnBack) stayOnBack = false; + stayOnTop = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a name of the control. + /// + public virtual string Name + { + get { return name; } + set { name = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating whether this control has input focus. + /// + public virtual bool Focused + { + get + { + return (Manager.FocusedControl == this); + } + set + { + this.Invalidate(); + if (value) + { + bool f = Focused; + Manager.FocusedControl = this; + if (!Suspended && value && !f) OnFocusGained(new EventArgs()); + if (Focused && Root != null && Root is Container) + { + (Root as Container).ScrollTo(this); + } + } + else + { + bool f = Focused; + if (Manager.FocusedControl == this) Manager.FocusedControl = null; + if (!Suspended && !value && f) OnFocusLost(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets a value indicating current state of the control. + /// + public virtual ControlState ControlState + { + get + { + if (DesignMode) return ControlState.Enabled; + else if (Suspended) return ControlState.Disabled; + else + { + if (!enabled) return ControlState.Disabled; + + if ((IsPressed && inside) || (Focused && IsPressed)) return ControlState.Pressed; + else if (hovered && !IsPressed) return ControlState.Hovered; + else if ((Focused && !inside) || (hovered && IsPressed && !inside) || (Focused && !hovered && inside)) return ControlState.Focused; + else return ControlState.Enabled; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Type ToolTipType + { + get { return toolTipType; } + set + { + toolTipType = value; + if (toolTip != null) + { + toolTip.Dispose(); + toolTip = null; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ToolTip ToolTip + { + get + { + if (toolTip == null) + { + Type[] t = new Type[1] {typeof(Manager)}; + object[] p = new object[1] {Manager}; + + toolTip = (ToolTip)toolTipType.GetConstructor(t).Invoke(p); + toolTip.Init(); + toolTip.Visible = false; + } + return toolTip; + } + set + { + toolTip = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal protected virtual bool IsPressed + { + get + { + for (int i = 0; i < pressed.Length - 1; i++) + { + if (pressed[i]) return true; + } + return false; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal virtual int TopModifier + { + get { return topModifier; } + set { topModifier = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal virtual int LeftModifier + { + get { return leftModifier; } + set { leftModifier = value; } + } + //////////////////////////////////////////////////////////////////////////// + internal virtual int VirtualHeight + { + get { return GetVirtualHeight(); } + //set { virtualHeight = value; } + } + //////////////////////////////////////////////////////////////////////////// + internal virtual int VirtualWidth + { + get { return GetVirtualWidth(); } + //set { virtualWidth = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets an area where is the control supposed to be drawn. + /// + public Rectangle DrawingRect + { + get { return drawingRect; } + private set { drawingRect = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating whether this control should receive any events. + /// + public virtual bool Suspended + { + get { return suspended; } + set { suspended = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal protected virtual bool Hovered + { + get { return hovered; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal protected virtual bool Inside + { + get { return inside; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal protected virtual bool[] Pressed + { + get { return pressed; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating whether this controls is currently being moved. + /// + protected virtual bool IsMoving + { + get + { + return isMoving; + } + set + { + isMoving = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating whether this controls is currently being resized. + /// + protected virtual bool IsResizing + { + get + { + return isResizing; + } + set + { + isResizing = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the edges of the container to which a control is bound and determines how a control is resized with its parent. + /// + public virtual Anchors Anchor + { + get + { + return anchor; + } + set + { + anchor = value; + SetAnchorMargins(); + if (!Suspended) OnAnchorChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the edges of the contol which are allowed for resizing. + /// + public virtual Anchors ResizeEdge + { + get + { + return resizeEdge; + } + set + { + resizeEdge = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the skin used for rendering the control. + /// + public virtual SkinControl Skin + { + get + { + return skin; + } + set + { + skin = value; + ClientMargins = skin.ClientMargins; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the text associated with this control. + /// + public virtual string Text + { + get { return text; } + set + { + text = value; + Invalidate(); + if (!Suspended) OnTextChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the alpha value for this control. + /// + public virtual byte Alpha + { + get + { + return alpha; + } + set + { + alpha = value; + if (!Suspended) OnAlphaChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the background color for the control. + /// + public virtual Color BackColor + { + get + { + return backColor; + } + set + { + backColor = value; + Invalidate(); + if (!Suspended) OnBackColorChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the color for the control. + /// + public virtual Color Color + { + get + { + return color; + } + set + { + if (value != color) + { + color = value; + Invalidate(); + if (!Suspended) OnColorChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the text color for the control. + /// + public virtual Color TextColor + { + get + { + return textColor; + } + set + { + if (value != textColor) + { + textColor = value; + Invalidate(); + if (!Suspended) OnTextColorChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating whether the control can respond to user interaction. + /// + public virtual bool Enabled + { + get + { + return enabled; + } + set + { + if (Root != null && Root != this && !Root.Enabled && value) return; + + enabled = value; + Invalidate(); + + foreach (Control c in controls) + { + c.Enabled = value; + } + + if (!Suspended) OnEnabledChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value that indicates whether the control is rendered. + /// + public virtual bool Visible + { + get + { + return (visible && (parent == null || parent.Visible)); + } + set + { + visible = value; + Invalidate(); + + if (!Suspended) OnVisibleChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the parent for the control. + /// + public virtual Control Parent + { + get + { + return parent; + } + set + { + if (parent != value) + { + if (value != null) value.Add(this); + else Manager.Add(this); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the root for the control. + /// + public virtual Control Root + { + get + { + return root; + } + private set + { + if (root != value) + { + root = value; + + foreach (Control c in controls) + { + c.Root = root; + } + + if (!Suspended) OnRootChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the distance, in pixels, between the left edge of the control and the left edge of its parent. + /// + public virtual int Left + { + get + { + return left; + } + set + { + if (left != value) + { + int old = left; + left = value; + + SetAnchorMargins(); + + if (!Suspended) OnMove(new MoveEventArgs(left, top, old, top)); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the distance, in pixels, between the top edge of the control and the top edge of its parent. + /// + public virtual int Top + { + get + { + return top; + } + set + { + if (top != value) + { + int old = top; + top = value; + + SetAnchorMargins(); + + if (!Suspended) OnMove(new MoveEventArgs(left, top, left, old)); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the width of the control. + /// + public virtual int Width + { + get + { + return width; + } + set + { + if (width != value) + { + int old = width; + width = value; + + if (skin != null) + { + if (width + skin.OriginMargins.Horizontal > MaximumWidth) width = MaximumWidth - skin.OriginMargins.Horizontal; + } + else + { + if (width > MaximumWidth) width = MaximumWidth; + } + if (width < MinimumWidth) width = MinimumWidth; + + if (width > 0) SetAnchorMargins(); + + if (!Suspended) OnResize(new ResizeEventArgs(width, height, old, height)); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the height of the control. + /// + public virtual int Height + { + get + { + return height; + } + set + { + if (height != value) + { + int old = height; + + height = value; + + if (skin != null) + { + if (height + skin.OriginMargins.Vertical > MaximumHeight) + height = MaximumHeight - skin.OriginMargins.Vertical; + } + else + { + if (height > MaximumHeight) height = MaximumHeight; + } + if (height < MinimumHeight) height = MinimumHeight; + + if (height > 0) SetAnchorMargins(); + + if (!Suspended) OnResize(new ResizeEventArgs(width, height, width, old)); + } + + } + + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the minimum width in pixels the control can be sized to. + /// + public virtual int MinimumWidth + { + get + { + return minimumWidth; + } + set + { + minimumWidth = value; + if (minimumWidth < 0) minimumWidth = 0; + if (minimumWidth > maximumWidth) minimumWidth = maximumWidth; + if (width < MinimumWidth) Width = MinimumWidth; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// /// Gets or sets the minimum height in pixels the control can be sized to. + /// + public virtual int MinimumHeight + { + get + { + return minimumHeight; + } + set + { + minimumHeight = value; + if (minimumHeight < 0) minimumHeight = 0; + if (minimumHeight > maximumHeight) minimumHeight = maximumHeight; + if (height < MinimumHeight) Height = MinimumHeight; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// /// Gets or sets the maximum width in pixels the control can be sized to. + /// + public virtual int MaximumWidth + { + get + { + int max = maximumWidth; + if (max > Manager.TargetWidth) max = Manager.TargetWidth; + return max; + } + set + { + maximumWidth = value; + if (maximumWidth < minimumWidth) maximumWidth = minimumWidth; + if (width > MaximumWidth) Width = MaximumWidth; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the maximum height in pixels the control can be sized to. + /// + public virtual int MaximumHeight + { + get + { + int max = maximumHeight; + if (max > Manager.TargetHeight) max = Manager.TargetHeight; + return max; + } + set + { + maximumHeight = value; + if (maximumHeight < minimumHeight) maximumHeight = minimumHeight; + if (height > MaximumHeight) Height = MaximumHeight; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int AbsoluteLeft + { + get + { + if (parent == null) return left + LeftModifier; + else if (parent.Skin == null) return parent.AbsoluteLeft + left + LeftModifier; + else return parent.AbsoluteLeft + left - parent.Skin.OriginMargins.Left + LeftModifier; + } + } + //////////////////////////////////////////////////////////////////////////// + public virtual int AbsoluteTop + { + get + { + if (parent == null) return top + TopModifier; + else if (parent.Skin == null) return parent.AbsoluteTop + top + TopModifier; + else return parent.AbsoluteTop + top - parent.Skin.OriginMargins.Top + TopModifier; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int OriginLeft + { + get + { + if (skin == null) return AbsoluteLeft; + return AbsoluteLeft - skin.OriginMargins.Left; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int OriginTop + { + get + { + if (skin == null) return AbsoluteTop; + return AbsoluteTop - skin.OriginMargins.Top; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int OriginWidth + { + get + { + if (skin == null) return width; + return width + skin.OriginMargins.Left + skin.OriginMargins.Right; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int OriginHeight + { + get + { + if (skin == null) return height; + return height + skin.OriginMargins.Top + skin.OriginMargins.Bottom; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Margins ClientMargins + { + get { return clientMargins; } + set + { + clientMargins = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int ClientLeft + { + get + { + //if (skin == null) return Left; + return ClientMargins.Left; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int ClientTop + { + get + { + //if (skin == null) return Top; + return ClientMargins.Top; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int ClientWidth + { + get + { + //if (skin == null) return Width; + return OriginWidth - ClientMargins.Left - ClientMargins.Right; + } + set + { + Width = value + ClientMargins.Horizontal - skin.OriginMargins.Horizontal; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int ClientHeight + { + get + { + //if (skin == null) return Height; + return OriginHeight - ClientMargins.Top - ClientMargins.Bottom; + } + set + { + Height = value + ClientMargins.Vertical - skin.OriginMargins.Vertical; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Rectangle AbsoluteRect + { + get + { + return new Rectangle(AbsoluteLeft, AbsoluteTop, OriginWidth, OriginHeight); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Rectangle OriginRect + { + get + { + return new Rectangle(OriginLeft, OriginTop, OriginWidth, OriginHeight); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Rectangle ClientRect + { + get + { + return new Rectangle(ClientLeft, ClientTop, ClientWidth, ClientHeight); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Rectangle ControlRect + { + get + { + return new Rectangle(Left, Top, Width, Height); + } + set + { + Left = value.Left; + Top = value.Top; + Width = value.Width; + Height = value.Height; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private Rectangle OutlineRect + { + get { return outlineRect; } + set + { + outlineRect = value; + if (value != Rectangle.Empty) + { + if (outlineRect.Width > MaximumWidth) outlineRect.Width = MaximumWidth; + if (outlineRect.Height > MaximumHeight) outlineRect.Height = MaximumHeight; + if (outlineRect.Width < MinimumWidth) outlineRect.Width = MinimumWidth; + if (outlineRect.Height < MinimumHeight) outlineRect.Height = MinimumHeight; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler Click; + public event EventHandler DoubleClick; + public event MouseEventHandler MouseDown; + public event MouseEventHandler MousePress; + public event MouseEventHandler MouseUp; + public event MouseEventHandler MouseMove; + public event MouseEventHandler MouseOver; + public event MouseEventHandler MouseOut; + /// + /// Occurs when the mouse scroll wheel position changes + /// + public event MouseEventHandler MouseScroll; + public event KeyEventHandler KeyDown; + public event KeyEventHandler KeyPress; + public event KeyEventHandler KeyUp; + public event GamePadEventHandler GamePadDown; + public event GamePadEventHandler GamePadUp; + public event GamePadEventHandler GamePadPress; + public event MoveEventHandler Move; + public event MoveEventHandler ValidateMove; + public event ResizeEventHandler Resize; + public event ResizeEventHandler ValidateResize; + public event DrawEventHandler Draw; + public event EventHandler MoveBegin; + public event EventHandler MoveEnd; + public event EventHandler ResizeBegin; + public event EventHandler ResizeEnd; + public event EventHandler ColorChanged; + public event EventHandler TextColorChanged; + public event EventHandler BackColorChanged; + public event EventHandler TextChanged; + public event EventHandler AnchorChanged; + public event EventHandler SkinChanging; + public event EventHandler SkinChanged; + public event EventHandler ParentChanged; + public event EventHandler RootChanged; + public event EventHandler VisibleChanged; + public event EventHandler EnabledChanged; + public event EventHandler AlphaChanged; + public event EventHandler FocusLost; + public event EventHandler FocusGained; + public event DrawEventHandler DrawTexture; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public Control(Manager manager): base(manager) + { + if (Manager == null) + { + throw new Exception("Control cannot be created. Manager instance is needed."); + } + else if (Manager.Skin == null) + { + throw new Exception("Control cannot be created. No skin loaded."); + } + + text = Utilities.DeriveControlName(this); + root = this; + + InitSkin(); + + CheckLayer(skin, "Control"); + + if (Skin != null) + { + SetDefaultSize(width, height); + SetMinimumSize(MinimumWidth, MinimumHeight); + ResizerSize = skin.ResizerSize; + } + + Stack.Add(this); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (parent != null) parent.Remove(this); + else if (Manager != null) Manager.Remove(this); + if (Manager.OrderList != null) Manager.OrderList.Remove(this); + + // Possibly we added the menu to another parent than this control, + // so we dispose it manually, beacause in logic it belongs to this control. + if (contextMenu != null) + { + contextMenu.Dispose(); + contextMenu = null; + } + + // Recursively disposing all controls. The collection might change from its children, + // so we check it on count greater than zero. + if (controls != null) + { + int c = controls.Count; + for (int i = 0; i < c; i++) + { + if (controls.Count > 0) + { + controls[0].Dispose(); + } + } + } + + // Disposes tooltip owned by Manager + if (toolTip != null && !Manager.Disposing) + { + toolTip.Dispose(); + toolTip = null; + } + + // Removing this control from the global stack. + Stack.Remove(this); + + if (target != null) + { + target.Dispose(); + target = null; + } + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + #region //// Private /////////// + + //////////////////////////////////////////////////////////////////////////// + private int GetVirtualHeight() + { + if (this.Parent is Container && (this.Parent as Container).AutoScroll) + { + int maxy = 0; + + foreach (Control c in Controls) + { + if ((c.Anchor & Anchors.Bottom) != Anchors.Bottom && c.Visible) + { + if (c.Top + c.Height > maxy) maxy = c.Top + c.Height; + } + } + if (maxy < Height) maxy = Height; + + return maxy; + } + else + { + return Height; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int GetVirtualWidth() + { + if (this.Parent is Container && (this.Parent as Container).AutoScroll) + { + int maxx = 0; + + foreach (Control c in Controls) + { + if ((c.Anchor & Anchors.Right) != Anchors.Right && c.Visible) + { + if (c.Left + c.Width > maxx) maxx = c.Left + c.Width; + } + } + if (maxx < Width) maxx = Width; + + return maxx; + } + else + { + return Width; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private Rectangle GetClippingRect(Control c) + { + Rectangle r = Rectangle.Empty; + + r = new Rectangle(c.OriginLeft - root.AbsoluteLeft, + c.OriginTop - root.AbsoluteTop, + c.OriginWidth, + c.OriginHeight); + + int x1 = r.Left; + int x2 = r.Right; + int y1 = r.Top; + int y2 = r.Bottom; + + Control ctrl = c.Parent; + while (ctrl != null) + { + int cx1 = ctrl.OriginLeft - root.AbsoluteLeft; + int cy1 = ctrl.OriginTop - root.AbsoluteTop; + int cx2 = cx1 + ctrl.OriginWidth; + int cy2 = cy1 + ctrl.OriginHeight; + + if (x1 < cx1) x1 = cx1; + if (y1 < cy1) y1 = cy1; + if (x2 > cx2) x2 = cx2; + if (y2 > cy2) y2 = cy2; + + ctrl = ctrl.Parent; + } + + int fx2 = x2 - x1; + int fy2 = y2 - y1; + + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (fx2 < 0) fx2 = 0; + if (fy2 < 0) fy2 = 0; + if (x1 > root.Width) { x1 = root.Width; } + if (y1 > root.Height){ y1 = root.Height; } + if (fx2 > root.Width) fx2 = root.Width; + if (fy2 > root.Height) fy2 = root.Height; + + Rectangle ret = new Rectangle(x1, y1, fx2, fy2); + + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private RenderTarget2D CreateRenderTarget(int width, int height) + { + if (width > 0 && height > 0) + { + return new RenderTarget2D(Manager.GraphicsDevice, + width, + height, + false, + SurfaceFormat.Color, + DepthFormat.None, + Manager.GraphicsDevice.PresentationParameters.MultiSampleCount, + Manager._RenderTargetUsage); + + } + + return null; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal virtual void PrepareTexture(Renderer renderer, GameTime gameTime) + { + if (visible) + { + if (invalidated) + { + OnDrawTexture(new DrawEventArgs(renderer, new Rectangle(0, 0, OriginWidth, OriginHeight), gameTime)); + + if (target == null || target.Width < OriginWidth || target.Height < OriginHeight) + { + if (target != null) + { + target.Dispose(); + target = null; + } + + int w = OriginWidth + (Manager.TextureResizeIncrement - (OriginWidth % Manager.TextureResizeIncrement)); + int h = OriginHeight + (Manager.TextureResizeIncrement - (OriginHeight % Manager.TextureResizeIncrement)); + + if (h > Manager.TargetHeight) h = Manager.TargetHeight; + if (w > Manager.TargetWidth) w = Manager.TargetWidth; + + target = CreateRenderTarget(w, h); + } + + if (target != null) + { + Manager.GraphicsDevice.SetRenderTarget(target); + target.GraphicsDevice.Clear(backColor); + + Rectangle rect = new Rectangle(0, 0, OriginWidth, OriginHeight); + DrawControls(renderer, rect, gameTime, false); + + Manager.GraphicsDevice.SetRenderTarget(null); + } + invalidated = false; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckDetached(Control c) + { + Control parent = c.Parent; + while (parent != null) + { + if (parent.Detached) + { + return true; + } + parent = parent.Parent; + } + + return c.Detached; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DrawChildControls(Renderer renderer, GameTime gameTime, bool firstDetachedLevel) + { + if (controls != null) + { + foreach (Control c in controls) + { + // We skip detached controls for first level after root (they are rendered separately in Draw() method) + if (((c.Root == c.Parent && !c.Detached) || c.Root != c.Parent) && AbsoluteRect.Intersects(c.AbsoluteRect) && c.visible) + { + Manager.GraphicsDevice.ScissorRectangle = GetClippingRect(c); + + Rectangle rect = new Rectangle(c.OriginLeft - root.AbsoluteLeft, c.OriginTop - root.AbsoluteTop, c.OriginWidth, c.OriginHeight); + if (c.Root != c.Parent && ((!c.Detached && CheckDetached(c)) || firstDetachedLevel)) + { + rect = new Rectangle(c.OriginLeft, c.OriginTop, c.OriginWidth, c.OriginHeight); + Manager.GraphicsDevice.ScissorRectangle = rect; + } + + renderer.Begin(BlendingMode.Default); + c.DrawingRect = rect; + c.DrawControl(renderer, rect, gameTime); + + DrawEventArgs args = new DrawEventArgs(); + args.Rectangle = rect; + args.Renderer = renderer; + args.GameTime = gameTime; + c.OnDraw(args); + renderer.End(); + + c.DrawChildControls(renderer, gameTime, firstDetachedLevel); + + c.DrawOutline(renderer, true); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DrawControls(Renderer renderer, Rectangle rect, GameTime gameTime, bool firstDetach) + { + renderer.Begin(BlendingMode.Default); + + DrawingRect = rect; + DrawControl(renderer, rect, gameTime); + + DrawEventArgs args = new DrawEventArgs(); + args.Rectangle = rect; + args.Renderer = renderer; + args.GameTime = gameTime; + OnDraw(args); + + renderer.End(); + + DrawChildControls(renderer, gameTime, firstDetach); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DrawDetached(Control control, Renderer renderer, GameTime gameTime) + { + if (control.Controls != null) + { + foreach (Control c in control.Controls) + { + if (c.Detached && c.Visible) + { + c.DrawControls(renderer, new Rectangle(c.OriginLeft, c.OriginTop, c.OriginWidth, c.OriginHeight), gameTime, true); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal virtual void Render(Renderer renderer, GameTime gameTime) + { + if (visible && target != null) + { + bool draw = true; + + if (draw) + { + renderer.Begin(BlendingMode.Default); + renderer.Draw(target, OriginLeft, OriginTop, new Rectangle(0, 0, OriginWidth, OriginHeight), Color.FromNonPremultiplied(255, 255, 255, Alpha)); + renderer.End(); + + DrawDetached(this, renderer, gameTime); + + DrawOutline(renderer, false); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DrawOutline(Renderer renderer, bool child) + { + if (!OutlineRect.IsEmpty) + { + Rectangle r = OutlineRect; + if (child) + { + r = new Rectangle(OutlineRect.Left + (parent.AbsoluteLeft - root.AbsoluteLeft), OutlineRect.Top + (parent.AbsoluteTop - root.AbsoluteTop), OutlineRect.Width, OutlineRect.Height); + } + + Texture2D t = Manager.Skin.Controls["Control.Outline"].Layers[0].Image.Resource; + + int s = resizerSize; + Rectangle r1 = new Rectangle(r.Left + leftModifier, r.Top + topModifier, r.Width, s); + Rectangle r2 = new Rectangle(r.Left + leftModifier, r.Top + s + topModifier, resizerSize, r.Height - (2 * s)); + Rectangle r3 = new Rectangle(r.Right - s + leftModifier, r.Top + s + topModifier, s, r.Height - (2 * s)); + Rectangle r4 = new Rectangle(r.Left + leftModifier, r.Bottom - s + topModifier, r.Width, s); + + Color c = Manager.Skin.Controls["Control.Outline"].Layers[0].States.Enabled.Color; + + renderer.Begin(BlendingMode.Default); + if ((ResizeEdge & Anchors.Top) == Anchors.Top || !partialOutline) renderer.Draw(t, r1, c); + if ((ResizeEdge & Anchors.Left) == Anchors.Left || !partialOutline) renderer.Draw(t, r2, c); + if ((ResizeEdge & Anchors.Right) == Anchors.Right || !partialOutline) renderer.Draw(t, r3, c); + if ((ResizeEdge & Anchors.Bottom) == Anchors.Bottom || !partialOutline) renderer.Draw(t, r4, c); + renderer.End(); + } + else if (DesignMode && Focused) + { + Rectangle r = ControlRect; + if (child) + { + r = new Rectangle(r.Left + (parent.AbsoluteLeft - root.AbsoluteLeft), r.Top + (parent.AbsoluteTop - root.AbsoluteTop), r.Width, r.Height); + } + + Texture2D t = Manager.Skin.Controls["Control.Outline"].Layers[0].Image.Resource; + + int s = resizerSize; + Rectangle r1 = new Rectangle(r.Left + leftModifier, r.Top + topModifier, r.Width, s); + Rectangle r2 = new Rectangle(r.Left + leftModifier, r.Top + s + topModifier, resizerSize, r.Height - (2 * s)); + Rectangle r3 = new Rectangle(r.Right - s + leftModifier, r.Top + s + topModifier, s, r.Height - (2 * s)); + Rectangle r4 = new Rectangle(r.Left + leftModifier, r.Bottom - s + topModifier, r.Width, s); + + Color c = Manager.Skin.Controls["Control.Outline"].Layers[0].States.Enabled.Color; + + renderer.Begin(BlendingMode.Default); + renderer.Draw(t, r1, c); + renderer.Draw(t, r2, c); + renderer.Draw(t, r3, c); + renderer.Draw(t, r4, c); + renderer.End(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SetPosition(int left, int top) + { + this.left = left; + this.top = top; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SetSize(int width, int height) + { + this.width = width; + this.height = height; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal void SetAnchorMargins() + { + if (Parent != null) + { + anchorMargins.Left = Left; + anchorMargins.Top = Top; + anchorMargins.Right = Parent.VirtualWidth - Width - Left; + anchorMargins.Bottom = Parent.VirtualHeight - Height - Top; + } + else + { + anchorMargins = new Margins(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ProcessAnchor(ResizeEventArgs e) + { + if (((Anchor & Anchors.Right) == Anchors.Right) && ((Anchor & Anchors.Left) != Anchors.Left)) + { + Left = Parent.VirtualWidth - Width - anchorMargins.Right; + } + else if (((Anchor & Anchors.Right) == Anchors.Right) && ((Anchor & Anchors.Left) == Anchors.Left)) + { + Width = Parent.VirtualWidth - Left - anchorMargins.Right; + } + else if (((Anchor & Anchors.Right) != Anchors.Right) && ((Anchor & Anchors.Left) != Anchors.Left)) + { + int diff = (e.Width - e.OldWidth); + if (e.Width % 2 != 0 && diff != 0) + { + diff += (diff / Math.Abs(diff)); + } + Left += (diff / 2); + } + if (((Anchor & Anchors.Bottom) == Anchors.Bottom) && ((Anchor & Anchors.Top) != Anchors.Top)) + { + Top = Parent.VirtualHeight - Height - anchorMargins.Bottom; + } + else if (((Anchor & Anchors.Bottom) == Anchors.Bottom) && ((Anchor & Anchors.Top) == Anchors.Top)) + { + Height = Parent.VirtualHeight - Top - anchorMargins.Bottom; + } + else if (((Anchor & Anchors.Bottom) != Anchors.Bottom) && ((Anchor & Anchors.Top) != Anchors.Top)) + { + int diff = (e.Height - e.OldHeight); + if (e.Height % 2 != 0 && diff != 0) + { + diff += (diff / Math.Abs(diff)); + } + Top += (diff / 2); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Protected ///////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + OnMove(new MoveEventArgs()); + OnResize(new ResizeEventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal virtual void InitSkin() + { + if (Manager != null && Manager.Skin != null && Manager.Skin.Controls != null) + { + SkinControl s = Manager.Skin.Controls[Utilities.DeriveControlName(this)]; + if (s != null) Skin = new SkinControl(s); + else Skin = new SkinControl(Manager.Skin.Controls["Control"]); + } + else + { + throw new Exception("Control skin cannot be initialized. No skin loaded."); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void SetDefaultSize(int width, int height) + { + if (skin.DefaultSize.Width > 0) Width = skin.DefaultSize.Width; + else Width = width; + if (skin.DefaultSize.Height > 0) Height = skin.DefaultSize.Height; + else Height = height; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void SetMinimumSize(int minimumWidth, int minimumHeight) + { + if (skin.MinimumSize.Width > 0) MinimumWidth = skin.MinimumSize.Width; + else MinimumWidth = minimumWidth; + if (skin.MinimumSize.Height > 0) MinimumHeight = skin.MinimumSize.Height; + else MinimumHeight = minimumHeight; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal void OnDeviceSettingsChanged(DeviceEventArgs e) + { + if (!e.Handled) + { + Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + if (backColor != UndefinedColor && backColor != Color.Transparent) + { + renderer.Draw(Manager.Skin.Images["Control"].Resource, rect, backColor); + } + renderer.DrawLayer(this, skin.Layers[0], rect); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + + ToolTipUpdate(); + + if (controls != null) + { + ControlsList list = new ControlsList(); + list.AddRange(controls); + foreach (Control c in list) + { + c.Update(gameTime); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal virtual void CheckLayer(SkinControl skin, string layer) + { + if (!(skin != null && skin.Layers != null && skin.Layers.Count > 0 && skin.Layers[layer] != null)) + { + throw new Exception("Unable to read skin layer \"" + layer + "\" for control \"" + Utilities.DeriveControlName(this) + "\"."); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal virtual void CheckLayer(SkinControl skin, int layer) + { + if (!(skin != null && skin.Layers != null && skin.Layers.Count > 0 && skin.Layers[layer] != null)) + { + throw new Exception("Unable to read skin layer with index \"" + layer.ToString() + "\" for control \"" + Utilities.DeriveControlName(this) + "\"."); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Public //////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Control GetControl(string name) + { + Control ret = null; + foreach (Control c in Controls) + { + if (c.Name.ToLower() == name.ToLower()) + { + ret = c; + break; + } + else + { + ret = c.GetControl(name); + if (ret != null) break; + } + } + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Add(Control control) + { + if (control != null) + { + if (!controls.Contains(control)) + { + if (control.Parent != null) control.Parent.Remove(control); + else Manager.Remove(control); + + control.Manager = Manager; + control.parent = this; + control.Root = root; + control.Enabled = (Enabled ? control.Enabled : Enabled); + controls.Add(control); + + virtualHeight = GetVirtualHeight(); + virtualWidth = GetVirtualWidth(); + + Manager.DeviceSettingsChanged += new DeviceEventHandler(control.OnDeviceSettingsChanged); + Manager.SkinChanging += new SkinEventHandler(control.OnSkinChanging); + Manager.SkinChanged += new SkinEventHandler(control.OnSkinChanged); + Resize += new ResizeEventHandler(control.OnParentResize); + + control.SetAnchorMargins(); + + if (!Suspended) OnParentChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Remove(Control control) + { + if (control != null) + { + if (control.Focused && control.Root != null) control.Root.Focused = true; + else if (control.Focused) control.Focused = false; + + controls.Remove(control); + + control.parent = null; + control.Root = control; + + Resize -= control.OnParentResize; + Manager.DeviceSettingsChanged -= control.OnDeviceSettingsChanged; + Manager.SkinChanging -= control.OnSkinChanging; + Manager.SkinChanged -= control.OnSkinChanged; + + if (!Suspended) OnParentChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool Contains(Control control, bool recursively) + { + if (Controls != null) + { + foreach (Control c in Controls) + { + if (c == control) return true; + if (recursively && c.Contains(control, true)) return true; + } + } + return false; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Invalidate() + { + invalidated = true; + + if (parent != null) + { + parent.Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void BringToFront() + { + if (Manager != null) Manager.BringToFront(this); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SendToBack() + { + if (Manager != null) Manager.SendToBack(this); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Show() + { + Visible = true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Hide() + { + Visible = false; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Refresh() + { + OnMove(new MoveEventArgs(left, top, left, top)); + OnResize(new ResizeEventArgs(width, height, width, height)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SendMessage(Message message, EventArgs e) + { + MessageProcess(message, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void MessageProcess(Message message, EventArgs e) + { + switch (message) + { + case Message.Click: + { + ClickProcess(e as MouseEventArgs); + break; + } + case Message.MouseDown: + { + MouseDownProcess(e as MouseEventArgs); + break; + } + case Message.MouseUp: + { + MouseUpProcess(e as MouseEventArgs); + break; + } + case Message.MousePress: + { + MousePressProcess(e as MouseEventArgs); + break; + } + case Message.MouseScroll: + { + MouseScrollProcess(e as MouseEventArgs); + break; + } + case Message.MouseMove: + { + MouseMoveProcess(e as MouseEventArgs); + break; + } + case Message.MouseOver: + { + MouseOverProcess(e as MouseEventArgs); + break; + } + case Message.MouseOut: + { + MouseOutProcess(e as MouseEventArgs); + break; + } + case Message.GamePadDown: + { + GamePadDownProcess(e as GamePadEventArgs); + break; + } + case Message.GamePadUp: + { + GamePadUpProcess(e as GamePadEventArgs); + break; + } + case Message.GamePadPress: + { + GamePadPressProcess(e as GamePadEventArgs); + break; + } + case Message.KeyDown: + { + KeyDownProcess(e as KeyEventArgs); + break; + } + case Message.KeyUp: + { + KeyUpProcess(e as KeyEventArgs); + break; + } + case Message.KeyPress: + { + KeyPressProcess(e as KeyEventArgs); + break; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// GamePad /////////// + + //////////////////////////////////////////////////////////////////////////// + private void GamePadPressProcess(GamePadEventArgs e) + { + Invalidate(); + if (!Suspended) OnGamePadPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void GamePadUpProcess(GamePadEventArgs e) + { + Invalidate(); + + if (e.Button == GamePadActions.Press && pressed[(int)e.Button]) + { + pressed[(int)e.Button] = false; + } + + if (!Suspended) OnGamePadUp(e); + + if (e.Button == GamePadActions.ContextMenu && !e.Handled) + { + if (contextMenu != null) + { + contextMenu.Show(this, AbsoluteLeft + 8, AbsoluteTop + 8); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void GamePadDownProcess(GamePadEventArgs e) + { + Invalidate(); + + ToolTipOut(); + + if (e.Button == GamePadActions.Press && !IsPressed) + { + pressed[(int)e.Button] = true; + } + + if (!Suspended) OnGamePadDown(e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Keyboard ////////// + + //////////////////////////////////////////////////////////////////////////// + private void KeyPressProcess(KeyEventArgs e) + { + Invalidate(); + if (!Suspended) OnKeyPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////// + private void KeyDownProcess(KeyEventArgs e) + { + Invalidate(); + + ToolTipOut(); + + if (e.Key == Microsoft.Xna.Framework.Input.Keys.Space && !IsPressed) + { + pressed[(int)MouseButton.None] = true; + } + + if (!Suspended) OnKeyDown(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void KeyUpProcess(KeyEventArgs e) + { + Invalidate(); + + if (e.Key == Microsoft.Xna.Framework.Input.Keys.Space && pressed[(int)MouseButton.None]) + { + pressed[(int)MouseButton.None] = false; + } + + if (!Suspended) OnKeyUp(e); + + if (e.Key == Microsoft.Xna.Framework.Input.Keys.Apps && !e.Handled) + { + if (contextMenu != null) + { + contextMenu.Show(this, AbsoluteLeft + 8, AbsoluteTop + 8); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Mouse ///////////// + + //////////////////////////////////////////////////////////////////////////// + private void MouseDownProcess(MouseEventArgs e) + { + Invalidate(); + pressed[(int)e.Button] = true; + + if (e.Button == MouseButton.Left) + { + pressSpot = new Point(TransformPosition(e).Position.X, TransformPosition(e).Position.Y); + + if (CheckResizableArea(e.Position)) + { + pressDiff[0] = pressSpot.X; + pressDiff[1] = pressSpot.Y; + pressDiff[2] = Width - pressSpot.X; + pressDiff[3] = Height - pressSpot.Y; + + IsResizing = true; + if (outlineResizing) OutlineRect = ControlRect; + if (!Suspended) OnResizeBegin(e); + } + else if (CheckMovableArea(e.Position)) + { + IsMoving = true; + if (outlineMoving) OutlineRect = ControlRect; + if (!Suspended) OnMoveBegin(e); + } + } + + ToolTipOut(); + + if (!Suspended) OnMouseDown(TransformPosition(e)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void MouseUpProcess(MouseEventArgs e) + { + Invalidate(); + if (pressed[(int)e.Button] || isMoving || isResizing) + { + pressed[(int)e.Button] = false; + + if (e.Button == MouseButton.Left) + { + if (IsResizing) + { + IsResizing = false; + if (outlineResizing) + { + Left = OutlineRect.Left; + Top = OutlineRect.Top; + Width = OutlineRect.Width; + Height = OutlineRect.Height; + OutlineRect = Rectangle.Empty; + } + if (!Suspended) OnResizeEnd(e); + } + else if (IsMoving) + { + IsMoving = false; + if (outlineMoving) + { + Left = OutlineRect.Left; + Top = OutlineRect.Top; + OutlineRect = Rectangle.Empty; + } + if (!Suspended) OnMoveEnd(e); + } + } + if (!Suspended) OnMouseUp(TransformPosition(e)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void MousePressProcess(MouseEventArgs e) + { + if (pressed[(int)e.Button] && !IsMoving && !IsResizing) + { + if (!Suspended) OnMousePress(TransformPosition(e)); + } + } + //////////////////////////////////////////////////////////////////////////// + + void MouseScrollProcess(MouseEventArgs e) + { + if (!IsMoving && !IsResizing && !Suspended) + { + OnMouseScroll(e); + } + } + + //////////////////////////////////////////////////////////////////////////// + private void MouseOverProcess(MouseEventArgs e) + { + Invalidate(); + hovered = true; + ToolTipOver(); + + #if (!XBOX && !XBOX_FAKE) + if (cursor != null && Manager.Cursor != cursor) Manager.Cursor = cursor; + #endif + + if (!Suspended) OnMouseOver(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void MouseOutProcess(MouseEventArgs e) + { + Invalidate(); + hovered = false; + ToolTipOut(); + + #if (!XBOX && !XBOX_FAKE) + Manager.Cursor = Manager.Skin.Cursors["Default"].Resource; + #endif + + if (!Suspended) OnMouseOut(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void MouseMoveProcess(MouseEventArgs e) + { + if (CheckPosition(e.Position) && !inside) + { + inside = true; + Invalidate(); + } + else if (!CheckPosition(e.Position) && inside) + { + inside = false; + Invalidate(); + } + + PerformResize(e); + + if (!IsResizing && IsMoving) + { + int x = (parent != null) ? parent.AbsoluteLeft : 0; + int y = (parent != null) ? parent.AbsoluteTop : 0; + + int l = e.Position.X - x - pressSpot.X - leftModifier; + int t = e.Position.Y - y - pressSpot.Y - topModifier; + + if (!Suspended) + { + MoveEventArgs v = new MoveEventArgs(l, t, Left, Top); + OnValidateMove(v); + + l = v.Left; + t = v.Top; + } + + if (outlineMoving) + { + OutlineRect = new Rectangle(l, t, OutlineRect.Width, OutlineRect.Height); + if (parent != null) parent.Invalidate(); + } + else + { + Left = l; + Top = t; + } + } + + if (!Suspended) + { + OnMouseMove(TransformPosition(e)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ClickProcess(EventArgs e) + { + long timer = (long)TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds; + + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + + if ((doubleClickTimer == 0 || (timer - doubleClickTimer > Manager.DoubleClickTime)) || + !doubleClicks) + { + TimeSpan ts = new TimeSpan(DateTime.Now.Ticks); + doubleClickTimer = (long)ts.TotalMilliseconds; + doubleClickButton = ex.Button; + + if (!Suspended) OnClick(e); + + + } + else if (timer - doubleClickTimer <= Manager.DoubleClickTime && (ex.Button == doubleClickButton && ex.Button != MouseButton.None)) + { + doubleClickTimer = 0; + if (!Suspended) OnDoubleClick(e); + } + else + { + doubleClickButton = MouseButton.None; + } + + if (ex.Button == MouseButton.Right && contextMenu != null && !e.Handled) + { + contextMenu.Show(this, ex.Position.X, ex.Position.Y); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ToolTipUpdate() + { + if (Manager.ToolTipsEnabled && toolTip != null && tooltipTimer > 0 && (TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds - tooltipTimer) >= Manager.ToolTipDelay) + { + tooltipTimer = 0; + toolTip.Visible = true; + Manager.Add(toolTip); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ToolTipOver() + { + if (Manager.ToolTipsEnabled && toolTip != null && tooltipTimer == 0) + { + TimeSpan ts = new TimeSpan(DateTime.Now.Ticks); + tooltipTimer = (long)ts.TotalMilliseconds; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ToolTipOut() + { + if (Manager.ToolTipsEnabled && toolTip != null) + { + tooltipTimer = 0; + toolTip.Visible = false; + Manager.Remove(toolTip); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckPosition(Point pos) + { + if ((pos.X >= AbsoluteLeft) && (pos.X < AbsoluteLeft + Width )) + { + if ((pos.Y >= AbsoluteTop) && (pos.Y < AbsoluteTop + Height)) + { + return true; + } + } + return false; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckMovableArea(Point pos) + { + if (movable) + { + Rectangle rect = movableArea; + + if (rect == Rectangle.Empty) + { + rect = new Rectangle(0, 0, width, height); + } + + pos.X -= AbsoluteLeft; + pos.Y -= AbsoluteTop; + + if ((pos.X >= rect.X) && (pos.X < rect.X + rect.Width)) + { + if ((pos.Y >= rect.Y) && (pos.Y < rect.Y + rect.Height)) + { + return true; + } + } + } + return false; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckResizableArea(Point pos) + { + if (resizable) + { + pos.X -= AbsoluteLeft; + pos.Y -= AbsoluteTop; + + if ((pos.X >= 0 && pos.X < resizerSize && pos.Y >= 0 && pos.Y < Height) || + (pos.X >= Width - resizerSize && pos.X < Width && pos.Y >=0 && pos.Y < Height) || + (pos.Y >= 0 && pos.Y < resizerSize && pos.X >=0 && pos.X < Width) || + (pos.Y >= Height - resizerSize && pos.Y < Height && pos.X >=0 && pos.X < Width)) + { + return true; + } + } + return false; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected MouseEventArgs TransformPosition(MouseEventArgs e) + { + MouseEventArgs ee = new MouseEventArgs(e.State, e.Button, e.Position); + ee.Difference = e.Difference; + + ee.Position.X = ee.State.X - AbsoluteLeft; + ee.Position.Y = ee.State.Y - AbsoluteTop; + return ee; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int CheckWidth(ref int w) + { + int diff = 0; + + if (w > MaximumWidth) + { + diff = MaximumWidth - w; + w = MaximumWidth; + } + if (w < MinimumWidth) + { + diff = MinimumWidth - w; + w = MinimumWidth; + } + + return diff; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int CheckHeight(ref int h) + { + int diff = 0; + + if (h > MaximumHeight) + { + diff = MaximumHeight - h; + h = MaximumHeight; + } + if (h < MinimumHeight) + { + diff = MinimumHeight - h; + h = MinimumHeight; + } + + return diff; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void PerformResize(MouseEventArgs e) + { + if (resizable && !IsMoving) + { + if (!IsResizing) + { + #if (!XBOX && !XBOX_FAKE) + GetResizePosition(e); + Manager.Cursor = Cursor = GetResizeCursor(); + #endif + } + + if (IsResizing) + { + invalidated = true; + + bool top = false; + bool bottom = false; + bool left = false; + bool right = false; + + if ((resizeArea == Alignment.TopCenter || + resizeArea == Alignment.TopLeft || + resizeArea == Alignment.TopRight) && (resizeEdge & Anchors.Top) == Anchors.Top) top = true; + + else if ((resizeArea == Alignment.BottomCenter || + resizeArea == Alignment.BottomLeft || + resizeArea == Alignment.BottomRight) && (resizeEdge & Anchors.Bottom) == Anchors.Bottom) bottom = true; + + if ((resizeArea == Alignment.MiddleLeft || + resizeArea == Alignment.BottomLeft || + resizeArea == Alignment.TopLeft) && (resizeEdge & Anchors.Left) == Anchors.Left) left = true; + + else if ((resizeArea == Alignment.MiddleRight || + resizeArea == Alignment.BottomRight || + resizeArea == Alignment.TopRight) && (resizeEdge & Anchors.Right) == Anchors.Right) right = true; + + int w = Width; + int h = Height; + int l = Left; + int t = Top; + + if (outlineResizing && !OutlineRect.IsEmpty) + { + l = OutlineRect.Left; + t = OutlineRect.Top; + w = OutlineRect.Width; + h = OutlineRect.Height; + } + + int px = e.Position.X - (parent != null ? parent.AbsoluteLeft : 0); + int py = e.Position.Y - (parent != null ? parent.AbsoluteTop : 0); + + if (left) + { + w = w + (l - px) + leftModifier + pressDiff[0]; + l = px - leftModifier - pressDiff[0] - CheckWidth(ref w); + + } + else if (right) + { + w = px - l - leftModifier + pressDiff[2]; + CheckWidth(ref w); + } + + if (top) + { + h = h + (t - py) + topModifier + pressDiff[1]; + t = py - topModifier - pressDiff[1] - CheckHeight(ref h); + } + else if (bottom) + { + h = py - t - topModifier + pressDiff[3]; + CheckHeight(ref h); + } + + if (!Suspended) + { + ResizeEventArgs v = new ResizeEventArgs(w, h, Width, Height); + OnValidateResize(v); + + if (top) + { + // Compensate for a possible height change from Validate event + t += (h - v.Height); + } + if (left) + { + // Compensate for a possible width change from Validate event + l += (w - v.Width); + } + w = v.Width; + h = v.Height; + } + + if (outlineResizing) + { + OutlineRect = new Rectangle(l, t, w, h); + if (parent != null) parent.Invalidate(); + } + else + { + Width = w; + Height = h; + Top = t; + Left = l; + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + #if (!XBOX && !XBOX_FAKE) + private Cursor GetResizeCursor() + { + Cursor cur = Cursor; + switch (resizeArea) + { + case Alignment.TopCenter: + { + return ((resizeEdge & Anchors.Top) == Anchors.Top) ? Manager.Skin.Cursors["Vertical"].Resource : Cursor; + } + case Alignment.BottomCenter: + { + return ((resizeEdge & Anchors.Bottom) == Anchors.Bottom) ? Manager.Skin.Cursors["Vertical"].Resource : Cursor; + } + case Alignment.MiddleLeft: + { + return ((resizeEdge & Anchors.Left) == Anchors.Left) ? Manager.Skin.Cursors["Horizontal"].Resource : Cursor; + } + case Alignment.MiddleRight: + { + return ((resizeEdge & Anchors.Right) == Anchors.Right) ? Manager.Skin.Cursors["Horizontal"].Resource : Cursor; + } + case Alignment.TopLeft: + { + return ((resizeEdge & Anchors.Left) == Anchors.Left && (resizeEdge & Anchors.Top) == Anchors.Top) ? Manager.Skin.Cursors["DiagonalLeft"].Resource : Cursor; + } + case Alignment.BottomRight: + { + return ((resizeEdge & Anchors.Bottom) == Anchors.Bottom && (resizeEdge & Anchors.Right) == Anchors.Right) ? Manager.Skin.Cursors["DiagonalLeft"].Resource : Cursor; + } + case Alignment.TopRight: + { + return ((resizeEdge & Anchors.Top) == Anchors.Top && (resizeEdge & Anchors.Right) == Anchors.Right) ? Manager.Skin.Cursors["DiagonalRight"].Resource : Cursor; + } + case Alignment.BottomLeft: + { + return ((resizeEdge & Anchors.Bottom) == Anchors.Bottom && (resizeEdge & Anchors.Left) == Anchors.Left) ? Manager.Skin.Cursors["DiagonalRight"].Resource : Cursor; + } + } + return Manager.Skin.Cursors["Default"].Resource; + } + #endif + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void GetResizePosition(MouseEventArgs e) + { + int x = e.Position.X - AbsoluteLeft; + int y = e.Position.Y - AbsoluteTop; + bool l = false, t = false, r = false, b = false; + + resizeArea = Alignment.None; + + if (CheckResizableArea(e.Position)) + { + if (x < resizerSize) l = true; + if (x >= Width - resizerSize) r = true; + if (y < resizerSize) t = true; + if (y >= Height - resizerSize) b = true; + + if (l && t) resizeArea = Alignment.TopLeft; + else if (l && b) resizeArea = Alignment.BottomLeft; + else if (r && t) resizeArea = Alignment.TopRight; + else if (r && b) resizeArea = Alignment.BottomRight; + else if (l) resizeArea = Alignment.MiddleLeft; + else if (t) resizeArea = Alignment.TopCenter; + else if (r) resizeArea = Alignment.MiddleRight; + else if (b) resizeArea = Alignment.BottomCenter; + } + else + { + resizeArea = Alignment.None; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Handlers ////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMouseUp(MouseEventArgs e) + { + if (MouseUp != null) MouseUp.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMouseDown(MouseEventArgs e) + { + if (MouseDown != null) MouseDown.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMouseMove(MouseEventArgs e) + { + if (MouseMove != null) MouseMove.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMouseOver(MouseEventArgs e) + { + if (MouseOver != null) MouseOver.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMouseOut(MouseEventArgs e) + { + if (MouseOut != null) MouseOut.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnClick(EventArgs e) + { + if (Click != null) Click.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnDoubleClick(EventArgs e) + { + if (DoubleClick != null) DoubleClick.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMove(MoveEventArgs e) + { + if (parent != null) parent.Invalidate(); + if (Move != null) Move.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnResize(ResizeEventArgs e) + { + Invalidate(); + if (Resize != null) Resize.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnValidateResize(ResizeEventArgs e) + { + if (ValidateResize != null) ValidateResize.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnValidateMove(MoveEventArgs e) + { + if (ValidateMove != null) ValidateMove.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMoveBegin(EventArgs e) + { + if (MoveBegin != null) MoveBegin.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMoveEnd(EventArgs e) + { + if (MoveEnd != null) MoveEnd.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnResizeBegin(EventArgs e) + { + if (ResizeBegin != null) ResizeBegin.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnResizeEnd(EventArgs e) + { + if (ResizeEnd != null) ResizeEnd.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnParentResize(object sender, ResizeEventArgs e) + { + ProcessAnchor(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnKeyUp(KeyEventArgs e) + { + if (KeyUp != null) KeyUp.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnKeyDown(KeyEventArgs e) + { + if (KeyDown != null) KeyDown.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnKeyPress(KeyEventArgs e) + { + if (KeyPress != null) KeyPress.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnGamePadUp(GamePadEventArgs e) + { + if (GamePadUp != null) GamePadUp.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnGamePadDown(GamePadEventArgs e) + { + if (GamePadDown != null) GamePadDown.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnGamePadPress(GamePadEventArgs e) + { + if (GamePadPress != null) GamePadPress.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal void OnDraw(DrawEventArgs e) + { + if (Draw != null) Draw.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected void OnDrawTexture(DrawEventArgs e) + { + if (DrawTexture != null) DrawTexture.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnColorChanged(EventArgs e) + { + if (ColorChanged != null) ColorChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnTextColorChanged(EventArgs e) + { + if (TextColorChanged != null) TextColorChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnBackColorChanged(EventArgs e) + { + if (BackColorChanged != null) BackColorChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnTextChanged(EventArgs e) + { + if (TextChanged != null) TextChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnAnchorChanged(EventArgs e) + { + if (AnchorChanged != null) AnchorChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal virtual void OnSkinChanged(EventArgs e) + { + if (SkinChanged != null) SkinChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal virtual void OnSkinChanging(EventArgs e) + { + if (SkinChanging != null) SkinChanging.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnParentChanged(EventArgs e) + { + if (ParentChanged != null) ParentChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnRootChanged(EventArgs e) + { + if (RootChanged != null) RootChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnVisibleChanged(EventArgs e) + { + if (VisibleChanged != null) VisibleChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnEnabledChanged(EventArgs e) + { + if (EnabledChanged != null) EnabledChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnAlphaChanged(EventArgs e) + { + if (AlphaChanged != null) AlphaChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnFocusLost(EventArgs e) + { + if (FocusLost != null) FocusLost.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnFocusGained(EventArgs e) + { + if (FocusGained != null) FocusGained.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnMousePress(MouseEventArgs e) + { + if (MousePress != null) MousePress.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + protected virtual void OnMouseScroll(MouseEventArgs e) + { + if (MouseScroll != null) MouseScroll.Invoke(this, e); + } + + #endregion + + #endregion + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} \ No newline at end of file diff --git a/Neoforce/Cursor.cs b/Neoforce/Cursor.cs new file mode 100644 index 0000000..8448744 --- /dev/null +++ b/Neoforce/Cursor.cs @@ -0,0 +1,56 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TomShane.Neoforce.Controls +{ + /// + /// Provides a basic Software cursor + /// + public class Cursor + { + private Texture2D cursorTexture; + + public Texture2D CursorTexture + { + get { return cursorTexture;} + set { cursorTexture = value; } + } + + internal string cursorPath; + + private Vector2 hotspot; + private int width; + private int height; + + public int Height + { + get { return height; } + set { height = value; } + } + + public int Width + { + get { return width; } + set { width = value; } + } + + public Vector2 HotSpot + { + get { return hotspot; } + set { hotspot = value; } + } + + public Cursor(string path, Vector2 hotspot, int width, int height) + { + this.cursorPath = path; + this.hotspot = hotspot; + this.width = width; + this.height = height; + } + } +} diff --git a/Neoforce/Delegates.cs b/Neoforce/Delegates.cs new file mode 100644 index 0000000..8f07ca2 --- /dev/null +++ b/Neoforce/Delegates.cs @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Delegates.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Delegates ///////// + + //////////////////////////////////////////////////////////////////////////// + public delegate void DeviceEventHandler(DeviceEventArgs e); + public delegate void SkinEventHandler(EventArgs e); + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public delegate void EventHandler(object sender, EventArgs e); + public delegate void MouseEventHandler(object sender, MouseEventArgs e); + public delegate void KeyEventHandler(object sender, KeyEventArgs e); + public delegate void GamePadEventHandler(object sender, GamePadEventArgs e); + public delegate void DrawEventHandler(object sender, DrawEventArgs e); + public delegate void MoveEventHandler(object sender, MoveEventArgs e); + public delegate void ResizeEventHandler(object sender, ResizeEventArgs e); + public delegate void WindowClosingEventHandler(object sender, WindowClosingEventArgs e); + public delegate void WindowClosedEventHandler(object sender, WindowClosedEventArgs e); + public delegate void ConsoleMessageEventHandler(object sender, ConsoleMessageEventArgs e); + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} diff --git a/Neoforce/Dialog.cs b/Neoforce/Dialog.cs new file mode 100644 index 0000000..0de13ae --- /dev/null +++ b/Neoforce/Dialog.cs @@ -0,0 +1,148 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Central // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Dialog.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class Dialog: Window + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Panel pnlTop = null; + private Label lblCapt = null; + private Label lblDesc = null; + private Panel pnlBottom = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public Panel TopPanel { get { return pnlTop; } } + public Panel BottomPanel { get { return pnlBottom; } } + public Label Caption { get { return lblCapt; } } + public Label Description { get { return lblDesc; } } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public Dialog(Manager manager): base(manager) + { + pnlTop = new Panel(manager); + pnlTop.Anchor = Anchors.Left | Anchors.Top | Anchors.Right; + pnlTop.Init(); + pnlTop.Parent = this; + pnlTop.Width = ClientWidth; + pnlTop.Height = 64; + pnlTop.BevelBorder = BevelBorder.Bottom; + + lblCapt = new Label(manager); + lblCapt.Init(); + lblCapt.Parent = pnlTop; + lblCapt.Width = lblCapt.Parent.ClientWidth - 16; + lblCapt.Text = "Caption"; + lblCapt.Left = 8; + lblCapt.Top = 8; + lblCapt.Alignment = Alignment.TopLeft; + lblCapt.Anchor = Anchors.Left | Anchors.Top | Anchors.Right; + + lblDesc = new Label(manager); + lblDesc.Init(); + lblDesc.Parent = pnlTop; + lblDesc.Width = lblDesc.Parent.ClientWidth - 16; + lblDesc.Left = 8; + lblDesc.Text = "Description text."; + lblDesc.Alignment = Alignment.TopLeft; + lblDesc.Anchor = Anchors.Left | Anchors.Top | Anchors.Right; + + pnlBottom = new Panel(manager); + pnlBottom.Init(); + pnlBottom.Parent = this; + pnlBottom.Width = ClientWidth; + pnlBottom.Height = 24 + 16; + pnlBottom.Top = ClientHeight - pnlBottom.Height; + pnlBottom.BevelBorder = BevelBorder.Top; + pnlBottom.Anchor = Anchors.Left | Anchors.Bottom | Anchors.Right; + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + SkinLayer lc = new SkinLayer(lblCapt.Skin.Layers[0]); + lc.Text.Font.Resource = Manager.Skin.Fonts[Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["CaptFont"].Value].Resource; + lc.Text.Colors.Enabled = Utilities.ParseColor(Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["CaptFontColor"].Value); + + SkinLayer ld = new SkinLayer(lblDesc.Skin.Layers[0]); + ld.Text.Font.Resource = Manager.Skin.Fonts[Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["DescFont"].Value].Resource; + ld.Text.Colors.Enabled = Utilities.ParseColor(Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["DescFontColor"].Value); + + pnlTop.Color = Utilities.ParseColor(Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["Color"].Value); + pnlTop.BevelMargin = int.Parse(Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["BevelMargin"].Value); + pnlTop.BevelStyle = Utilities.ParseBevelStyle(Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["BevelStyle"].Value); + + lblCapt.Skin = new SkinControl(lblCapt.Skin); + lblCapt.Skin.Layers[0] = lc; + lblCapt.Height = Manager.Skin.Fonts[Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["CaptFont"].Value].Height; + + lblDesc.Skin = new SkinControl(lblDesc.Skin); + lblDesc.Skin.Layers[0] = ld; + lblDesc.Height = Manager.Skin.Fonts[Manager.Skin.Controls["Dialog"].Layers["TopPanel"].Attributes["DescFont"].Value].Height; + lblDesc.Top = lblCapt.Top + lblCapt.Height + 4; + lblDesc.Height = lblDesc.Parent.ClientHeight - lblDesc.Top - 8; + + pnlBottom.Color = Utilities.ParseColor(Manager.Skin.Controls["Dialog"].Layers["BottomPanel"].Attributes["Color"].Value); + pnlBottom.BevelMargin = int.Parse(Manager.Skin.Controls["Dialog"].Layers["BottomPanel"].Attributes["BevelMargin"].Value); + pnlBottom.BevelStyle = Utilities.ParseBevelStyle(Manager.Skin.Controls["Dialog"].Layers["BottomPanel"].Attributes["BevelStyle"].Value); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Disposable.cs b/Neoforce/Disposable.cs new file mode 100644 index 0000000..74fbff4 --- /dev/null +++ b/Neoforce/Disposable.cs @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Disposable.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public abstract class Disposable: Unknown, IDisposable + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private static int count = 0; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public static int Count { get { return count; } } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + protected Disposable() + { + count += 1; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + ////////////////////////////////////////////////////////////////////////// + ~Disposable() + { + Dispose(false); + } + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + count -= 1; + } + } + ////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/EventArgs.cs b/Neoforce/EventArgs.cs new file mode 100644 index 0000000..28fad57 --- /dev/null +++ b/Neoforce/EventArgs.cs @@ -0,0 +1,427 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: EventArgs.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + public class EventArgs: System.EventArgs + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public bool Handled = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Consructors /////// + + //////////////////////////////////////////////////////////////////////////// + public EventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class KeyEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public Keys Key = Keys.None; + public bool Control = false; + public bool Shift = false; + public bool Alt = false; + public bool Caps = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public KeyEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public KeyEventArgs(Keys key) + { + Key = key; + Control = false; + Shift = false; + Alt = false; + Caps = false; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public KeyEventArgs(Keys key, bool control, bool shift, bool alt, bool caps) + { + Key = key; + Control = control; + Shift = shift; + Alt = alt; + Caps = caps; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class MouseEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public MouseState State = new MouseState(); + public MouseButton Button = MouseButton.None; + public Point Position = new Point(0, 0); + public Point Difference = new Point(0, 0); + /// + /// Mouse scroll direction + /// + public MouseScrollDirection ScrollDirection = MouseScrollDirection.None; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public MouseEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public MouseEventArgs(MouseState state, MouseButton button, Point position) + { + State = state; + Button = button; + Position = position; + } + //////////////////////////////////////////////////////////////////////////// + + /// + /// Creates a new initialized instace of the MouseEventArgs class. + /// Mouse state at the time of the event. + /// Mouse button state at the time of the event. + /// Mosue cursor position at the time of the event. + /// Mouse scroll direction at the time of the event. + public MouseEventArgs(MouseState state, MouseButton button, Point position, MouseScrollDirection scrollDirection) + : this(state, button, position) + { + ScrollDirection = scrollDirection; + } + + //////////////////////////////////////////////////////////////////////////// + public MouseEventArgs(MouseEventArgs e) + : this(e.State, e.Button, e.Position) + { + Difference = e.Difference; + ScrollDirection = e.ScrollDirection; + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class GamePadEventArgs : EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public PlayerIndex PlayerIndex = PlayerIndex.One; + public GamePadState State = new GamePadState(); + public GamePadButton Button = GamePadButton.None; + public GamePadVectors Vectors; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + /* + public GamePadEventArgs() + { + }*/ + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public GamePadEventArgs(PlayerIndex playerIndex) + { + PlayerIndex = playerIndex; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public GamePadEventArgs(PlayerIndex playerIndex, GamePadButton button) + { + PlayerIndex = playerIndex; + Button = button; + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class DrawEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public Renderer Renderer = null; + public Rectangle Rectangle = Rectangle.Empty; + public GameTime GameTime = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public DrawEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public DrawEventArgs(Renderer renderer, Rectangle rectangle, GameTime gameTime) + { + Renderer = renderer; + Rectangle = rectangle; + GameTime = gameTime; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class ResizeEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public int Width = 0; + public int Height = 0; + public int OldWidth = 0; + public int OldHeight = 0; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public ResizeEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public ResizeEventArgs(int width, int height, int oldWidth, int oldHeight) + { + Width = width; + Height = height; + OldWidth = oldWidth; + OldHeight = oldHeight; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class MoveEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public int Left = 0; + public int Top = 0; + public int OldLeft = 0; + public int OldTop = 0; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public MoveEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public MoveEventArgs(int left, int top, int oldLeft, int oldTop) + { + Left = left; + Top = top; + OldLeft = oldLeft; + OldTop = oldTop; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class DeviceEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public PreparingDeviceSettingsEventArgs DeviceSettings = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public DeviceEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public DeviceEventArgs(PreparingDeviceSettingsEventArgs deviceSettings) + { + DeviceSettings = deviceSettings; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class WindowClosingEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public bool Cancel = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Consructors /////// + + //////////////////////////////////////////////////////////////////////////// + public WindowClosingEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class WindowClosedEventArgs: EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public bool Dispose = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Consructors /////// + + //////////////////////////////////////////////////////////////////////////// + public WindowClosedEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class ConsoleMessageEventArgs : EventArgs + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public ConsoleMessage Message; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Consructors /////// + + //////////////////////////////////////////////////////////////////////////// + public ConsoleMessageEventArgs() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public ConsoleMessageEventArgs(ConsoleMessage message) + { + Message = message; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + +} diff --git a/Neoforce/EventedList.cs b/Neoforce/EventedList.cs new file mode 100644 index 0000000..9a4d13d --- /dev/null +++ b/Neoforce/EventedList.cs @@ -0,0 +1,146 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: EventedList.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections; +using System.Collections.Generic; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class EventedList: List + { + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler ItemAdded; + public event EventHandler ItemRemoved; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public EventedList(): base() {} + public EventedList(int capacity): base(capacity) {} + public EventedList(IEnumerable collection): base(collection) {} + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public new void Add(T item) + { + int c = this.Count; + base.Add(item); + if (ItemAdded != null && c != this.Count) ItemAdded.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new void Remove(T obj) + { + int c = this.Count; + base.Remove(obj); + if (ItemRemoved != null && c != this.Count) ItemRemoved.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new void Clear() + { + int c = this.Count; + base.Clear(); + if (ItemRemoved != null && c != this.Count) ItemRemoved.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new void AddRange(IEnumerable collection) + { + int c = this.Count; + base.AddRange(collection); + if (ItemAdded != null && c != this.Count) ItemAdded.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new void Insert(int index, T item) + { + int c = this.Count; + base.Insert(index, item); + if (ItemAdded != null && c != this.Count) ItemAdded.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new void InsertRange(int index, IEnumerable collection) + { + int c = this.Count; + base.InsertRange(index, collection); + if (ItemAdded != null && c != this.Count) ItemAdded.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new int RemoveAll(Predicate match) + { + int c = this.Count; + int ret = base.RemoveAll(match); + if (ItemRemoved != null && c != this.Count) ItemRemoved.Invoke(this, new EventArgs()); + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new void RemoveAt(int index) + { + int c = this.Count; + base.RemoveAt(index); + if (ItemRemoved != null && c != this.Count) ItemRemoved.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public new void RemoveRange(int index, int count) + { + int c = this.Count; + base.RemoveRange(index, count); + if (ItemRemoved != null && c != this.Count) ItemRemoved.Invoke(this, new EventArgs()); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/ExitDialog.cs b/Neoforce/ExitDialog.cs new file mode 100644 index 0000000..4a16f58 --- /dev/null +++ b/Neoforce/ExitDialog.cs @@ -0,0 +1,130 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Central // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ExitDialog.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ExitDialog: Dialog + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public Button btnYes; + public Button btnNo; + private Label lblMessage; + private ImageBox imgIcon; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ExitDialog(Manager manager): base(manager) + { + string msg = "Do you really want to exit " + Manager.Game.Window.Title + "?"; + ClientWidth = (int)Manager.Skin.Controls["Label"].Layers[0].Text.Font.Resource.MeasureString(msg).X + 48 + 16 + 16 + 16; + ClientHeight = 120; + TopPanel.Visible = false; + IconVisible = true; + Resizable = false; + Text = Manager.Game.Window.Title; + Center(); + + imgIcon = new ImageBox(Manager); + imgIcon.Init(); + imgIcon.Image = Manager.Skin.Images["Icon.Question"].Resource; + imgIcon.Left = 16; + imgIcon.Top = 16; + imgIcon.Width = 48; + imgIcon.Height = 48; + imgIcon.SizeMode = SizeMode.Stretched; + + lblMessage = new Label(Manager); + lblMessage.Init(); + + lblMessage.Left = 80; + lblMessage.Top = 16; + lblMessage.Width = ClientWidth - lblMessage.Left; + lblMessage.Height = 48; + lblMessage.Alignment = Alignment.TopLeft; + lblMessage.Text = msg; + + btnYes = new Button(Manager); + btnYes.Init(); + btnYes.Left = (BottomPanel.ClientWidth / 2) - btnYes.Width - 4; + btnYes.Top = 8; + btnYes.Text = "Yes"; + btnYes.ModalResult = ModalResult.Yes; + + btnNo = new Button(Manager); + btnNo.Init(); + btnNo.Left = (BottomPanel.ClientWidth / 2) + 4; + btnNo.Top = 8; + btnNo.Text = "No"; + btnNo.ModalResult = ModalResult.No; + + Add(imgIcon); + Add(lblMessage); + BottomPanel.Add(btnYes); + BottomPanel.Add(btnNo); + + DefaultControl = btnNo; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/External/Zip/Crc32.cs b/Neoforce/External/Zip/Crc32.cs new file mode 100644 index 0000000..6bbf8b1 --- /dev/null +++ b/Neoforce/External/Zip/Crc32.cs @@ -0,0 +1,84 @@ +using System; + +namespace TomShane.Neoforce.External.Zip +{ + internal class CRC32 + { + private UInt32[] crc32Table; + private const int BUFFER_SIZE = 8192; + + private Int32 _TotalBytesRead= 0; + public Int32 TotalBytesRead { + get { + return _TotalBytesRead; + } + } + + public UInt32 GetCrc32(System.IO.Stream input) + { + return GetCrc32AndCopy(input, null) ; + } + + public UInt32 GetCrc32AndCopy(System.IO.Stream input, System.IO.Stream output) + { + unchecked + { + UInt32 crc32Result; + crc32Result = 0xFFFFFFFF; + byte[] buffer = new byte[BUFFER_SIZE]; + int readSize = BUFFER_SIZE; + + _TotalBytesRead= 0; + int count = input.Read(buffer, 0, readSize); + if (output != null) output.Write(buffer,0,count); + _TotalBytesRead += count; + while (count > 0) + { + for (int i = 0; i < count; i++) + { + crc32Result = ((crc32Result) >> 8) ^ crc32Table[(buffer[i]) ^ ((crc32Result) & 0x000000FF)]; + } + count = input.Read(buffer, 0, readSize); + if (output != null) output.Write(buffer,0,count); + _TotalBytesRead += count; + + } + + return ~crc32Result; + } + } + + + public CRC32() + { + unchecked + { + // This is the official polynomial used by CRC32 in PKZip. + // Often the polynomial is shown reversed as 0x04C11DB7. + UInt32 dwPolynomial = 0xEDB88320; + UInt32 i, j; + + crc32Table = new UInt32[256]; + + UInt32 dwCrc; + for(i = 0; i < 256; i++) + { + dwCrc = i; + for(j = 8; j > 0; j--) + { + if ((dwCrc & 1)==1) + { + dwCrc = (dwCrc >> 1) ^ dwPolynomial; + } + else + { + dwCrc >>= 1; + } + } + crc32Table[i] = dwCrc; + } + } + } + } + +} diff --git a/Neoforce/External/Zip/Shared.cs b/Neoforce/External/Zip/Shared.cs new file mode 100644 index 0000000..60b7c9a --- /dev/null +++ b/Neoforce/External/Zip/Shared.cs @@ -0,0 +1,112 @@ +using System; + +namespace TomShane.Neoforce.External.Zip +{ + + internal class Shared + { + protected internal static string StringFromBuffer(byte[] buf, int start, int maxlength) + { + int i; + char[] c = new char[maxlength]; + for (i = 0; (i < maxlength) && (i < buf.Length) && (buf[i] != 0); i++) + { + c[i] = (char)buf[i]; // System.BitConverter.ToChar(buf, start+i*2); + } + string s = new System.String(c, 0, i); + return s; + } + + protected internal static int ReadSignature(System.IO.Stream s) + { + int n = 0; + byte[] sig = new byte[4]; + n = s.Read(sig, 0, sig.Length); + if (n != sig.Length) throw new Exception("Could not read signature - no data!"); + int signature = (((sig[3] * 256 + sig[2]) * 256) + sig[1]) * 256 + sig[0]; + return signature; + } + + protected internal static long FindSignature(System.IO.Stream s, int SignatureToFind) + { + long startingPosition = s.Position; + + int BATCH_SIZE = 1024; + byte[] targetBytes = new byte[4]; + targetBytes[0] = (byte) (SignatureToFind >> 24); + targetBytes[1] = (byte) ((SignatureToFind & 0x00FF0000) >> 16); + targetBytes[2] = (byte) ((SignatureToFind & 0x0000FF00) >> 8); + targetBytes[3] = (byte) (SignatureToFind & 0x000000FF); + byte[] batch = new byte[BATCH_SIZE]; + int n = 0; + bool success = false; + do + { + n = s.Read(batch, 0, batch.Length); + if (n != 0) + { + for (int i = 0; i < n; i++) + { + if (batch[i] == targetBytes[3]) + { + s.Seek(i - n, System.IO.SeekOrigin.Current); + int sig = ReadSignature(s); + success = (sig == SignatureToFind); + if (!success) s.Seek(-3, System.IO.SeekOrigin.Current); + break; // out of for loop + } + } + } + else break; + if (success) break; + } while (true); + if (!success) + { + s.Seek(startingPosition, System.IO.SeekOrigin.Begin); + return -1; // or throw? + } + + // subtract 4 for the signature. + long bytesRead = (s.Position - startingPosition) - 4 ; + // number of bytes read, should be the same as compressed size of file + return bytesRead; + } + protected internal static DateTime PackedToDateTime(Int32 packedDateTime) + { + Int16 packedTime = (Int16)(packedDateTime & 0x0000ffff); + Int16 packedDate = (Int16)((packedDateTime & 0xffff0000) >> 16); + + int year = 1980 + ((packedDate & 0xFE00) >> 9); + int month = (packedDate & 0x01E0) >> 5; + int day = packedDate & 0x001F; + + + int hour = (packedTime & 0xF800) >> 11; + int minute = (packedTime & 0x07E0) >> 5; + int second = packedTime & 0x001F; + + DateTime d = System.DateTime.Now; + try { d = new System.DateTime(year, month, day, hour, minute, second, 0); } + catch + { + Console.Write("\nInvalid date/time?:\nyear: {0} ", year); + Console.Write("month: {0} ", month); + Console.WriteLine("day: {0} ", day); + Console.WriteLine("HH:MM:SS= {0}:{1}:{2}", hour, minute, second); + } + + return d; + } + + + protected internal static Int32 DateTimeToPacked(DateTime time) + { + UInt16 packedDate = (UInt16)((time.Day & 0x0000001F) | ((time.Month << 5) & 0x000001E0) | (((time.Year - 1980) << 9) & 0x0000FE00)); + UInt16 packedTime = (UInt16)((time.Second & 0x0000001F) | ((time.Minute << 5) & 0x000007E0) | ((time.Hour << 11) & 0x0000F800)); + return (Int32)(((UInt32)(packedDate << 16)) | packedTime); + } + } + + + +} diff --git a/Neoforce/External/Zip/ZipDirEntry.cs b/Neoforce/External/Zip/ZipDirEntry.cs new file mode 100644 index 0000000..a4fdd3e --- /dev/null +++ b/Neoforce/External/Zip/ZipDirEntry.cs @@ -0,0 +1,149 @@ +using System; + +namespace TomShane.Neoforce.External.Zip +{ + + + internal class ZipDirEntry + { + + internal const int ZipDirEntrySignature = 0x02014b50; + + private bool _Debug = false; + + private ZipDirEntry() { } + + private DateTime _LastModified; + public DateTime LastModified + { + get { return _LastModified; } + } + + private string _FileName; + public string FileName + { + get { return _FileName; } + } + + private string _Comment; + public string Comment + { + get { return _Comment; } + } + + private Int16 _VersionMadeBy; + public Int16 VersionMadeBy + { + get { return _VersionMadeBy; } + } + + private Int16 _VersionNeeded; + public Int16 VersionNeeded + { + get { return _VersionNeeded; } + } + + private Int16 _CompressionMethod; + public Int16 CompressionMethod + { + get { return _CompressionMethod; } + } + + private Int32 _CompressedSize; + public Int32 CompressedSize + { + get { return _CompressedSize; } + } + + private Int32 _UncompressedSize; + public Int32 UncompressedSize + { + get { return _UncompressedSize; } + } + + public Double CompressionRatio + { + get + { + return 100 * (1.0 - (1.0 * CompressedSize) / (1.0 * UncompressedSize)); + } + } + + private Int16 _BitField; + private Int32 _LastModDateTime; + + private Int32 _Crc32; + private byte[] _Extra; + + internal ZipDirEntry(ZipEntry ze) { } + + + internal static ZipDirEntry Read(System.IO.Stream s) + { + return Read(s, false); + } + + + internal static ZipDirEntry Read(System.IO.Stream s, bool TurnOnDebug) + { + + int signature = TomShane.Neoforce.External.Zip.Shared.ReadSignature(s); + // return null if this is not a local file header signature + if (SignatureIsNotValid(signature)) + { + s.Seek(-4, System.IO.SeekOrigin.Current); + if (TurnOnDebug) System.Console.WriteLine(" ZipDirEntry::Read(): Bad signature ({0:X8}) at position {1}", signature, s.Position); + return null; + } + + byte[] block = new byte[42]; + int n = s.Read(block, 0, block.Length); + if (n != block.Length) return null; + + int i = 0; + ZipDirEntry zde = new ZipDirEntry(); + + zde._Debug = TurnOnDebug; + zde._VersionMadeBy = (short)(block[i++] + block[i++] * 256); + zde._VersionNeeded = (short)(block[i++] + block[i++] * 256); + zde._BitField = (short)(block[i++] + block[i++] * 256); + zde._CompressionMethod = (short)(block[i++] + block[i++] * 256); + zde._LastModDateTime = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + zde._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + zde._CompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + zde._UncompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + + zde._LastModified = TomShane.Neoforce.External.Zip.Shared.PackedToDateTime(zde._LastModDateTime); + + Int16 filenameLength = (short)(block[i++] + block[i++] * 256); + Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256); + Int16 commentLength = (short)(block[i++] + block[i++] * 256); + Int16 diskNumber = (short)(block[i++] + block[i++] * 256); + Int16 internalFileAttrs = (short)(block[i++] + block[i++] * 256); + Int32 externalFileAttrs = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + Int32 Offset = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + + block = new byte[filenameLength]; + n = s.Read(block, 0, block.Length); + zde._FileName = TomShane.Neoforce.External.Zip.Shared.StringFromBuffer(block, 0, block.Length); + + zde._Extra = new byte[extraFieldLength]; + n = s.Read(zde._Extra, 0, zde._Extra.Length); + + block = new byte[commentLength]; + n = s.Read(block, 0, block.Length); + zde._Comment = TomShane.Neoforce.External.Zip.Shared.StringFromBuffer(block, 0, block.Length); + + return zde; + } + + private static bool SignatureIsNotValid(int signature) + { + return (signature != ZipDirEntrySignature); + } + + } + + + +} diff --git a/Neoforce/External/Zip/ZipEntry.cs b/Neoforce/External/Zip/ZipEntry.cs new file mode 100644 index 0000000..e2d45f2 --- /dev/null +++ b/Neoforce/External/Zip/ZipEntry.cs @@ -0,0 +1,699 @@ +using System; + +namespace TomShane.Neoforce.External.Zip +{ + internal class ZipEntry + { + + private const int ZipEntrySignature = 0x04034b50; + private const int ZipEntryDataDescriptorSignature= 0x08074b50; + + private bool _Debug = false; + + private DateTime _LastModified; + public DateTime LastModified + { + get { return _LastModified; } + } + + // when this is set, we trim the volume (eg C:\) off any fully-qualified pathname, + // before writing the ZipEntry into the ZipFile. + private bool _TrimVolumeFromFullyQualifiedPaths= true; // by default, trim them. + public bool TrimVolumeFromFullyQualifiedPaths + { + get { return _TrimVolumeFromFullyQualifiedPaths; } + set { _TrimVolumeFromFullyQualifiedPaths= value; } + } + + private string _FileName; + public string FileName + { + get { return _FileName; } + } + + private Int16 _VersionNeeded; + public Int16 VersionNeeded + { + get { return _VersionNeeded; } + } + + private Int16 _BitField; + public Int16 BitField + { + get { return _BitField; } + } + + private Int16 _CompressionMethod; + public Int16 CompressionMethod + { + get { return _CompressionMethod; } + } + + private Int32 _CompressedSize; + public Int32 CompressedSize + { + get { return _CompressedSize; } + } + + private Int32 _UncompressedSize; + public Int32 UncompressedSize + { + get { return _UncompressedSize; } + } + + public Double CompressionRatio + { + get + { + return 100 * (1.0 - (1.0 * CompressedSize) / (1.0 * UncompressedSize)); + } + } + + private Int32 _LastModDateTime; + private Int32 _Crc32; + private byte[] _Extra; + + private byte[] __filedata; + private byte[] _FileData + { + get + { + if (__filedata == null) + { + } + return __filedata; + } + } + + private System.IO.MemoryStream _UnderlyingMemoryStream; + private System.IO.Compression.DeflateStream _CompressedStream; + private System.IO.Compression.DeflateStream CompressedStream + { + get + { + if (_CompressedStream == null) + { + _UnderlyingMemoryStream = new System.IO.MemoryStream(); + bool LeaveUnderlyingStreamOpen = true; + _CompressedStream = new System.IO.Compression.DeflateStream(_UnderlyingMemoryStream, + System.IO.Compression.CompressionMode.Compress, + LeaveUnderlyingStreamOpen); + } + return _CompressedStream; + } + } + + private byte[] _header; + internal byte[] Header + { + get + { + return _header; + } + } + + private int _RelativeOffsetOfHeader; + + + private static bool ReadHeader(System.IO.Stream s, ZipEntry ze) + { + int signature = TomShane.Neoforce.External.Zip.Shared.ReadSignature(s); + + // return null if this is not a local file header signature + if (SignatureIsNotValid(signature)) + { + s.Seek(-4, System.IO.SeekOrigin.Current); + if (ze._Debug) System.Console.WriteLine(" ZipEntry::Read(): Bad signature ({0:X8}) at position {1}", signature, s.Position); + return false; + } + + byte[] block = new byte[26]; + int n = s.Read(block, 0, block.Length); + if (n != block.Length) return false; + + int i = 0; + ze._VersionNeeded = (short)(block[i++] + block[i++] * 256); + ze._BitField = (short)(block[i++] + block[i++] * 256); + ze._CompressionMethod = (short)(block[i++] + block[i++] * 256); + ze._LastModDateTime = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + + // the PKZIP spec says that if bit 3 is set (0x0008), then the CRC, Compressed size, and uncompressed size + // come directly after the file data. The only way to find it is to scan the zip archive for the signature of + // the Data Descriptor, and presume that that signature does not appear in the (compressed) data of the compressed file. + + if ((ze._BitField & 0x0008) != 0x0008) + { + ze._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + ze._CompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + ze._UncompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + } + else + { + // the CRC, compressed size, and uncompressed size are stored later in the stream. + // here, we advance the pointer. + i += 12; + } + + Int16 filenameLength = (short)(block[i++] + block[i++] * 256); + Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256); + + block = new byte[filenameLength]; + n = s.Read(block, 0, block.Length); + ze._FileName = TomShane.Neoforce.External.Zip.Shared.StringFromBuffer(block, 0, block.Length); + + ze._Extra = new byte[extraFieldLength]; + n = s.Read(ze._Extra, 0, ze._Extra.Length); + + // transform the time data into something usable + ze._LastModified = TomShane.Neoforce.External.Zip.Shared.PackedToDateTime(ze._LastModDateTime); + + // actually get the compressed size and CRC if necessary + if ((ze._BitField & 0x0008) == 0x0008) + { + long posn = s.Position; + long SizeOfDataRead = TomShane.Neoforce.External.Zip.Shared.FindSignature(s, ZipEntryDataDescriptorSignature); + if (SizeOfDataRead == -1) return false; + + // read 3x 4-byte fields (CRC, Compressed Size, Uncompressed Size) + block = new byte[12]; + n = s.Read(block, 0, block.Length); + if (n != 12) return false; + i = 0; + ze._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + ze._CompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + ze._UncompressedSize = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; + + if (SizeOfDataRead != ze._CompressedSize) + throw new Exception("Data format error (bit 3 is set)"); + + // seek back to previous position, to read file data + s.Seek(posn, System.IO.SeekOrigin.Begin); + } + + return true; + } + + + private static bool SignatureIsNotValid(int signature) + { + return (signature != ZipEntrySignature); + } + + + public static ZipEntry Read(System.IO.Stream s) + { + return Read(s, false); + } + + + internal static ZipEntry Read(System.IO.Stream s, bool TurnOnDebug) + { + ZipEntry entry = new ZipEntry(); + entry._Debug = TurnOnDebug; + if (!ReadHeader(s, entry)) return null; + + entry.__filedata = new byte[entry.CompressedSize]; + int n = s.Read(entry._FileData, 0, entry._FileData.Length); + if (n != entry._FileData.Length) + { + throw new Exception("badly formatted zip file."); + } + // finally, seek past the (already read) Data descriptor if necessary + if ((entry._BitField & 0x0008) == 0x0008) + { + s.Seek(16, System.IO.SeekOrigin.Current); + } + return entry; + } + + + + internal static ZipEntry Create(String filename) + { + ZipEntry entry = new ZipEntry(); + entry._FileName = filename; + + entry._LastModified = System.IO.File.GetLastWriteTime(filename); + // adjust the time if the .NET BCL thinks it is in DST. + // see the note elsewhere in this file for more info. + if (entry._LastModified.IsDaylightSavingTime()) + { + System.DateTime AdjustedTime = entry._LastModified - new System.TimeSpan(1, 0, 0); + entry._LastModDateTime = TomShane.Neoforce.External.Zip.Shared.DateTimeToPacked(AdjustedTime); + } + else + entry._LastModDateTime = TomShane.Neoforce.External.Zip.Shared.DateTimeToPacked(entry._LastModified); + + // we don't actually slurp in the file until the caller invokes Write on this entry. + + return entry; + } + + + + public void Extract() + { + Extract("."); + } + + public void Extract(System.IO.Stream s) + { + Extract(null, s); + } + + public void Extract(string basedir) + { + Extract(basedir, null); + } + + + internal System.IO.Stream GetStream() + { + System.IO.MemoryStream memstream = new System.IO.MemoryStream(_FileData); + + if (CompressedSize == UncompressedSize) + return memstream; + + return new System.IO.Compression.DeflateStream( + memstream, System.IO.Compression.CompressionMode.Decompress); + } + + // pass in either basedir or s, but not both. + // In other words, you can extract to a stream or to a directory, but not both! + private void Extract(string basedir, System.IO.Stream s) + { + string TargetFile = null; + if (basedir != null) + { + TargetFile = System.IO.Path.Combine(basedir, FileName); + + // check if a directory + if (FileName.EndsWith("/")) + { + if (!System.IO.Directory.Exists(TargetFile)) + System.IO.Directory.CreateDirectory(TargetFile); + return; + } + } + else if (s != null) + { + if (FileName.EndsWith("/")) + // extract a directory to streamwriter? nothing to do! + return; + } + else throw new Exception("Invalid input."); + + + using (System.IO.MemoryStream memstream = new System.IO.MemoryStream(_FileData)) + { + + System.IO.Stream input = null; + try + { + + if (CompressedSize == UncompressedSize) + { + // the System.IO.Compression.DeflateStream class does not handle uncompressed data. + // so if an entry is not compressed, then we just translate the bytes directly. + input = memstream; + } + else + { + input = new System.IO.Compression.DeflateStream(memstream, System.IO.Compression.CompressionMode.Decompress); + } + + + if (TargetFile != null) + { + // ensure the target path exists + if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(TargetFile))) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(TargetFile)); + } + } + + + System.IO.Stream output = null; + try + { + if (TargetFile != null) + output = new System.IO.FileStream(TargetFile, System.IO.FileMode.CreateNew); + else + output = s; + + + byte[] bytes = new byte[4096]; + int n; + + if (_Debug) + { + Console.WriteLine("{0}: _FileData.Length= {1}", TargetFile, _FileData.Length); + Console.WriteLine("{0}: memstream.Position: {1}", TargetFile, memstream.Position); + n = _FileData.Length; + if (n > 1000) + { + n = 500; + Console.WriteLine("{0}: truncating dump from {1} to {2} bytes...", TargetFile, _FileData.Length, n); + } + for (int j = 0; j < n; j += 2) + { + if ((j > 0) && (j % 40 == 0)) + System.Console.WriteLine(); + System.Console.Write(" {0:X2}", _FileData[j]); + if (j + 1 < n) + System.Console.Write("{0:X2}", _FileData[j + 1]); + } + System.Console.WriteLine("\n"); + } + + n = 1; // anything non-zero + while (n != 0) + { + if (_Debug) Console.WriteLine("{0}: about to read...", TargetFile); + n = input.Read(bytes, 0, bytes.Length); + if (_Debug) Console.WriteLine("{0}: got {1} bytes", TargetFile, n); + if (n > 0) + { + if (_Debug) Console.WriteLine("{0}: about to write...", TargetFile); + output.Write(bytes, 0, n); + } + } + } + finally + { + // we only close the output stream if we opened it. + if ((output != null) && (TargetFile != null)) + { + output.Close(); + output.Dispose(); + } + } + + if (TargetFile != null) + { + // We may have to adjust the last modified time to compensate + // for differences in how the .NET Base Class Library deals + // with daylight saving time (DST) versus how the Windows + // filesystem deals with daylight saving time. See + // http://blogs.msdn.com/oldnewthing/archive/2003/10/24/55413.aspx for some context. + + // in a nutshell: Daylight savings time rules change regularly. In + // 2007, for example, the inception week of DST changed. In 1977, + // DST was in place all year round. in 1945, likewise. And so on. + // Win32 does not attempt to guess which time zone rules were in + // effect at the time in question. It will render a time as + // "standard time" and allow the app to change to DST as necessary. + // .NET makes a different choice. + + // ------------------------------------------------------- + // Compare the output of FileInfo.LastWriteTime.ToString("f") with + // what you see in the property sheet for a file that was last + // written to on the other side of the DST transition. For example, + // suppose the file was last modified on October 17, during DST but + // DST is not currently in effect. Explorer's file properties + // reports Thursday, October 17, 2003, 8:45:38 AM, but .NETs + // FileInfo reports Thursday, October 17, 2003, 9:45 AM. + + // Win32 says, "Thursday, October 17, 2002 8:45:38 AM PST". Note: + // Pacific STANDARD Time. Even though October 17 of that year + // occurred during Pacific Daylight Time, Win32 displays the time as + // standard time because that's what time it is NOW. + + // .NET BCL assumes that the current DST rules were in place at the + // time in question. So, .NET says, "Well, if the rules in effect + // now were also in effect on October 17, 2003, then that would be + // daylight time" so it displays "Thursday, October 17, 2003, 9:45 + // AM PDT" - daylight time. + + // So .NET gives a value which is more intuitively correct, but is + // also potentially incorrect, and which is not invertible. Win32 + // gives a value which is intuitively incorrect, but is strictly + // correct. + // ------------------------------------------------------- + + // With this adjustment, I add one hour to the tweaked .NET time, if + // necessary. That is to say, if the time in question had occurred + // in what the .NET BCL assumed to be DST (an assumption that may be + // wrong given the constantly changing DST rules). + +#if !XBOX + if (LastModified.IsDaylightSavingTime()) + { + DateTime AdjustedLastModified = LastModified + new System.TimeSpan(1, 0, 0); + System.IO.File.SetLastWriteTime(TargetFile, AdjustedLastModified); + } + else + System.IO.File.SetLastWriteTime(TargetFile, LastModified); +#endif + } + + } + finally + { + // we only close the output stream if we opened it. + // we cannot use using() here because in some cases we do not want to Dispose the stream! + if ((input != null) && (input != memstream)) + { + input.Close(); + input.Dispose(); + } + } + } + } + + + internal void WriteCentralDirectoryEntry(System.IO.Stream s) + { + byte[] bytes = new byte[4096]; + int i = 0; + // signature + bytes[i++] = (byte)(ZipDirEntry.ZipDirEntrySignature & 0x000000FF); + bytes[i++] = (byte)((ZipDirEntry.ZipDirEntrySignature & 0x0000FF00) >> 8); + bytes[i++] = (byte)((ZipDirEntry.ZipDirEntrySignature & 0x00FF0000) >> 16); + bytes[i++] = (byte)((ZipDirEntry.ZipDirEntrySignature & 0xFF000000) >> 24); + + // Version Made By + bytes[i++] = Header[4]; + bytes[i++] = Header[5]; + + // Version Needed, Bitfield, compression method, lastmod, + // crc, sizes, filename length and extra field length - + // are all the same as the local file header. So just copy them + int j = 0; + for (j = 0; j < 26; j++) + bytes[i + j] = Header[4 + j]; + + i += j; // positioned at next available byte + + // File Comment Length + bytes[i++] = 0; + bytes[i++] = 0; + + // Disk number start + bytes[i++] = 0; + bytes[i++] = 0; + + // internal file attrs + bytes[i++] = 1; + bytes[i++] = 0; + + // external file attrs + bytes[i++] = 0x20; + bytes[i++] = 0; + bytes[i++] = 0xb6; + bytes[i++] = 0x81; + + // relative offset of local header (I think this can be zero) + bytes[i++] = (byte)(_RelativeOffsetOfHeader & 0x000000FF); + bytes[i++] = (byte)((_RelativeOffsetOfHeader & 0x0000FF00) >> 8); + bytes[i++] = (byte)((_RelativeOffsetOfHeader & 0x00FF0000) >> 16); + bytes[i++] = (byte)((_RelativeOffsetOfHeader & 0xFF000000) >> 24); + + if (_Debug) System.Console.WriteLine("\ninserting filename into CDS: (length= {0})", Header.Length - 30); + // actual filename (starts at offset 34 in header) + for (j = 0; j < Header.Length - 30; j++) + { + bytes[i + j] = Header[30 + j]; + if (_Debug) System.Console.Write(" {0:X2}", bytes[i + j]); + } + if (_Debug) System.Console.WriteLine(); + i += j; + + s.Write(bytes, 0, i); + } + + + private void WriteHeader(System.IO.Stream s, byte[] bytes) + { + // write the header info + + int i = 0; + // signature + bytes[i++] = (byte)(ZipEntrySignature & 0x000000FF); + bytes[i++] = (byte)((ZipEntrySignature & 0x0000FF00) >> 8); + bytes[i++] = (byte)((ZipEntrySignature & 0x00FF0000) >> 16); + bytes[i++] = (byte)((ZipEntrySignature & 0xFF000000) >> 24); + + // version needed + Int16 FixedVersionNeeded = 0x14; // from examining existing zip files + bytes[i++] = (byte)(FixedVersionNeeded & 0x00FF); + bytes[i++] = (byte)((FixedVersionNeeded & 0xFF00) >> 8); + + // bitfield + Int16 BitField = 0x00; // from examining existing zip files + bytes[i++] = (byte)(BitField & 0x00FF); + bytes[i++] = (byte)((BitField & 0xFF00) >> 8); + + // compression method + Int16 CompressionMethod = 0x08; // 0x08 = Deflate + bytes[i++] = (byte)(CompressionMethod & 0x00FF); + bytes[i++] = (byte)((CompressionMethod & 0xFF00) >> 8); + + // LastMod + bytes[i++] = (byte)(_LastModDateTime & 0x000000FF); + bytes[i++] = (byte)((_LastModDateTime & 0x0000FF00) >> 8); + bytes[i++] = (byte)((_LastModDateTime & 0x00FF0000) >> 16); + bytes[i++] = (byte)((_LastModDateTime & 0xFF000000) >> 24); + + // CRC32 (Int32) + CRC32 crc32 = new CRC32(); + UInt32 crc = 0; + using (System.IO.Stream input = System.IO.File.OpenRead(FileName)) + { + crc = crc32.GetCrc32AndCopy(input, CompressedStream); + } + CompressedStream.Close(); // to get the footer bytes written to the underlying stream + + bytes[i++] = (byte)(crc & 0x000000FF); + bytes[i++] = (byte)((crc & 0x0000FF00) >> 8); + bytes[i++] = (byte)((crc & 0x00FF0000) >> 16); + bytes[i++] = (byte)((crc & 0xFF000000) >> 24); + + // CompressedSize (Int32) + Int32 isz = (Int32)_UnderlyingMemoryStream.Length; + UInt32 sz = (UInt32)isz; + bytes[i++] = (byte)(sz & 0x000000FF); + bytes[i++] = (byte)((sz & 0x0000FF00) >> 8); + bytes[i++] = (byte)((sz & 0x00FF0000) >> 16); + bytes[i++] = (byte)((sz & 0xFF000000) >> 24); + + // UncompressedSize (Int32) + if (_Debug) System.Console.WriteLine("Uncompressed Size: {0}", crc32.TotalBytesRead); + bytes[i++] = (byte)(crc32.TotalBytesRead & 0x000000FF); + bytes[i++] = (byte)((crc32.TotalBytesRead & 0x0000FF00) >> 8); + bytes[i++] = (byte)((crc32.TotalBytesRead & 0x00FF0000) >> 16); + bytes[i++] = (byte)((crc32.TotalBytesRead & 0xFF000000) >> 24); + + // filename length (Int16) + Int16 length = (Int16)FileName.Length; + // see note below about TrimVolumeFromFullyQualifiedPaths. + if ( (TrimVolumeFromFullyQualifiedPaths) && (FileName[1]==':') && (FileName[2]=='\\')) length-=3; + bytes[i++] = (byte)(length & 0x00FF); + bytes[i++] = (byte)((length & 0xFF00) >> 8); + + // extra field length (short) + Int16 ExtraFieldLength = 0x00; + bytes[i++] = (byte)(ExtraFieldLength & 0x00FF); + bytes[i++] = (byte)((ExtraFieldLength & 0xFF00) >> 8); + + // Tue, 27 Mar 2007 16:35 + + // Creating a zip that contains entries with "fully qualified" pathnames + // can result in a zip archive that is unreadable by Windows Explorer. + // Such archives are valid according to other tools but not to explorer. + // To avoid this, we can trim off the leading volume name and slash (eg + // c:\) when creating (writing) a zip file. We do this by default and we + // leave the old behavior available with the + // TrimVolumeFromFullyQualifiedPaths flag - set it to false to get the old + // behavior. It only affects zip creation. + + // actual filename + char[] c = ( (TrimVolumeFromFullyQualifiedPaths) && (FileName[1]==':') && (FileName[2]=='\\')) ? + FileName.Substring(3).ToCharArray() : // trim off volume letter, colon, and slash + FileName.ToCharArray(); + int j = 0; + + if (_Debug) + { + System.Console.WriteLine("local header: writing filename, {0} chars", c.Length); + System.Console.WriteLine("starting offset={0}", i); + } + for (j = 0; (j < c.Length) && (i + j < bytes.Length); j++) + { + bytes[i + j] = System.BitConverter.GetBytes(c[j])[0]; + if (_Debug) System.Console.Write(" {0:X2}", bytes[i + j]); + } + if (_Debug) System.Console.WriteLine(); + + i += j; + + // extra field (we always write nothing in this implementation) + // ;; + + // remember the file offset of this header + _RelativeOffsetOfHeader = (int)s.Length; + + + if (_Debug) + { + System.Console.WriteLine("\nAll header data:"); + for (j = 0; j < i; j++) + System.Console.Write(" {0:X2}", bytes[j]); + System.Console.WriteLine(); + } + // finally, write the header to the stream + s.Write(bytes, 0, i); + + // preserve this header data for use with the central directory structure. + _header = new byte[i]; + if (_Debug) System.Console.WriteLine("preserving header of {0} bytes", _header.Length); + for (j = 0; j < i; j++) + _header[j] = bytes[j]; + + } + + + internal void Write(System.IO.Stream s) + { + byte[] bytes = new byte[4096]; + int n; + + // write the header: + WriteHeader(s, bytes); + + // write the actual file data: + _UnderlyingMemoryStream.Position = 0; + + if (_Debug) + { + Console.WriteLine("{0}: writing compressed data to zipfile...", FileName); + Console.WriteLine("{0}: total data length: {1}", FileName, _UnderlyingMemoryStream.Length); + } + while ((n = _UnderlyingMemoryStream.Read(bytes, 0, bytes.Length)) != 0) + { + + if (_Debug) + { + Console.WriteLine("{0}: transferring {1} bytes...", FileName, n); + + for (int j = 0; j < n; j += 2) + { + if ((j > 0) && (j % 40 == 0)) + System.Console.WriteLine(); + System.Console.Write(" {0:X2}", bytes[j]); + if (j + 1 < n) + System.Console.Write("{0:X2}", bytes[j + 1]); + } + System.Console.WriteLine("\n"); + } + + s.Write(bytes, 0, n); + } + + //_CompressedStream.Close(); + //_CompressedStream= null; + _UnderlyingMemoryStream.Close(); + _UnderlyingMemoryStream = null; + } + } +} diff --git a/Neoforce/External/Zip/ZipFile.cs b/Neoforce/External/Zip/ZipFile.cs new file mode 100644 index 0000000..8a0c569 --- /dev/null +++ b/Neoforce/External/Zip/ZipFile.cs @@ -0,0 +1,505 @@ +using System; + +namespace TomShane.Neoforce.External.Zip +{ + + internal class ZipFile : System.Collections.Generic.IEnumerable, + IDisposable + { + private string _name; + public string Name + { + get { return _name; } + } + + + + // when this is set, we trim the volume (eg C:) off any fully-qualified pathname, + // before writing the ZipEntry into the ZipFile. + // We default this to true. This allows Windows Explorer to read the zip archives properly. + private bool _TrimVolumeFromFullyQualifiedPaths= true; + public bool TrimVolumeFromFullyQualifiedPaths + { + get { return _TrimVolumeFromFullyQualifiedPaths; } + set { _TrimVolumeFromFullyQualifiedPaths= value; } + } + + private System.IO.Stream ReadStream + { + get + { + if (_readstream == null) + { + _readstream = System.IO.File.OpenRead(_name); + } + return _readstream; + } + } + + private System.IO.FileStream WriteStream + { + get + { + if (_writestream == null) + { + _writestream = new System.IO.FileStream(_name, System.IO.FileMode.CreateNew); + } + return _writestream; + } + } + + private ZipFile() { } + + + #region For Writing Zip Files + + public ZipFile(string NewZipFileName) + { + // create a new zipfile + _name = NewZipFileName; + if (System.IO.File.Exists(_name)) + throw new System.Exception(String.Format("That file ({0}) already exists.", NewZipFileName)); + _entries = new System.Collections.Generic.List(); + } + + + public void AddItem(string FileOrDirectoryName) + { + AddItem(FileOrDirectoryName, false); + } + + public void AddItem(string FileOrDirectoryName, bool WantVerbose) + { + if (System.IO.File.Exists(FileOrDirectoryName)) + AddFile(FileOrDirectoryName, WantVerbose); + else if (System.IO.Directory.Exists(FileOrDirectoryName)) + AddDirectory(FileOrDirectoryName, WantVerbose); + + else + throw new Exception(String.Format("That file or directory ({0}) does not exist!", FileOrDirectoryName)); + } + + public void AddFile(string FileName) + { + AddFile(FileName, false); + } + + public void AddFile(string FileName, bool WantVerbose) + { + ZipEntry ze = ZipEntry.Create(FileName); + ze.TrimVolumeFromFullyQualifiedPaths= TrimVolumeFromFullyQualifiedPaths; + if (WantVerbose) Console.WriteLine("adding {0}...", FileName); + ze.Write(WriteStream); + _entries.Add(ze); + } + + public void AddDirectory(string DirectoryName) + { + AddDirectory(DirectoryName, false); + } + + public void AddDirectory(string DirectoryName, bool WantVerbose) + { + String[] filenames = System.IO.Directory.GetFiles(DirectoryName); + foreach (String filename in filenames) + { + if (WantVerbose) Console.WriteLine("adding {0}...", filename); + AddFile(filename); + } + } + + + public void Save() + { + WriteCentralDirectoryStructure(); + WriteStream.Close(); + _writestream = null; + } + + + private void WriteCentralDirectoryStructure() + { + // the central directory structure + long Start = WriteStream.Length; + foreach (ZipEntry e in _entries) + { + e.WriteCentralDirectoryEntry(WriteStream); + } + long Finish = WriteStream.Length; + + // now, the footer + WriteCentralDirectoryFooter(Start, Finish); + } + + + private void WriteCentralDirectoryFooter(long StartOfCentralDirectory, long EndOfCentralDirectory) + { + byte[] bytes = new byte[1024]; + int i = 0; + // signature + UInt32 EndOfCentralDirectorySignature = 0x06054b50; + bytes[i++] = (byte)(EndOfCentralDirectorySignature & 0x000000FF); + bytes[i++] = (byte)((EndOfCentralDirectorySignature & 0x0000FF00) >> 8); + bytes[i++] = (byte)((EndOfCentralDirectorySignature & 0x00FF0000) >> 16); + bytes[i++] = (byte)((EndOfCentralDirectorySignature & 0xFF000000) >> 24); + + // number of this disk + bytes[i++] = 0; + bytes[i++] = 0; + + // number of the disk with the start of the central directory + bytes[i++] = 0; + bytes[i++] = 0; + + // total number of entries in the central dir on this disk + bytes[i++] = (byte)(_entries.Count & 0x00FF); + bytes[i++] = (byte)((_entries.Count & 0xFF00) >> 8); + + // total number of entries in the central directory + bytes[i++] = (byte)(_entries.Count & 0x00FF); + bytes[i++] = (byte)((_entries.Count & 0xFF00) >> 8); + + // size of the central directory + Int32 SizeOfCentralDirectory = (Int32)(EndOfCentralDirectory - StartOfCentralDirectory); + bytes[i++] = (byte)(SizeOfCentralDirectory & 0x000000FF); + bytes[i++] = (byte)((SizeOfCentralDirectory & 0x0000FF00) >> 8); + bytes[i++] = (byte)((SizeOfCentralDirectory & 0x00FF0000) >> 16); + bytes[i++] = (byte)((SizeOfCentralDirectory & 0xFF000000) >> 24); + + // offset of the start of the central directory + Int32 StartOffset = (Int32)StartOfCentralDirectory; // cast down from Long + bytes[i++] = (byte)(StartOffset & 0x000000FF); + bytes[i++] = (byte)((StartOffset & 0x0000FF00) >> 8); + bytes[i++] = (byte)((StartOffset & 0x00FF0000) >> 16); + bytes[i++] = (byte)((StartOffset & 0xFF000000) >> 24); + + // zip comment length + bytes[i++] = 0; + bytes[i++] = 0; + + WriteStream.Write(bytes, 0, i); + } + + #endregion + + #region For Reading Zip Files + + internal static ZipFile Read(string zipfilename) + { + return Read(zipfilename, false); + } + + internal static ZipFile Read(string zipfilename, bool TurnOnDebug) + { + + ZipFile zf = new ZipFile(); + zf._Debug = TurnOnDebug; + zf._name = zipfilename; + zf._entries = new System.Collections.Generic.List(); + ZipEntry e; + while ((e = ZipEntry.Read(zf.ReadStream, zf._Debug)) != null) + { + if (zf._Debug) System.Console.WriteLine(" ZipFile::Read(): ZipEntry: {0}", e.FileName); + zf._entries.Add(e); + } + + // read the zipfile's central directory structure here. + zf._direntries = new System.Collections.Generic.List(); + + ZipDirEntry de; + while ((de = ZipDirEntry.Read(zf.ReadStream, zf._Debug)) != null) + { + if (zf._Debug) System.Console.WriteLine(" ZipFile::Read(): ZipDirEntry: {0}", de.FileName); + zf._direntries.Add(de); + } + + return zf; + } + + public System.Collections.Generic.IEnumerator GetEnumerator() + { + foreach (ZipEntry e in _entries) + yield return e; + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + + public void ExtractAll(string path) + { + ExtractAll(path, false); + } + + + public void ExtractAll(string path, bool WantVerbose) + { + bool header = WantVerbose; + foreach (ZipEntry e in _entries) + { + if (header) + { + System.Console.WriteLine("\n{1,-22} {2,-6} {3,4} {4,-8} {0}", + "Name", "Modified", "Size", "Ratio", "Packed"); + System.Console.WriteLine(new System.String('-', 72)); + header = false; + } + if (WantVerbose) + System.Console.WriteLine("{1,-22} {2,-6} {3,4:F0}% {4,-8} {0}", + e.FileName, + e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"), + e.UncompressedSize, + e.CompressionRatio, + e.CompressedSize); + e.Extract(path); + } + } + + + public void Extract(string filename) + { + this[filename].Extract(); + } + + + public void Extract(string filename, System.IO.Stream s) + { + this[filename].Extract(s); + } + + + public ZipEntry this[String filename] + { + get + { + foreach (ZipEntry e in _entries) + { + if (e.FileName == filename) return e; + } + return null; + } + } + + #endregion + + // the destructor + ~ZipFile() + { + // call Dispose with false. Since we're in the + // destructor call, the managed resources will be + // disposed of anyways. + Dispose(false); + } + + public void Dispose() + { + // dispose of the managed and unmanaged resources + Dispose(true); + + // tell the GC that the Finalize process no longer needs + // to be run for this object. + GC.SuppressFinalize(this); + } + + + protected virtual void Dispose(bool disposeManagedResources) + { + if (!this._disposed) + { + if (disposeManagedResources) + { + // dispose managed resources + if (_readstream != null) + { + _readstream.Dispose(); + _readstream = null; + } + if (_writestream != null) + { + _writestream.Dispose(); + _writestream = null; + } + } + this._disposed = true; + } + } + + + private System.IO.Stream _readstream; + private System.IO.FileStream _writestream; + private bool _Debug = false; + private bool _disposed = false; + private System.Collections.Generic.List _entries = null; + private System.Collections.Generic.List _direntries = null; + } + +} + +#region More Info +// Example usage: +// 1. Extracting all files from a Zip file: +// +// try +// { +// using(ZipFile zip= ZipFile.Read(ZipFile)) +// { +// zip.ExtractAll(TargetDirectory, true); +// } +// } +// catch (System.Exception ex1) +// { +// System.Console.Error.WriteLine("exception: " + ex1); +// } +// +// 2. Extracting files from a zip individually: +// +// try +// { +// using(ZipFile zip= ZipFile.Read(ZipFile)) +// { +// foreach (ZipEntry e in zip) +// { +// e.Extract(TargetDirectory); +// } +// } +// } +// catch (System.Exception ex1) +// { +// System.Console.Error.WriteLine("exception: " + ex1); +// } +// +// 3. Creating a zip archive: +// +// try +// { +// using(ZipFile zip= new ZipFile(NewZipFile)) +// { +// +// String[] filenames= System.IO.Directory.GetFiles(Directory); +// foreach (String filename in filenames) +// { +// zip.Add(filename); +// } +// +// zip.Save(); +// } +// +// } +// catch (System.Exception ex1) +// { +// System.Console.Error.WriteLine("exception: " + ex1); +// } +// +// +// ================================================================== +// +// +// +// Information on the ZIP format: +// +// From +// http://www.pkware.com/business_and_developers/developer/popups/appnote.txt +// +// Overall .ZIP file format: +// +// [local file header 1] +// [file data 1] +// [data descriptor 1] ** sometimes +// . +// . +// . +// [local file header n] +// [file data n] +// [data descriptor n] ** sometimes +// [archive decryption header] +// [archive extra data record] +// [central directory] +// [zip64 end of central directory record] +// [zip64 end of central directory locator] +// [end of central directory record] +// +// Local File Header format: +// local file header signature 4 bytes (0x04034b50) +// version needed to extract 2 bytes +// general purpose bit flag 2 bytes +// compression method 2 bytes +// last mod file time 2 bytes +// last mod file date 2 bytes +// crc-32 4 bytes +// compressed size 4 bytes +// uncompressed size 4 bytes +// file name length 2 bytes +// extra field length 2 bytes +// file name varies +// extra field varies +// +// +// Data descriptor: (used only when bit 3 of the general purpose bitfield is set) +// local file header signature 4 bytes (0x08074b50) +// crc-32 4 bytes +// compressed size 4 bytes +// uncompressed size 4 bytes +// +// +// Central directory structure: +// +// [file header 1] +// . +// . +// . +// [file header n] +// [digital signature] +// +// +// File header: (This is ZipDirEntry in the code above) +// central file header signature 4 bytes (0x02014b50) +// version made by 2 bytes +// version needed to extract 2 bytes +// general purpose bit flag 2 bytes +// compression method 2 bytes +// last mod file time 2 bytes +// last mod file date 2 bytes +// crc-32 4 bytes +// compressed size 4 bytes +// uncompressed size 4 bytes +// file name length 2 bytes +// extra field length 2 bytes +// file comment length 2 bytes +// disk number start 2 bytes +// internal file attributes 2 bytes +// external file attributes 4 bytes +// relative offset of local header 4 bytes +// file name (variable size) +// extra field (variable size) +// file comment (variable size) +// +// End of central directory record: +// +// end of central dir signature 4 bytes (0x06054b50) +// number of this disk 2 bytes +// number of the disk with the +// start of the central directory 2 bytes +// total number of entries in the +// central directory on this disk 2 bytes +// total number of entries in +// the central directory 2 bytes +// size of the central directory 4 bytes +// offset of start of central +// directory with respect to +// the starting disk number 4 bytes +// .ZIP file comment length 2 bytes +// .ZIP file comment (variable size) +// +// date and time are packed values, as MSDOS did them +// time: bits 0-4 : second +// 5-10: minute +// 11-15: hour +// date bits 0-4 : day +// 5-8: month +// 9-15 year (since 1980) +// +// see http://www.vsft.com/hal/dostime.htm + +#endregion \ No newline at end of file diff --git a/Neoforce/GroupBox.cs b/Neoforce/GroupBox.cs new file mode 100644 index 0000000..04228ca --- /dev/null +++ b/Neoforce/GroupBox.cs @@ -0,0 +1,139 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: GroupBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public enum GroupBoxType + { + Normal, + Flat + } + + public class GroupBox : Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private GroupBoxType type = GroupBoxType.Normal; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual GroupBoxType Type + { + get { return type; } + set { type = value; Invalidate(); } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public GroupBox(Manager manager) + : base(manager) + { + CheckLayer(Skin, "Control"); + CheckLayer(Skin, "Flat"); + + CanFocus = false; + Passive = true; + Width = 64; + Height = 64; + BackColor = Color.Transparent; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + + private void AdjustClientMargins() + { + SkinLayer layer = this.type == GroupBoxType.Normal ? this.Skin.Layers["Control"] : this.Skin.Layers["Flat"]; + SpriteFont font = (layer.Text != null && layer.Text.Font != null) ? layer.Text.Font.Resource : null; + Vector2 size = font.MeasureString(this.Text); + var cm = this.ClientMargins; + cm.Top = string.IsNullOrWhiteSpace(this.Text) ? this.ClientTop : (int)size.Y; + this.ClientMargins = new Margins(cm.Left, cm.Top, cm.Right, cm.Bottom); + } + + protected override void OnTextChanged(EventArgs e) + { + base.OnTextChanged(e); + AdjustClientMargins(); + } + + protected internal override void OnSkinChanged(EventArgs e) + { + base.OnSkinChanged(e); + AdjustClientMargins(); + } + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + SkinLayer layer = type == GroupBoxType.Normal ? Skin.Layers["Control"] : Skin.Layers["Flat"]; + SpriteFont font = (layer.Text != null && layer.Text.Font != null) ? layer.Text.Font.Resource : null; + Color col = (layer.Text != null) ? layer.Text.Colors.Enabled : Color.White; + Point offset = new Point(layer.Text.OffsetX, layer.Text.OffsetY); + Vector2 size = font.MeasureString(Text); + size.Y = font.LineSpacing; + Rectangle r = new Rectangle(rect.Left, rect.Top + (int)(size.Y / 2), rect.Width, rect.Height - (int)(size.Y / 2)); + + renderer.DrawLayer(this, layer, r); + + if (font != null && Text != null && Text != "") + { + Rectangle bg = new Rectangle(r.Left + offset.X, (r.Top - (int)(size.Y / 2)) + offset.Y, (int)size.X + layer.ContentMargins.Horizontal, (int)size.Y); + renderer.DrawLayer(Manager.Skin.Controls["Control"].Layers[0], bg, new Color(64, 64, 64), 0); + renderer.DrawString(this, layer, Text, new Rectangle(r.Left, r.Top - (int)(size.Y / 2), (int)(size.X), (int)size.Y), true, 0, 0, false); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/GroupPanel.cs b/Neoforce/GroupPanel.cs new file mode 100644 index 0000000..ac25828 --- /dev/null +++ b/Neoforce/GroupPanel.cs @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: GroupPanel.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class GroupPanel: Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public GroupPanel(Manager manager): base(manager) + { + CanFocus = false; + Passive = true; + Width = 64; + Height = 64; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + SkinLayer layer = Skin.Layers["Control"]; + SpriteFont font = (layer.Text != null && layer.Text.Font != null) ? layer.Text.Font.Resource : null; + Color col = (layer.Text != null) ? layer.Text.Colors.Enabled : Color.White; + Point offset = new Point(layer.Text.OffsetX, layer.Text.OffsetY); + + renderer.DrawLayer(this, layer, rect); + + if (font != null && Text != null && Text != "") + { + renderer.DrawString(this, layer, Text, new Rectangle(rect.Left, rect.Top + layer.ContentMargins.Top, rect.Width, Skin.ClientMargins.Top - layer.ContentMargins.Horizontal), false, offset.X, offset.Y, false); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/ImageBox.cs b/Neoforce/ImageBox.cs new file mode 100644 index 0000000..146a15f --- /dev/null +++ b/Neoforce/ImageBox.cs @@ -0,0 +1,197 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ImageBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ImageBox: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Texture2D image = null; + private SizeMode sizeMode = SizeMode.Normal; + private Rectangle sourceRect = Rectangle.Empty; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public Texture2D Image + { + get { return image; } + set + { + image = value; + sourceRect = new Rectangle(0, 0, image.Width, image.Height); + Invalidate(); + if (!Suspended) OnImageChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public Rectangle SourceRect + { + get { return sourceRect; } + set + { + if (value != null && image != null) + { + int l = value.Left; + int t = value.Top; + int w = value.Width; + int h = value.Height; + + if (l < 0) l = 0; + if (t < 0) t = 0; + if (w > image.Width) w = image.Width; + if (h > image.Height) h = image.Height; + if (l + w > image.Width) w = (image.Width - l); + if (t + h > image.Height) h = (image.Height - t); + + sourceRect = new Rectangle(l, t, w, h); + } + else if (image != null) + { + sourceRect = new Rectangle(0, 0, image.Width, image.Height); + } + else + { + sourceRect = Rectangle.Empty; + } + Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SizeMode SizeMode + { + get { return sizeMode; } + set + { + if (value == SizeMode.Auto && image != null) + { + Width = image.Width; + Height = image.Height; + } + sizeMode = value; + Invalidate(); + if (!Suspended) OnSizeModeChanged(new EventArgs()); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler ImageChanged; + public event EventHandler SizeModeChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ImageBox(Manager manager): base(manager) + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + CanFocus = false; + Color = Color.White; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + if (image != null) + { + if (sizeMode == SizeMode.Normal) + { + renderer.Draw(image, rect.X, rect.Y, sourceRect, Color); + } + else if (sizeMode == SizeMode.Auto) + { + renderer.Draw(image, rect.X, rect.Y, sourceRect, Color); + } + else if (sizeMode == SizeMode.Stretched) + { + renderer.Draw(image, rect, sourceRect, Color); + } + else if (sizeMode == SizeMode.Centered) + { + int x = (rect.Width / 2) - (image.Width / 2); + int y = (rect.Height / 2) - (image.Height / 2); + + renderer.Draw(image, x, y, sourceRect, Color); + } + else if (sizeMode == SizeMode.Tiled) + { + renderer.DrawTileTexture(image, rect, Color); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnImageChanged(EventArgs e) + { + if (ImageChanged != null) ImageChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnSizeModeChanged(EventArgs e) + { + if (SizeModeChanged != null) SizeModeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/InputSystem.cs b/Neoforce/InputSystem.cs new file mode 100644 index 0000000..43e4249 --- /dev/null +++ b/Neoforce/InputSystem.cs @@ -0,0 +1,735 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: InputSystem.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.GamerServices; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + [Flags] + public enum InputMethods + { + None = 0x00, + Keyboard = 0x01, + Mouse = 0x02, + GamePad = 0x04, + All = Keyboard | Mouse | 0x04 + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum MouseButton + { + None = 0, + Left, + Right, + Middle, + XButton1, + XButton2 + } + //////////////////////////////////////////////////////////////////////////// + + public enum MouseScrollDirection + { + None = 0, + Down = 1, + Up = 2 + } + + //////////////////////////////////////////////////////////////////////////// + public enum GamePadButton + { + None = 0, + Start = 6, + Back, + Up, + Down, + Left, + Right, + A, + B, + X, + Y, + BigButton, + LeftShoulder, + RightShoulder, + LeftTrigger, + RightTrigger, + LeftStick, + RightStick, + LeftStickLeft, + LeftStickRight, + LeftStickUp, + LeftStickDown, + RightStickLeft, + RightStickRight, + RightStickUp, + RightStickDown + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum ActivePlayer + { + None = -1, + One = 0, + Two = 1, + Three = 2, + Four = 3 + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Structs /////////// + + //////////////////////////////////////////////////////////////////////////// + public struct GamePadVectors + { + public Vector2 LeftStick; + public Vector2 RightStick; + public float LeftTrigger; + public float RightTrigger; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public struct InputOffset + { + public int X; + public int Y; + public float RatioX; + public float RatioY; + + public InputOffset(int x, int y, float rx, float ry) + { + X = x; + Y = y; + RatioX = rx; + RatioY = ry; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + public class InputSystem: Disposable + { + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + private class InputKey + { + public Keys Key = Keys.None; + public bool Pressed = false; + public double Countdown = RepeatDelay; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private class InputMouseButton + { + public MouseButton Button = MouseButton.None; + public bool Pressed = false; + public double Countdown = RepeatDelay; + + public InputMouseButton() + { + } + + public InputMouseButton(MouseButton button) + { + Button = button; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private class InputMouse + { + public MouseState State = new MouseState(); + public Point Position = new Point(0, 0); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private class InputGamePadButton + { + public GamePadButton Button = GamePadButton.None; + public bool Pressed = false; + public double Countdown = RepeatDelay; + + public InputGamePadButton() + { + } + + public InputGamePadButton(GamePadButton button) + { + Button = button; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Consts //////////// + + //////////////////////////////////////////////////////////////////////////// + private const int RepeatDelay = 500; + private const int RepeatRate = 50; + private float ClickThreshold = 0.5f; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private List keys = new List(); + private List mouseButtons = new List(); + private List gamePadButtons = new List(); + private MouseState mouseState = new MouseState(); + private GamePadState gamePadState = new GamePadState(); + private Manager manager = null; + private InputOffset inputOffset = new InputOffset(0, 0, 1.0f, 1.0f); + private InputMethods inputMethods = InputMethods.All; + private ActivePlayer activePlayer = ActivePlayer.None; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Sets or gets input offset and ratio when rescaling controls in render target. + /// + public virtual InputOffset InputOffset + { + get { return inputOffset; } + set { inputOffset = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Sets or gets input methods allowed for navigation. + /// + public virtual InputMethods InputMethods + { + get { return inputMethods; } + set { inputMethods = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ActivePlayer ActivePlayer + { + get { return activePlayer; } + set { activePlayer = value; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event KeyEventHandler KeyDown; + public event KeyEventHandler KeyPress; + public event KeyEventHandler KeyUp; + + public event MouseEventHandler MouseDown; + public event MouseEventHandler MousePress; + public event MouseEventHandler MouseUp; + public event MouseEventHandler MouseMove; + /// + /// Occurs when the mouse is scrolled. + /// + public event MouseEventHandler MouseScroll; + + public event GamePadEventHandler GamePadUp; + public event GamePadEventHandler GamePadDown; + public event GamePadEventHandler GamePadPress; + public event GamePadEventHandler GamePadMove; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public InputSystem(Manager manager, InputOffset offset) + { + this.inputOffset = offset; + this.manager = manager; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public InputSystem(Manager manager): this(manager, new InputOffset(0, 0, 1.0f, 1.0f)) + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Initialize() + { + keys.Clear(); + mouseButtons.Clear(); + gamePadButtons.Clear(); + + #if (!XBOX && !XBOX_FAKE) + foreach (string str in Enum.GetNames(typeof(Keys))) + { + InputKey key = new InputKey(); + key.Key = (Keys)Enum.Parse(typeof(Keys), str); + keys.Add(key); + } + + foreach (string str in Enum.GetNames(typeof(MouseButton))) + { + InputMouseButton btn = new InputMouseButton(); + btn.Button = (MouseButton)Enum.Parse(typeof(MouseButton), str); + mouseButtons.Add(btn); + } + + foreach (string str in Enum.GetNames(typeof(GamePadButton))) + { + InputGamePadButton btn = new InputGamePadButton(); + btn.Button = (GamePadButton)Enum.Parse(typeof(GamePadButton), str); + gamePadButtons.Add(btn); + } + #else + gamePadButtons.Add(new InputGamePadButton(GamePadButton.None)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.Start)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.Back)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.Up)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.Down)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.Left)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.Right)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.A)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.B)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.X)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.Y)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.BigButton)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.LeftShoulder)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.RightShoulder)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.LeftTrigger)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.RightTrigger)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.LeftStick)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.RightStick)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.LeftStickLeft)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.LeftStickRight)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.LeftStickUp)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.LeftStickDown)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.RightStickLeft)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.RightStickRight)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.RightStickUp)); + gamePadButtons.Add(new InputGamePadButton(GamePadButton.RightStickDown)); + #endif + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SendMouseState(MouseState state, GameTime gameTime) + { + UpdateMouse(state, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SendKeyboardState(KeyboardState state, GameTime gameTime) + { + UpdateKeys(state, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SendGamePadState(PlayerIndex playerIndex, GamePadState state, GameTime gameTime) + { + UpdateGamePad(playerIndex, state, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Update(GameTime gameTime) + { + if (manager.UseGuide && Guide.IsVisible) return; + + #if (!XBOX && !XBOX_FAKE) + MouseState ms = Mouse.GetState(); + KeyboardState ks = Keyboard.GetState(); + #endif + + + + { + #if (!XBOX && !XBOX_FAKE) + if ((inputMethods & InputMethods.Mouse) == InputMethods.Mouse) UpdateMouse(ms, gameTime); + if ((inputMethods & InputMethods.Keyboard) == InputMethods.Keyboard) UpdateKeys(ks, gameTime); + #endif + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private ButtonState GetVectorState(GamePadButton button, GamePadState state) + { + ButtonState ret = ButtonState.Released; + bool down = false; + float t = ClickThreshold; + + switch (button) + { + case GamePadButton.LeftStickLeft: down = state.ThumbSticks.Left.X < -t; break; + case GamePadButton.LeftStickRight: down = state.ThumbSticks.Left.X > t; break; + case GamePadButton.LeftStickUp: down = state.ThumbSticks.Left.Y > t; break; + case GamePadButton.LeftStickDown: down = state.ThumbSticks.Left.Y < -t; break; + + case GamePadButton.RightStickLeft: down = state.ThumbSticks.Right.X < -t; break; + case GamePadButton.RightStickRight: down = state.ThumbSticks.Right.X > t; break; + case GamePadButton.RightStickUp: down = state.ThumbSticks.Right.Y > t; break; + case GamePadButton.RightStickDown: down = state.ThumbSticks.Right.Y < -t; break; + + case GamePadButton.LeftTrigger: down = state.Triggers.Left > t; break; + case GamePadButton.RightTrigger: down = state.Triggers.Right > t; break; + } + + ret = down ? ButtonState.Pressed : ButtonState.Released; + + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void UpdateGamePad(PlayerIndex playerIndex, GamePadState state, GameTime gameTime) + { + GamePadEventArgs e = new GamePadEventArgs(playerIndex); + + if (state.ThumbSticks.Left != gamePadState.ThumbSticks.Left || + state.ThumbSticks.Right != gamePadState.ThumbSticks.Right || + state.Triggers.Left != gamePadState.Triggers.Left || + state.Triggers.Right != gamePadState.Triggers.Right) + { + BuildGamePadEvent(state, GamePadButton.None, ref e); + if (GamePadMove != null) GamePadMove.Invoke(this, e); + } + + foreach (InputGamePadButton btn in gamePadButtons) + { + ButtonState bs = ButtonState.Released; + + if (btn.Button == GamePadButton.None) continue; + else if (btn.Button == GamePadButton.A) bs = state.Buttons.A; + else if (btn.Button == GamePadButton.B) bs = state.Buttons.B; + else if (btn.Button == GamePadButton.Back) bs = state.Buttons.Back; + else if (btn.Button == GamePadButton.Down) bs = state.DPad.Down; + else if (btn.Button == GamePadButton.Left) bs = state.DPad.Left; + else if (btn.Button == GamePadButton.Right) bs = state.DPad.Right; + else if (btn.Button == GamePadButton.Start) bs = state.Buttons.Start; + else if (btn.Button == GamePadButton.Up) bs = state.DPad.Up; + else if (btn.Button == GamePadButton.X) bs = state.Buttons.X; + else if (btn.Button == GamePadButton.Y) bs = state.Buttons.Y; + else if (btn.Button == GamePadButton.BigButton) bs = state.Buttons.BigButton; + else if (btn.Button == GamePadButton.LeftShoulder) bs = state.Buttons.LeftShoulder; + else if (btn.Button == GamePadButton.RightShoulder) bs = state.Buttons.RightShoulder; + else if (btn.Button == GamePadButton.LeftStick) bs = state.Buttons.LeftStick; + else if (btn.Button == GamePadButton.RightStick) bs = state.Buttons.RightStick; + else bs = GetVectorState(btn.Button, state); + + bool pressed = (bs == ButtonState.Pressed); + if (pressed) + { + double ms = gameTime.ElapsedGameTime.TotalMilliseconds; + if (pressed) btn.Countdown -= ms; + } + + if ((pressed) && (!btn.Pressed)) + { + btn.Pressed = true; + BuildGamePadEvent(state, btn.Button, ref e); + + if (GamePadDown != null) GamePadDown.Invoke(this, e); + if (GamePadPress != null) GamePadPress.Invoke(this, e); + } + else if ((!pressed) && (btn.Pressed)) + { + btn.Pressed = false; + btn.Countdown = RepeatDelay; + BuildGamePadEvent(state, btn.Button, ref e); + + if (GamePadUp != null) GamePadUp.Invoke(this, e); + } + else if (btn.Pressed && btn.Countdown < 0) + { + e.Button = btn.Button; + btn.Countdown = RepeatRate; + BuildGamePadEvent(state, btn.Button, ref e); + + if (GamePadPress != null) GamePadPress.Invoke(this, e); + } + } + gamePadState = state; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void BuildGamePadEvent(GamePadState state, GamePadButton button, ref GamePadEventArgs e) + { + e.State = state; + e.Button = button; + e.Vectors.LeftStick = new Vector2(state.ThumbSticks.Left.X, state.ThumbSticks.Left.Y); + e.Vectors.RightStick = new Vector2(state.ThumbSticks.Right.X, state.ThumbSticks.Right.Y); + e.Vectors.LeftTrigger = state.Triggers.Left; + e.Vectors.RightTrigger = state.Triggers.Right; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void UpdateKeys(KeyboardState state, GameTime gameTime) + { + #if (!XBOX && !XBOX_FAKE) + + KeyEventArgs e = new KeyEventArgs(); + + e.Caps = (((ushort)NativeMethods.GetKeyState(0x14)) & 0xffff) != 0; + + foreach (Keys key in state.GetPressedKeys()) + { + if (key == Keys.LeftAlt || key == Keys.RightAlt) e.Alt = true; + else if (key == Keys.LeftShift || key == Keys.RightShift) e.Shift = true; + else if (key == Keys.LeftControl || key == Keys.RightControl) e.Control = true; + } + + foreach (InputKey key in keys) + { + if (key.Key == Keys.LeftAlt || key.Key == Keys.RightAlt || + key.Key == Keys.LeftShift || key.Key == Keys.RightShift || + key.Key == Keys.LeftControl || key.Key == Keys.RightControl) + { + continue; + } + + bool pressed = state.IsKeyDown(key.Key); + + double ms = gameTime.ElapsedGameTime.TotalMilliseconds; + if (pressed) key.Countdown -= ms; + + if ((pressed) && (!key.Pressed)) + { + key.Pressed = true; + e.Key = key.Key; + + if (KeyDown != null) KeyDown.Invoke(this, e); + if (KeyPress != null) KeyPress.Invoke(this, e); + } + else if ((!pressed) && (key.Pressed)) + { + key.Pressed = false; + key.Countdown = RepeatDelay; + e.Key = key.Key; + + if (KeyUp != null) KeyUp.Invoke(this, e); + } + else if (key.Pressed && key.Countdown < 0) + { + key.Countdown = RepeatRate; + e.Key = key.Key; + + if (KeyPress != null) KeyPress.Invoke(this, e); + } + } + #endif + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private Point RecalcPosition(Point pos) + { + return new Point((int)((pos.X - InputOffset.X) / InputOffset.RatioX), (int)((pos.Y - InputOffset.Y) / InputOffset.RatioY)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void AdjustPosition(ref MouseEventArgs e) + { + Rectangle screen = manager.Game.Window.ClientBounds; + + if (e.Position.X < 0) e.Position.X = 0; + if (e.Position.Y < 0) e.Position.Y = 0; + if (e.Position.X >= screen.Width) e.Position.X = screen.Width - 1; + if (e.Position.Y >= screen.Height) e.Position.Y = screen.Height - 1; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void BuildMouseEvent(MouseState state, MouseButton button, ref MouseEventArgs e) + { + e.State = state; + e.Button = button; + + e.Position = new Point(state.X, state.Y); + AdjustPosition(ref e); + + e.Position = RecalcPosition(e.Position); + e.State = new MouseState(e.Position.X, e.Position.Y, e.State.ScrollWheelValue, e.State.LeftButton, e.State.MiddleButton, e.State.RightButton, e.State.XButton1, e.State.XButton2); + + Point pos = RecalcPosition(new Point(mouseState.X, mouseState.Y)); + e.Difference = new Point(e.Position.X - pos.X, e.Position.Y - pos.Y); + } + //////////////////////////////////////////////////////////////////////////// + + private void BuildMouseEvent(MouseState state, MouseButton button, MouseScrollDirection direction, ref MouseEventArgs e) + { + BuildMouseEvent(state, button, ref e); + + e.ScrollDirection = direction; + } + + //////////////////////////////////////////////////////////////////////////// + private void UpdateMouse(MouseState state, GameTime gameTime) + { + #if (!XBOX && !XBOX_FAKE) + + if ((state.X != mouseState.X) || (state.Y != mouseState.Y)) + { + MouseEventArgs e = new MouseEventArgs(); + + MouseButton btn = MouseButton.None; + if (state.LeftButton == ButtonState.Pressed) btn = MouseButton.Left; + else if (state.RightButton == ButtonState.Pressed) btn = MouseButton.Right; + else if (state.MiddleButton == ButtonState.Pressed) btn = MouseButton.Middle; + else if (state.XButton1 == ButtonState.Pressed) btn = MouseButton.XButton1; + else if (state.XButton2 == ButtonState.Pressed) btn = MouseButton.XButton2; + + BuildMouseEvent(state, btn, ref e); + if (MouseMove != null) + { + MouseMove.Invoke(this, e); + } + } + + // Mouse wheel position changed + if (state.ScrollWheelValue != mouseState.ScrollWheelValue) + { + MouseEventArgs e = new MouseEventArgs(); + MouseScrollDirection direction = state.ScrollWheelValue < mouseState.ScrollWheelValue ? MouseScrollDirection.Down : MouseScrollDirection.Up; + + BuildMouseEvent(state, MouseButton.None, direction, ref e); + + if (MouseScroll != null) + { + MouseScroll.Invoke(this, e); + } + } + + UpdateButtons(state, gameTime); + + mouseState = state; + + #endif + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void UpdateButtons(MouseState state, GameTime gameTime) + { + #if (!XBOX && !XBOX_FAKE) + + MouseEventArgs e = new MouseEventArgs(); + + foreach (InputMouseButton btn in mouseButtons) + { + ButtonState bs = ButtonState.Released; + + if (btn.Button == MouseButton.Left) bs = state.LeftButton; + else if (btn.Button == MouseButton.Right) bs = state.RightButton; + else if (btn.Button == MouseButton.Middle) bs = state.MiddleButton; + else if (btn.Button == MouseButton.XButton1) bs = state.XButton1; + else if (btn.Button == MouseButton.XButton2) bs = state.XButton2; + else continue; + + bool pressed = (bs == ButtonState.Pressed); + if (pressed) + { + double ms = gameTime.ElapsedGameTime.TotalMilliseconds; + if (pressed) btn.Countdown -= ms; + } + + if ((pressed) && (!btn.Pressed)) + { + btn.Pressed = true; + BuildMouseEvent(state, btn.Button, ref e); + + if (MouseDown != null) MouseDown.Invoke(this, e); + if (MousePress != null) MousePress.Invoke(this, e); + } + else if ((!pressed) && (btn.Pressed)) + { + btn.Pressed = false; + btn.Countdown = RepeatDelay; + BuildMouseEvent(state, btn.Button, ref e); + + if (MouseUp != null) MouseUp.Invoke(this, e); + } + else if (btn.Pressed && btn.Countdown < 0) + { + e.Button = btn.Button; + btn.Countdown = RepeatRate; + BuildMouseEvent(state, btn.Button, ref e); + + if (MousePress != null) MousePress.Invoke(this, e); + } + } + + #endif + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} \ No newline at end of file diff --git a/Neoforce/KeyboardLayout.cs b/Neoforce/KeyboardLayout.cs new file mode 100644 index 0000000..6a93ff0 --- /dev/null +++ b/Neoforce/KeyboardLayout.cs @@ -0,0 +1,461 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: KeyboardLayout.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +/***** + * Made Changes to the German Input, based on Kergos, input. + *****/ + +#region //// Using ///////////// + +////////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Input; +using System.Globalization; +using System.Collections.Generic; +using System; +using System.Text; +////////////////////////////////////////////////////////////////////////////// + +#endregion + + +namespace TomShane.Neoforce.Controls +{ + + public class KeyboardLayout + { + + #region //// Fields //////////// + + ////////////////////////////////////////////////////////////////////////// + private string name = "English"; + public List LayoutList = new List(); + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + ////////////////////////////////////////////////////////////////////////// + public virtual string Name + { + get { return name; } + set { name = value; } + } + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + ////////////////////////////////////////////////////////////////////////// + public KeyboardLayout() + { + LayoutList.Add(1033); + } + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual string GetKey(KeyEventArgs args) + { + string ret = ""; + + if (args.Caps && !args.Shift) ret = KeyToString(args).ToUpper(); + else if (!args.Caps && args.Shift) ret = KeyToString(args).ToUpper(); + else if (args.Caps && args.Shift) ret = KeyToString(args).ToLower(); + else if (!args.Caps && !args.Shift) ret = KeyToString(args).ToLower(); + + + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual string KeyToString(KeyEventArgs args) + { + switch (args.Key) + { + case Keys.A: + return "a"; + case Keys.B: + return "b"; + case Keys.C: + return "c"; + case Keys.D: + return "d"; + case Keys.E: + return "e"; + case Keys.F: + return "f"; + case Keys.G: + return "g"; + case Keys.H: + return "h"; + case Keys.I: + return "i"; + case Keys.J: + return "j"; + case Keys.K: + return "k"; + case Keys.L: + return "l"; + case Keys.M: + return "m"; + case Keys.N: + return "n"; + case Keys.O: + return "o"; + case Keys.P: + return "p"; + case Keys.Q: + return "q"; + case Keys.R: + return "r"; + case Keys.S: + return "s"; + case Keys.T: + return "t"; + case Keys.U: + return "u"; + case Keys.V: + return "v"; + case Keys.W: + return "w"; + case Keys.X: + return "x"; + case Keys.Y: + return "y"; + case Keys.Z: + return "z"; + + case Keys.D0: + return (args.Shift) ? ")" : "0"; + case Keys.D1: + return (args.Shift) ? "!" : "1"; + case Keys.D2: + return (args.Shift) ? "@" : "2"; + case Keys.D3: + return (args.Shift) ? "#" : "3"; + case Keys.D4: + return (args.Shift) ? "$" : "4"; + case Keys.D5: + return (args.Shift) ? "%" : "5"; + case Keys.D6: + return (args.Shift) ? "^" : "6"; + case Keys.D7: + return (args.Shift) ? "&" : "7"; + case Keys.D8: + return (args.Shift) ? "*" : "8"; + case Keys.D9: + return (args.Shift) ? "(" : "9"; + + case Keys.OemPlus: + return (args.Shift) ? "+" : "="; + case Keys.OemMinus: + return (args.Shift) ? "_" : "-"; + case Keys.OemOpenBrackets: + return (args.Shift) ? "{" : "["; + case Keys.OemCloseBrackets: + return (args.Shift) ? "}" : "]"; + case Keys.OemQuestion: + return (args.Shift) ? "?" : "/"; + case Keys.OemPeriod: + return (args.Shift) ? ">" : "."; + case Keys.OemComma: + return (args.Shift) ? "<" : ","; + case Keys.OemPipe: + return (args.Shift) ? "|" : "\\"; + case Keys.Space: + return " "; + case Keys.OemSemicolon: + return (args.Shift) ? ":" : ";"; + case Keys.OemQuotes: + return (args.Shift) ? "\"" : "'"; + case Keys.OemTilde: + return (args.Shift) ? "~" : "`"; + + case Keys.NumPad0: + return (args.Shift) ? "" : "0"; + case Keys.NumPad1: + return (args.Shift) ? "" : "1"; + case Keys.NumPad2: + return (args.Shift) ? "" : "2"; + case Keys.NumPad3: + return (args.Shift) ? "" : "3"; + case Keys.NumPad4: + return (args.Shift) ? "" : "4"; + case Keys.NumPad5: + return (args.Shift) ? "" : "5"; + case Keys.NumPad6: + return (args.Shift) ? "" : "6"; + case Keys.NumPad7: + return (args.Shift) ? "" : "7"; + case Keys.NumPad8: + return (args.Shift) ? "" : "8"; + case Keys.NumPad9: + return (args.Shift) ? "" : "9"; + case Keys.Decimal: + return (args.Shift) ? "" : "."; + + case Keys.Divide: + return (args.Shift) ? "/" : "/"; + case Keys.Multiply: + return (args.Shift) ? "*" : "*"; + case Keys.Subtract: + return (args.Shift) ? "-" : "-"; + case Keys.Add: + return (args.Shift) ? "+" : "+"; + + default: + return ""; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + + public class CzechKeyboardLayout: KeyboardLayout + { + + #region //// Constructors ////// + + ////////////////////////////////////////////////////////////////////////// + public CzechKeyboardLayout() + { + Name = "Czech"; + LayoutList.Clear(); + LayoutList.Add(1029); + } + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected override string KeyToString(KeyEventArgs args) + { + switch (args.Key) + { + case Keys.D0: + return (args.Shift) ? "0" : ""; + case Keys.D1: + return (args.Shift) ? "1" : "+"; + case Keys.D2: + return (args.Shift) ? "2" : ""; + case Keys.D3: + return (args.Shift) ? "3" : ""; + case Keys.D4: + return (args.Shift) ? "4" : ""; + case Keys.D5: + return (args.Shift) ? "5" : ""; + case Keys.D6: + return (args.Shift) ? "6" : ""; + case Keys.D7: + return (args.Shift) ? "7" : ""; + case Keys.D8: + return (args.Shift) ? "8" : ""; + case Keys.D9: + return (args.Shift) ? "9" : ""; + + case Keys.OemPlus: + return (args.Shift) ? "" : ""; + case Keys.OemMinus: + return (args.Shift) ? "%" : "="; + case Keys.OemOpenBrackets: + return (args.Shift) ? "/" : ""; + case Keys.OemCloseBrackets: + return (args.Shift) ? "(" : ")"; + case Keys.OemQuestion: + return (args.Shift) ? "_" : "-"; + case Keys.OemPeriod: + return (args.Shift) ? ":" : "."; + case Keys.OemComma: + return (args.Shift) ? "?" : ","; + case Keys.OemPipe: + return (args.Shift) ? "'" : ""; + case Keys.Space: + return " "; + case Keys.OemSemicolon: + return (args.Shift) ? "\"" : ""; + case Keys.OemQuotes: + return (args.Shift) ? "!" : ""; + case Keys.OemTilde: + return (args.Shift) ? "" : ";"; + + case Keys.Decimal: + return (args.Shift) ? "" : ","; + + default: + return base.KeyToString(args); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + + public class GermanKeyboardLayout : KeyboardLayout + { + + #region //// Constructors ////// + + ////////////////////////////////////////////////////////////////////////// + public GermanKeyboardLayout() + { + Name = "German"; + LayoutList.Clear(); + LayoutList.Add(1031); + } + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected override string KeyToString(KeyEventArgs args) + { + switch (args.Key) + { + case Keys.D0: + return (args.Shift) ? "=" : "0"; + case Keys.D1: + return (args.Shift) ? "!" : "1"; + case Keys.D2: + return (args.Shift) ? "\"": "2"; + case Keys.D3: + return (args.Shift) ? "" : "3"; + case Keys.D4: + return (args.Shift) ? "$" : "4"; + case Keys.D5: + return (args.Shift) ? "%" : "5"; + case Keys.D6: + return (args.Shift) ? "&" : "6"; + case Keys.D7: + return (args.Shift) ? "/" : "7"; + case Keys.D8: + return (args.Shift) ? "(" : "8"; + case Keys.D9: + return (args.Shift) ? ")" : "9"; + case Keys.OemBackslash: + return (args.Shift) ? ">" : "<"; + case Keys.OemPlus: + return (args.Shift) ? "*" : "+"; + case Keys.OemMinus: + return (args.Shift) ? "_" : "-"; + case Keys.OemOpenBrackets: + return (args.Shift) ? "?" : ""; + case Keys.OemCloseBrackets: + return (args.Shift) ? "`" : ""; + case Keys.OemQuestion: + return (args.Shift) ? "'" : "#"; + case Keys.OemPeriod: + return (args.Shift) ? ":" : "."; + case Keys.OemComma: + return (args.Shift) ? ";" : ","; + case Keys.OemPipe: + return (args.Shift) ? "" : "^"; + case Keys.Space: + return " "; + case Keys.OemSemicolon: + return (args.Shift) ? "" : ""; + case Keys.OemQuotes: + return (args.Shift) ? "" : ""; + case Keys.OemTilde: + return (args.Shift) ? "" : ""; + + case Keys.Decimal: + return (args.Shift) ? "" : "."; + + default: + return base.KeyToString(args); + } + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + + } + + public class PolishKeyboardLayout: KeyboardLayout + { + + #region //// Constructors ////// + ////////////////////////////////////////////////////////////////////////// + public PolishKeyboardLayout() + { + Name = "Polish"; + LayoutList.Clear(); + LayoutList.Add(1045); + } + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected override string KeyToString(KeyEventArgs args) + { + if (args.Alt) + { + switch (args.Key) + { + case Keys.A: + return (args.Shift) ? "" : ""; + case Keys.C: + return (args.Shift) ? "" : ""; + case Keys.E: + return (args.Shift) ? "" : ""; + case Keys.L: + return (args.Shift) ? "" : ""; + case Keys.N: + return (args.Shift) ? "" : ""; + case Keys.O: + return (args.Shift) ? "" : ""; + case Keys.S: + return (args.Shift) ? "" : ""; + case Keys.X: + return (args.Shift) ? "" : ""; + case Keys.Z: + return (args.Shift) ? "" : ""; + } + } + return base.KeyToString(args); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} + diff --git a/Neoforce/Label.cs b/Neoforce/Label.cs new file mode 100644 index 0000000..a479796 --- /dev/null +++ b/Neoforce/Label.cs @@ -0,0 +1,104 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Label.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class Label: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Alignment alignment = Alignment.MiddleLeft; + private bool ellipsis = true; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Alignment Alignment + { + get { return alignment; } + set { alignment = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool Ellipsis + { + get { return ellipsis; } + set { ellipsis = value; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public Label(Manager manager): base(manager) + { + CanFocus = false; + Passive = true; + Width = 64; + Height = 16; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + //base.DrawControl(renderer, rect, gameTime); + + SkinLayer s = new SkinLayer(Skin.Layers[0]); + s.Text.Alignment = alignment; + renderer.DrawString(this, s, Text, rect, true, 0, 0, ellipsis); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Layout.cs b/Neoforce/Layout.cs new file mode 100644 index 0000000..fd9f62e --- /dev/null +++ b/Neoforce/Layout.cs @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Layout.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using Microsoft.Xna.Framework; +using System.Xml; +using System.Reflection; +using System.IO; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + + public static class Layout + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public static Container Load(Manager manager, string asset) + { + Container win = null; + LayoutXmlDocument doc = new LayoutXmlDocument(); + ArchiveManager content = new ArchiveManager(manager.Game.Services); + + try + { + content.RootDirectory = manager.LayoutDirectory; + + #if (!XBOX && !XBOX_FAKE) + + string file = content.RootDirectory + asset; + + if (File.Exists(file)) + { + doc.Load(file); + } + else + + #endif + { + doc = content.Load(asset); + } + + + if (doc != null && doc["Layout"]["Controls"] != null && doc["Layout"]["Controls"].HasChildNodes) + { + XmlNode node = doc["Layout"]["Controls"].GetElementsByTagName("Control").Item(0); + string cls = node.Attributes["Class"].Value; + Type type = Type.GetType(cls); + + if (type == null) + { + cls = "TomShane.Neoforce.Controls." + cls; + type = Type.GetType(cls); + } + + win = (Container)LoadControl(manager, node, type, null); + } + + } + finally + { + content.Dispose(); + } + + return win; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private static Control LoadControl(Manager manager, XmlNode node, Type type, Control parent) + { + Control c = null; + + Object[] args = new Object[] {manager}; + + c = (Control)type.InvokeMember(null, BindingFlags.CreateInstance, null, null, args); + if (parent != null) c.Parent = parent; + c.Name = node.Attributes["Name"].Value; + + if (node != null && node["Properties"] != null && node["Properties"].HasChildNodes) + { + LoadProperties(node["Properties"].GetElementsByTagName("Property"), c); + } + + if (node != null && node["Controls"] != null && node["Controls"].HasChildNodes) + { + foreach (XmlElement e in node["Controls"].GetElementsByTagName("Control")) + { + string cls = e.Attributes["Class"].Value; + Type t = Type.GetType(cls); + + if (t == null) + { + cls = "TomShane.Neoforce.Controls." + cls; + t = Type.GetType(cls); + } + LoadControl(manager, e, t, c); + } + } + + return c; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private static void LoadProperties(XmlNodeList node, Control c) + { + foreach (XmlElement e in node) + { + string name = e.Attributes["Name"].Value; + string val = e.Attributes["Value"].Value; + + PropertyInfo i = c.GetType().GetProperty(name); + + if (i != null) + { + { + try + { + i.SetValue(c, Convert.ChangeType(val, i.PropertyType, null), null); + } + catch + { + } + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/ListBox.cs b/Neoforce/ListBox.cs new file mode 100644 index 0000000..5598674 --- /dev/null +++ b/Neoforce/ListBox.cs @@ -0,0 +1,485 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ListBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public class ListBox : Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private List items = new List(); + private ScrollBar sbVert = null; + private ClipBox pane = null; + private int itemIndex = -1; + private bool hotTrack = false; + private int itemsCount = 0; + private bool hideSelection = true; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual List Items + { + get { return items; } + internal set { items = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool HotTrack + { + get { return hotTrack; } + set + { + if (hotTrack != value) + { + hotTrack = value; + if (!Suspended) OnHotTrackChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int ItemIndex + { + get { return itemIndex; } + set + { + //if (itemIndex != value) + { + if (value >= 0 && value < items.Count) + { + itemIndex = value; + } + else + { + itemIndex = -1; + } + ScrollTo(itemIndex); + + if (!Suspended) OnItemIndexChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool HideSelection + { + get { return hideSelection; } + set + { + if (hideSelection != value) + { + hideSelection = value; + Invalidate(); + if (!Suspended) OnHideSelectionChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler HotTrackChanged; + public event EventHandler ItemIndexChanged; + public event EventHandler HideSelectionChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ListBox(Manager manager) + : base(manager) + { + Width = 64; + Height = 64; + MinimumHeight = 16; + + sbVert = new ScrollBar(Manager, Orientation.Vertical); + sbVert.Init(); + sbVert.Parent = this; + sbVert.Left = Left + Width - sbVert.Width - Skin.Layers["Control"].ContentMargins.Right; + sbVert.Top = Top + Skin.Layers["Control"].ContentMargins.Top; + sbVert.Height = Height - Skin.Layers["Control"].ContentMargins.Vertical; + sbVert.Anchor = Anchors.Top | Anchors.Right | Anchors.Bottom; + sbVert.PageSize = 25; + sbVert.Range = 1; + sbVert.PageSize = 1; + sbVert.StepSize = 10; + + pane = new ClipBox(manager); + pane.Init(); + pane.Parent = this; + pane.Top = Skin.Layers["Control"].ContentMargins.Top; + pane.Left = Skin.Layers["Control"].ContentMargins.Left; + pane.Width = Width - sbVert.Width - Skin.Layers["Control"].ContentMargins.Horizontal - 1; + pane.Height = Height - Skin.Layers["Control"].ContentMargins.Vertical; + pane.Anchor = Anchors.All; + pane.Passive = true; + pane.CanFocus = false; + pane.Draw += new DrawEventHandler(DrawPane); + + CanFocus = true; + Passive = false; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void AutoHeight(int maxItems) + { + if (items != null && items.Count < maxItems) maxItems = items.Count; + if (maxItems < 3) + { + //maxItems = 3; + sbVert.Visible = false; + pane.Width = Width - Skin.Layers["Control"].ContentMargins.Horizontal - 1; + } + else + { + pane.Width = Width - sbVert.Width - Skin.Layers["Control"].ContentMargins.Horizontal - 1; + sbVert.Visible = true; + } + + SkinText font = Skin.Layers["Control"].Text; + if (items != null && items.Count > 0) + { + int h = (int)font.Font.Resource.MeasureString(items[0].ToString()).Y; + Height = (h * maxItems) + (Skin.Layers["Control"].ContentMargins.Vertical);// - Skin.OriginMargins.Vertical); + } + else + { + Height = 32; + } + } + //////////////////////////////////////////////////////////////////////////// + + public override int MinimumHeight + { + get { return base.MinimumHeight; } + set + { + base.MinimumHeight = value; + if (this.sbVert != null) this.sbVert.MinimumHeight = value; + } + } + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + sbVert.Invalidate(); + pane.Invalidate(); + //DrawPane(this, new DrawEventArgs(renderer, rect, gameTime)); + + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DrawPane(object sender, DrawEventArgs e) + { + if (items != null && items.Count > 0) + { + SkinText font = Skin.Layers["Control"].Text; + SkinLayer sel = Skin.Layers["ListBox.Selection"]; + int h = (int)font.Font.Resource.MeasureString(items[0].ToString()).Y; + int v = (sbVert.Value / 10); + int p = (sbVert.PageSize / 10); + int d = (int)(((sbVert.Value % 10) / 10f) * h); + int c = items.Count; + int s = itemIndex; + + for (int i = v; i <= v + p + 1; i++) + { + if (i < c) + { + e.Renderer.DrawString(this, Skin.Layers["Control"], items[i].ToString(), new Rectangle(e.Rectangle.Left, e.Rectangle.Top - d + ((i - v) * h), e.Rectangle.Width, h), false); + } + } + if (s >= 0 && s < c && (Focused || !hideSelection)) + { + int pos = -d + ((s - v) * h); + if (pos > -h && pos < (p + 1) * h) + { + e.Renderer.DrawLayer(this, sel, new Rectangle(e.Rectangle.Left, e.Rectangle.Top + pos, e.Rectangle.Width, h)); + e.Renderer.DrawString(this, sel, items[s].ToString(), new Rectangle(e.Rectangle.Left, e.Rectangle.Top + pos, e.Rectangle.Width, h), false); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (e.Button == MouseButton.Left || e.Button == MouseButton.Right) + { + TrackItem(e.Position.X, e.Position.Y); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void TrackItem(int x, int y) + { + if (items != null && items.Count > 0 && (pane.ControlRect.Contains(new Point(x, y)))) + { + SkinText font = Skin.Layers["Control"].Text; + int h = (int)font.Font.Resource.MeasureString(items[0].ToString()).Y; + int d = (int)(((sbVert.Value % 10) / 10f) * h); + int i = (int)Math.Floor((sbVert.Value / 10f) + ((float)y / h)); + if (i >= 0 && i < Items.Count && i >= (int)Math.Floor((float)sbVert.Value / 10f) && i < (int)Math.Ceiling((float)(sbVert.Value + sbVert.PageSize) / 10f)) ItemIndex = i; + Focused = true; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (hotTrack) + { + TrackItem(e.Position.X, e.Position.Y); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnKeyPress(KeyEventArgs e) + { + if (e.Key == Keys.Down) + { + e.Handled = true; + itemIndex += sbVert.StepSize / 10; + } + else if (e.Key == Keys.Up) + { + e.Handled = true; + itemIndex -= sbVert.StepSize / 10; + } + else if (e.Key == Keys.PageDown) + { + e.Handled = true; + itemIndex += sbVert.PageSize / 10; + } + else if (e.Key == Keys.PageUp) + { + e.Handled = true; + itemIndex -= sbVert.PageSize / 10; + } + else if (e.Key == Keys.Home) + { + e.Handled = true; + itemIndex = 0; + } + else if (e.Key == Keys.End) + { + e.Handled = true; + itemIndex = items.Count - 1; + } + + if (itemIndex < 0) itemIndex = 0; + else if (itemIndex >= Items.Count) itemIndex = Items.Count - 1; + + ItemIndex = itemIndex; + + base.OnKeyPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + /// + /// Handles mouse scroll events for the list box. + /// + /// + protected override void OnMouseScroll(MouseEventArgs e) + { + Focused = true; + + if (e.ScrollDirection == MouseScrollDirection.Down) + { + e.Handled = true; + itemIndex += sbVert.StepSize / 10; + } + else if (e.ScrollDirection == MouseScrollDirection.Up) + { + e.Handled = true; + itemIndex -= sbVert.StepSize / 10; + } + + // Wrap index in collection range. + if (itemIndex < 0) itemIndex = 0; + else if (itemIndex >= Items.Count) itemIndex = Items.Count - 1; + + ItemIndex = itemIndex; + + base.OnMouseScroll(e); + } + + //////////////////////////////////////////////////////////////////////////// + protected override void OnGamePadPress(GamePadEventArgs e) + { + if (e.Button == GamePadActions.Down) + { + e.Handled = true; + itemIndex += sbVert.StepSize / 10; + } + else if (e.Button == GamePadActions.Up) + { + e.Handled = true; + itemIndex -= sbVert.StepSize / 10; + } + + if (itemIndex < 0) itemIndex = 0; + else if (itemIndex >= Items.Count) itemIndex = Items.Count - 1; + + ItemIndex = itemIndex; + base.OnGamePadPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ItemsChanged() + { + if (items != null && items.Count > 0) + { + SkinText font = Skin.Layers["Control"].Text; + int h = (int)font.Font.Resource.MeasureString(items[0].ToString()).Y; + + int sizev = Height - Skin.Layers["Control"].ContentMargins.Vertical; + sbVert.Range = items.Count * 10; + sbVert.PageSize = (int)Math.Floor((float)sizev * 10 / h); + Invalidate(); + } + else if (items == null || items.Count <= 0) + { + sbVert.Range = 1; + sbVert.PageSize = 1; + Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + ItemsChanged(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void ScrollTo(int index) + { + ItemsChanged(); + if ((index * 10) < sbVert.Value) + { + sbVert.Value = index * 10; + } + else if (index >= (int)Math.Floor(((float)sbVert.Value + sbVert.PageSize) / 10f)) + { + sbVert.Value = ((index + 1) * 10) - sbVert.PageSize; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + + if (Visible && items != null && items.Count != itemsCount) + { + itemsCount = items.Count; + ItemsChanged(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnItemIndexChanged(EventArgs e) + { + if (ItemIndexChanged != null) ItemIndexChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnHotTrackChanged(EventArgs e) + { + if (HotTrackChanged != null) HotTrackChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnHideSelectionChanged(EventArgs e) + { + if (HideSelectionChanged != null) HideSelectionChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} diff --git a/Neoforce/MainMenu.cs b/Neoforce/MainMenu.cs new file mode 100644 index 0000000..b4f5ac4 --- /dev/null +++ b/Neoforce/MainMenu.cs @@ -0,0 +1,362 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: MainMenu.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class MainMenu: MenuBase + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Rectangle[] rs; + private int lastIndex = -1; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public MainMenu(Manager manager): base(manager) + { + Left = 0; + Top = 0; + Height = 24; + Detached = false; + DoubleClicks = false; + StayOnBack = true; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["MainMenu"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + SkinLayer l1 = Skin.Layers["Control"]; + SkinLayer l2 = Skin.Layers["Selection"]; + rs = new Rectangle[Items.Count]; + + renderer.DrawLayer(this, l1, rect, ControlState.Enabled); + + int prev = l1.ContentMargins.Left; + for (int i = 0; i < Items.Count; i++) + { + MenuItem mi = Items[i]; + + int tw = (int)l1.Text.Font.Resource.MeasureString(mi.Text).X + l1.ContentMargins.Horizontal; + rs[i] = new Rectangle(rect.Left + prev, rect.Top + l1.ContentMargins.Top, tw, Height - l1.ContentMargins.Vertical); + prev += tw; + + if (ItemIndex != i) + { + if (mi.Enabled && Enabled) + { + renderer.DrawString(this, l1, mi.Text, rs[i], ControlState.Enabled, false); + } + else + { + renderer.DrawString(this, l1, mi.Text, rs[i], ControlState.Disabled, false); + } + } + else + { + if (Items[i].Enabled && Enabled) + { + renderer.DrawLayer(this, l2, rs[i], ControlState.Enabled); + renderer.DrawString(this, l2, mi.Text, rs[i], ControlState.Enabled, false); + } + else + { + renderer.DrawLayer(this, l2, rs[i], ControlState.Disabled); + renderer.DrawString(this, l2, mi.Text, rs[i], ControlState.Disabled, false); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void TrackItem(int x, int y) + { + if (Items != null && Items.Count > 0 && rs != null) + { + Invalidate(); + for (int i = 0; i < rs.Length; i++) + { + if (rs[i].Contains(x, y)) + { + if (i >= 0 && i != ItemIndex) + { + Items[i].SelectedInvoke(new EventArgs()); + } + ItemIndex = i; + return; + } + } + if (ChildMenu == null) ItemIndex = -1; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckArea(int x, int y) + { + return true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + int i = lastIndex; + + TrackItem(e.State.X - Root.AbsoluteLeft, e.State.Y - Root.AbsoluteTop); + + if (ItemIndex >= 0 && (i == -1 || i != ItemIndex) && Items[ItemIndex].Items != null && Items[ItemIndex].Items.Count > 0 && ChildMenu != null) + { + HideSubMenu(); + lastIndex = ItemIndex; + OnClick(e); + } + else if (ChildMenu != null && i != ItemIndex) + { + HideSubMenu(); + Focused = true; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseOut(MouseEventArgs e) + { + base.OnMouseOut(e); + + OnMouseMove(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void HideSubMenu() + { + if (ChildMenu != null) + { + (ChildMenu as ContextMenu).HideMenu(true); + ChildMenu.Dispose(); + ChildMenu = null; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void HideMenu() + { + if (ChildMenu != null) + { + (ChildMenu as ContextMenu).HideMenu(true); + ChildMenu.Dispose(); + ChildMenu = null; + } + if (Manager.FocusedControl is MenuBase) Focused = true; + Invalidate(); + ItemIndex = -1; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnClick(EventArgs e) + { + base.OnClick(e); + + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + + if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None) + { + if (ItemIndex >= 0 && Items[ItemIndex].Enabled) + { + if (ItemIndex >= 0 && Items[ItemIndex].Items != null && Items[ItemIndex].Items.Count > 0) + { + if (ChildMenu != null) + { + ChildMenu.Dispose(); + ChildMenu = null; + } + ChildMenu = new ContextMenu(Manager); + (ChildMenu as ContextMenu).RootMenu = this; + (ChildMenu as ContextMenu).ParentMenu = this; + (ChildMenu as ContextMenu).Sender = this.Root; + ChildMenu.Items.AddRange(Items[ItemIndex].Items); + + int y = Root.AbsoluteTop + rs[ItemIndex].Bottom + 1; + (ChildMenu as ContextMenu).Show(this.Root, Root.AbsoluteLeft + rs[ItemIndex].Left, y); + if (ex.Button == MouseButton.None) (ChildMenu as ContextMenu).ItemIndex = 0; + } + else + { + if (ItemIndex >= 0) + { + Items[ItemIndex].ClickInvoke(ex); + } + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnKeyPress(KeyEventArgs e) + { + base.OnKeyPress(e); + + if (e.Key == Keys.Right) + { + ItemIndex += 1; + e.Handled = true; + } + if (e.Key == Keys.Left) + { + ItemIndex -= 1; + e.Handled = true; + } + + if (ItemIndex > Items.Count - 1) ItemIndex = 0; + if (ItemIndex < 0) ItemIndex = Items.Count - 1; + + if (e.Key == Keys.Down && Items.Count > 0 && Items[ItemIndex].Items.Count > 0) + { + e.Handled = true; + OnClick(new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + if (e.Key == Keys.Escape) + { + e.Handled = true; + ItemIndex = -1; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnGamePadPress(GamePadEventArgs e) + { + base.OnGamePadPress(e); + + if (e.Button == GamePadActions.Right) + { + ItemIndex += 1; + e.Handled = true; + } + if (e.Button == GamePadActions.Left) + { + ItemIndex -= 1; + e.Handled = true; + } + + if (ItemIndex > Items.Count - 1) ItemIndex = 0; + if (ItemIndex < 0) ItemIndex = Items.Count - 1; + + if (e.Button == GamePadActions.Down && Items[ItemIndex].Items.Count > 0) + { + e.Handled = true; + OnClick(new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnFocusGained(EventArgs e) + { + base.OnFocusGained(e); + if (ItemIndex < 0 && Items.Count > 0) ItemIndex = 0; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnFocusLost(EventArgs e) + { + base.OnFocusLost(e); + if (ChildMenu == null || !ChildMenu.Visible) ItemIndex = -1; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Manager.cs b/Neoforce/Manager.cs new file mode 100644 index 0000000..24fc0d8 --- /dev/null +++ b/Neoforce/Manager.cs @@ -0,0 +1,1849 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Manager.cs // +// // +// Version: 0.8 // +// // +// Date: 05/07/2014 // +// // +// Author: Nathan 'Grimston' Pipes // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane 2010 // +// Copyright (c) by Nathan Pipes 2014 // +// // +//////////////////////////////////////////////////////////////// + + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Reflection; +using Microsoft.Xna.Framework.Input; + +#if (!XBOX && !XBOX_FAKE) +using System.IO; +using System.Text; +using System.Media; +#endif +//////////////////////////////////////////////////////////////////////////// + +#endregion + +[assembly: CLSCompliant(false)] + +namespace TomShane.Neoforce.Controls +{ + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Manages rendering of all controls. + /// + public class Manager : DrawableGameComponent + { + + private struct ControlStates + { + public Control[] Buttons; + public int Click; + public Control Over; + } + + #region //// Consts //////////// + + //////////////////////////////////////////////////////////////////////////// + internal Version _SkinVersion = new Version(0, 7); + internal Version _LayoutVersion = new Version(0, 7); + internal const string _SkinDirectory = ".\\Content\\Skins\\"; + internal const string _LayoutDirectory = ".\\Content\\Layout\\"; + internal const string _DefaultSkin = "Default"; + internal const string _SkinExtension = ".skin"; + internal const int _MenuDelay = 500; + internal const int _ToolTipDelay = 500; + internal const int _DoubleClickTime = 500; + internal const int _TextureResizeIncrement = 32; + internal const RenderTargetUsage _RenderTargetUsage = RenderTargetUsage.DiscardContents; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool deviceReset = false; + private bool renderTargetValid = false; + private RenderTarget2D renderTarget = null; + private int targetFrames = 60; + private long drawTime = 0; + private long updateTime = 0; + private GraphicsDeviceManager graphics = null; + private ArchiveManager content = null; + private Renderer renderer = null; + private InputSystem input = null; + private bool inputEnabled = true; + private List components = null; + private ControlsList controls = null; + private ControlsList orderList = null; + private Skin skin = null; + private string skinName = _DefaultSkin; + private string layoutDirectory = _LayoutDirectory; + private string skinDirectory = _SkinDirectory; + private string skinExtension = _SkinExtension; + private Control focusedControl = null; + private ModalContainer modalWindow = null; + private float globalDepth = 0.0f; + private int toolTipDelay = _ToolTipDelay; + private bool toolTipsEnabled = true; + private int menuDelay = _MenuDelay; + private int doubleClickTime = _DoubleClickTime; + private int textureResizeIncrement = _TextureResizeIncrement; + private bool logUnhandledExceptions = true; + private ControlStates states = new ControlStates(); + private KeyboardLayout keyboardLayout = null; + private List keyboardLayouts = new List(); + private bool disposing = false; + private bool useGuide = false; + private bool autoUnfocus = true; + private bool autoCreateRenderTarget = true; + private Cursor cursor = null; + private bool softwareCursor = false; + + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets a value indicating whether Manager is in the process of disposing. + /// + public virtual bool Disposing + { + get { return disposing; } + } + //////////////////////////////////////////////////////////////////////////// + + /// + /// Gets or sets an application cursor. + /// + public Cursor Cursor + { + get { return cursor; } + set { cursor = value; } + } + + /// + /// Should a software cursor be drawn? Very handy on a PC build. + /// + public bool ShowSoftwareCursor + { + get { return softwareCursor; } + set { softwareCursor = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Returns associated component. + /// + public virtual new Game Game { get { return base.Game; } } + + /// + /// Returns associated . + /// + public virtual new GraphicsDevice GraphicsDevice { get { return base.GraphicsDevice; } } + + /// + /// Returns associated . + /// + public virtual GraphicsDeviceManager Graphics { get { return graphics; } } + + /// + /// Returns used for rendering controls. + /// + public virtual Renderer Renderer { get { return renderer; } } + + /// + /// Returns used for loading assets. + /// + public virtual ArchiveManager Content { get { return content; } } + + /// + /// Returns instance responsible for managing user input. + /// + public virtual InputSystem Input { get { return input; } } + + /// + /// Returns list of components added to the manager. + /// + public virtual IEnumerable Components { get { return components; } } + + /// + /// Returns list of controls added to the manager. + /// + public virtual IEnumerable Controls { get { return controls; } } + + /// + /// Gets or sets the depth value used for rendering sprites. + /// + public virtual float GlobalDepth { get { return globalDepth; } set { globalDepth = value; } } + + /// + /// Gets or sets the time that passes before the appears. + /// + public virtual int ToolTipDelay { get { return toolTipDelay; } set { toolTipDelay = value; } } + + /// + /// Gets or sets the time that passes before a submenu appears when hovered over menu item. + /// + public virtual int MenuDelay { get { return menuDelay; } set { menuDelay = value; } } + + /// + /// Gets or sets the maximum number of milliseconds that can elapse between a first click and a second click to consider the mouse action a double-click. + /// + public virtual int DoubleClickTime { get { return doubleClickTime; } set { doubleClickTime = value; } } + + /// + /// Gets or sets texture size increment in pixel while performing controls resizing. + /// + public virtual int TextureResizeIncrement { get { return textureResizeIncrement; } set { textureResizeIncrement = value; } } + + /// + /// Enables or disables showing of tooltips globally. + /// + public virtual bool ToolTipsEnabled { get { return toolTipsEnabled; } set { toolTipsEnabled = value; } } + + /// + /// Enables or disables logging of unhandled exceptions. + /// + public virtual bool LogUnhandledExceptions { get { return logUnhandledExceptions; } set { logUnhandledExceptions = value; } } + + /// + /// Enables or disables input processing. + /// + public virtual bool InputEnabled { get { return inputEnabled; } set { inputEnabled = value; } } + + /// + /// Gets or sets render target for drawing. + /// + public virtual RenderTarget2D RenderTarget { get { return renderTarget; } set { renderTarget = value; } } + + /// + /// Gets or sets update interval for drawing, logic and input. + /// + public virtual int TargetFrames { get { return targetFrames; } set { targetFrames = value; } } + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets collection of active keyboard layouts. + /// + public virtual List KeyboardLayouts + { + get { return keyboardLayouts; } + set { keyboardLayouts = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating if Guide component can be used + /// + public bool UseGuide + { + get { return useGuide; } + set { useGuide = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating if a control should unfocus if you click outside on the screen. + /// + //////////////////////////////////////////////////////////////////////////// + public virtual bool AutoUnfocus + { + get { return autoUnfocus; } + set { autoUnfocus = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets a value indicating wheter Manager should create render target automatically. + /// + //////////////////////////////////////////////////////////////////////////// + public virtual bool AutoCreateRenderTarget + { + get { return autoCreateRenderTarget; } + set { autoCreateRenderTarget = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets current keyboard layout for text input. + /// + public virtual KeyboardLayout KeyboardLayout + { + get + { + if (keyboardLayout == null) + { + keyboardLayout = new KeyboardLayout(); + } + return keyboardLayout; + } + set + { + keyboardLayout = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the initial directory for looking for the skins in. + /// + public virtual string SkinDirectory + { + get + { + if (!skinDirectory.EndsWith("\\")) + { + skinDirectory += "\\"; + } + return skinDirectory; + } + set + { + skinDirectory = value; + if (!skinDirectory.EndsWith("\\")) + { + skinDirectory += "\\"; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets the initial directory for looking for the layout files in. + /// + public virtual string LayoutDirectory + { + get + { + if (!layoutDirectory.EndsWith("\\")) + { + layoutDirectory += "\\"; + } + return layoutDirectory; + } + set + { + layoutDirectory = value; + if (!layoutDirectory.EndsWith("\\")) + { + layoutDirectory += "\\"; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets file extension for archived skin files. + /// + public string SkinExtension + { + get + { + if (!skinExtension.StartsWith(".")) + { + skinExtension = "." + skinExtension; + } + return skinExtension; + } + set + { + skinExtension = value; + if (!skinExtension.StartsWith(".")) + { + skinExtension = "." + skinExtension; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets width of the selected render target in pixels. + /// + public virtual int TargetWidth + { + get + { + if (renderTarget != null) + { + return renderTarget.Width; + } + else return ScreenWidth; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets height of the selected render target in pixels. + /// + public virtual int TargetHeight + { + get + { + if (renderTarget != null) + { + return renderTarget.Height; + } + else return ScreenHeight; + } + } + //////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets current width of the screen in pixels. + /// + public virtual int ScreenWidth + { + get + { + if (GraphicsDevice != null) + { + return GraphicsDevice.PresentationParameters.BackBufferWidth; + } + else return 0; + } + + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets current height of the screen in pixels. + /// + public virtual int ScreenHeight + { + get + { + if (GraphicsDevice != null) + { + return GraphicsDevice.PresentationParameters.BackBufferHeight; + } + else return 0; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Gets or sets new skin used by all controls. + /// + public virtual Skin Skin + { + get + { + return skin; + } + set + { + SetSkin(value); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Returns currently active modal window. + /// + public virtual ModalContainer ModalWindow + { + get + { + return modalWindow; + } + internal set + { + modalWindow = value; + + if (value != null) + { + value.ModalResult = ModalResult.None; + + value.Visible = true; + value.Focused = true; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Returns currently focused control. + /// + public virtual Control FocusedControl + { + get + { + return focusedControl; + } + internal set + { + if (value != null && value.Visible && value.Enabled) + { + if (value != null && value.CanFocus) + { + if (focusedControl == null || (focusedControl != null && value.Root != focusedControl.Root) || !value.IsRoot) + { + if (focusedControl != null && focusedControl != value) + { + focusedControl.Focused = false; + } + focusedControl = value; + } + } + else if (value != null && !value.CanFocus) + { + if (focusedControl != null && value.Root != focusedControl.Root) + { + if (focusedControl != value.Root) + { + focusedControl.Focused = false; + } + focusedControl = value.Root; + } + else if (focusedControl == null) + { + focusedControl = value.Root; + } + } + BringToFront(value.Root); + } + else if (value == null) + { + focusedControl = value; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal virtual ControlsList OrderList { get { return orderList; } } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Occurs when the GraphicsDevice settings are changed. + /// + public event DeviceEventHandler DeviceSettingsChanged; + + /// + /// Occurs when the skin is about to change. + /// + public event SkinEventHandler SkinChanging; + + /// + /// Occurs when the skin changes. + /// + public event SkinEventHandler SkinChanged; + + /// + /// Occurs when game window is about to close. + /// + public event WindowClosingEventHandler WindowClosing; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Initializes a new instance of the Manager class. + /// + /// + /// The Game class. + /// + /// + /// The GraphicsDeviceManager class provided by the Game class. + /// + /// + /// The name of the skin being loaded at the start. + /// + public Manager(Game game, GraphicsDeviceManager graphics, string skin) + : base(game) + { + disposing = false; + + AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(HandleUnhadledExceptions); + + content = new ArchiveManager(Game.Services); + input = new InputSystem(this, new InputOffset(0, 0, 1f, 1f)); + components = new List(); + controls = new ControlsList(); + orderList = new ControlsList(); + + this.graphics = graphics; + graphics.PreparingDeviceSettings += new EventHandler(PrepareGraphicsDevice); + + skinName = skin; + +#if (XBOX_FAKE) + game.Window.Title += " (XBOX_FAKE)"; +#endif + + states.Buttons = new Control[32]; + states.Click = -1; + states.Over = null; + + input.MouseDown += new MouseEventHandler(MouseDownProcess); + input.MouseUp += new MouseEventHandler(MouseUpProcess); + input.MousePress += new MouseEventHandler(MousePressProcess); + input.MouseMove += new MouseEventHandler(MouseMoveProcess); + input.MouseScroll += new MouseEventHandler(MouseScrollProcess); + + input.GamePadDown += new GamePadEventHandler(GamePadDownProcess); + input.GamePadUp += new GamePadEventHandler(GamePadUpProcess); + input.GamePadPress += new GamePadEventHandler(GamePadPressProcess); + + input.KeyDown += new KeyEventHandler(KeyDownProcess); + input.KeyUp += new KeyEventHandler(KeyUpProcess); + input.KeyPress += new KeyEventHandler(KeyPressProcess); + + keyboardLayouts.Add(new KeyboardLayout()); + keyboardLayouts.Add(new CzechKeyboardLayout()); + keyboardLayouts.Add(new GermanKeyboardLayout()); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Initializes a new instance of the Manager class. + /// + /// + /// The Game class. + /// + /// + /// The name of the skin being loaded at the start. + /// + public Manager(Game game, string skin) + : this(game, game.Services.GetService(typeof(IGraphicsDeviceManager)) as GraphicsDeviceManager, skin) + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Initializes a new instance of the Manager class, loads the default skin and registers manager in the game class automatically. + /// + /// + /// The Game class. + /// + /// + /// The GraphicsDeviceManager class provided by the Game class. + /// + public Manager(Game game, GraphicsDeviceManager graphics) + : this(game, graphics, _DefaultSkin) + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Initializes a new instance of the Manager class, loads the default skin and registers manager in the game class automatically. + /// + /// + /// The Game class. + /// + public Manager(Game game) + : this(game, game.Services.GetService(typeof(IGraphicsDeviceManager)) as GraphicsDeviceManager, _DefaultSkin) + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.disposing = true; + + // Recursively disposing all controls added to the manager and its child controls. + if (controls != null) + { + int c = controls.Count; + for (int i = 0; i < c; i++) + { + if (controls.Count > 0) controls[0].Dispose(); + } + } + + // Disposing all components added to manager. + if (components != null) + { + int c = components.Count; + for (int i = 0; i < c; i++) + { + if (components.Count > 0) components[0].Dispose(); + } + } + + if (content != null) + { + content.Unload(); + content.Dispose(); + content = null; + } + + if (renderer != null) + { + renderer.Dispose(); + renderer = null; + } + if (input != null) + { + input.Dispose(); + input = null; + } + } + if (GraphicsDevice != null) + GraphicsDevice.DeviceReset -= new System.EventHandler(GraphicsDevice_DeviceReset); + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + + public void SetCursor(Cursor cursor) + { + this.cursor = cursor; + if (this.cursor.CursorTexture == null) + { + this.cursor.CursorTexture = Texture2D.FromStream(GraphicsDevice, new FileStream( + this.cursor.cursorPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void InitSkins() + { + // Initializing skins for every control created, even not visible or + // not added to the manager or another parent. + foreach (Control c in Control.Stack) + { + c.InitSkin(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void InitControls() + { + // Initializing all controls created, even not visible or + // not added to the manager or another parent. + foreach (Control c in Control.Stack) + { + c.Init(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void SortLevel(ControlsList cs) + { + if (cs != null) + { + foreach (Control c in cs) + { + if (c.Visible) + { + OrderList.Add(c); + SortLevel(c.Controls as ControlsList); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Method used as an event handler for the GraphicsDeviceManager.PreparingDeviceSettings event. + /// + protected virtual void PrepareGraphicsDevice(object sender, PreparingDeviceSettingsEventArgs e) + { + e.GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage = _RenderTargetUsage; + int w = e.GraphicsDeviceInformation.PresentationParameters.BackBufferWidth; + int h = e.GraphicsDeviceInformation.PresentationParameters.BackBufferHeight; + + foreach (Control c in Controls) + { + SetMaxSize(c, w, h); + } + + + if (DeviceSettingsChanged != null) DeviceSettingsChanged.Invoke(new DeviceEventArgs(e)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void SetMaxSize(Control c, int w, int h) + { + if (c.Width > w) + { + w -= (c.Skin != null) ? c.Skin.OriginMargins.Horizontal : 0; + c.Width = w; + } + if (c.Height > h) + { + h -= (c.Skin != null) ? c.Skin.OriginMargins.Vertical : 0; + c.Height = h; + } + + foreach (Control cx in c.Controls) + { + SetMaxSize(cx, w, h); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Initializes the controls manager. + /// + //////////////////////////////////////////////////////////////////////////// + public override void Initialize() + { + base.Initialize(); + + if (autoCreateRenderTarget) + { + if (renderTarget != null) + { + renderTarget.Dispose(); + } + renderTarget = CreateRenderTarget(); + } + + GraphicsDevice.DeviceReset += new System.EventHandler(GraphicsDevice_DeviceReset); + + input.Initialize(); + renderer = new Renderer(this); + SetSkin(skinName); + } + //////////////////////////////////////////////////////////////////////////// + + private void InvalidateRenderTarget() + { + renderTargetValid = false; + } + + //////////////////////////////////////////////////////////////////////////// + public virtual RenderTarget2D CreateRenderTarget() + { + return CreateRenderTarget(ScreenWidth, ScreenHeight); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual RenderTarget2D CreateRenderTarget(int width, int height) + { + Input.InputOffset = new InputOffset(0, 0, ScreenWidth / (float)width, ScreenHeight / (float)height); + return new RenderTarget2D(GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None, GraphicsDevice.PresentationParameters.MultiSampleCount, _RenderTargetUsage); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Sets and loads the new skin. + /// + /// + /// The name of the skin being loaded. + /// + public virtual void SetSkin(string name) + { + Skin skin = new Skin(this, name); + SetSkin(skin); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Sets the new skin. + /// + /// + /// The skin being set. + /// + public virtual void SetSkin(Skin skin) + { + if (SkinChanging != null) SkinChanging.Invoke(new EventArgs()); + + if (this.skin != null) + { + Remove(this.skin); + this.skin.Dispose(); + this.skin = null; + GC.Collect(); + } + this.skin = skin; + this.skin.Init(); + Add(this.skin); + skinName = this.skin.Name; + +#if (!XBOX && !XBOX_FAKE) + if (this.skin.Cursors["Default"] != null) + { + SetCursor(this.skin.Cursors["Default"].Resource); + } +#endif + + InitSkins(); + if (SkinChanged != null) SkinChanged.Invoke(new EventArgs()); + + InitControls(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Brings the control to the front of the z-order. + /// + /// + /// The control being brought to the front. + /// + public virtual void BringToFront(Control control) + { + if (control != null && !control.StayOnBack) + { + ControlsList cs = (control.Parent == null) ? controls as ControlsList : control.Parent.Controls as ControlsList; + if (cs.Contains(control)) + { + cs.Remove(control); + if (!control.StayOnTop) + { + int pos = cs.Count; + for (int i = cs.Count - 1; i >= 0; i--) + { + if (!cs[i].StayOnTop) + { + break; + } + pos = i; + } + cs.Insert(pos, control); + } + else + { + cs.Add(control); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Sends the control to the back of the z-order. + /// + /// + /// The control being sent back. + /// + public virtual void SendToBack(Control control) + { + if (control != null && !control.StayOnTop) + { + ControlsList cs = (control.Parent == null) ? controls as ControlsList : control.Parent.Controls as ControlsList; + if (cs.Contains(control)) + { + cs.Remove(control); + if (!control.StayOnBack) + { + int pos = 0; + for (int i = 0; i < cs.Count; i++) + { + if (!cs[i].StayOnBack) + { + break; + } + pos = i; + } + cs.Insert(pos, control); + } + else + { + cs.Insert(0, control); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Called when the manager needs to be updated. + /// + /// + /// Time elapsed since the last call to Update. + /// + public override void Update(GameTime gameTime) + { + updateTime += gameTime.ElapsedGameTime.Ticks; + double ms = TimeSpan.FromTicks(updateTime).TotalMilliseconds; + + if (targetFrames == 0 || ms == 0 || ms >= (1000f / targetFrames)) + { + TimeSpan span = TimeSpan.FromTicks(updateTime); + gameTime = new GameTime(gameTime.TotalGameTime, span); + updateTime = 0; + + if (inputEnabled) + { + input.Update(gameTime); + } + + if (components != null) + { + foreach (Component c in components) + { + c.Update(gameTime); + } + } + + ControlsList list = new ControlsList(controls); + + if (list != null) + { + foreach (Control c in list) + { + c.Update(gameTime); + } + } + + OrderList.Clear(); + SortLevel(controls); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Adds a component or a control to the manager. + /// + /// + /// The component or control being added. + /// + public virtual void Add(Component component) + { + if (component != null) + { + if (component is Control && !controls.Contains(component as Control)) + { + Control c = (Control)component; + + if (c.Parent != null) c.Parent.Remove(c); + + controls.Add(c); + c.Manager = this; + c.Parent = null; + if (focusedControl == null) c.Focused = true; + + DeviceSettingsChanged += new DeviceEventHandler((component as Control).OnDeviceSettingsChanged); + SkinChanging += new SkinEventHandler((component as Control).OnSkinChanging); + SkinChanged += new SkinEventHandler((component as Control).OnSkinChanged); + } + else if (!(component is Control) && !components.Contains(component)) + { + components.Add(component); + component.Manager = this; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Removes a component or a control from the manager. + /// + /// + /// The component or control being removed. + /// + public virtual void Remove(Component component) + { + if (component != null) + { + if (component is Control) + { + Control c = component as Control; + SkinChanging -= c.OnSkinChanging; + SkinChanged -= c.OnSkinChanged; + DeviceSettingsChanged -= c.OnDeviceSettingsChanged; + + if (c.Focused) c.Focused = false; + controls.Remove(c); + } + else + { + components.Remove(component); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Prepare(GameTime gameTime) + { + + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Renders all controls added to the manager. + /// + /// + /// Time passed since the last call to Draw. + /// + public virtual void BeginDraw(GameTime gameTime) + { + if (!renderTargetValid && AutoCreateRenderTarget) + { + if (renderTarget != null) RenderTarget.Dispose(); + RenderTarget = CreateRenderTarget(); + renderer = new Renderer(this); + } + Draw(gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Draw(GameTime gameTime) + { + if (renderTarget != null) + { + drawTime += gameTime.ElapsedGameTime.Ticks; + double ms = TimeSpan.FromTicks(drawTime).TotalMilliseconds; + + //if (targetFrames == 0 || (ms == 0 || ms >= (1000f / targetFrames))) + //{ + TimeSpan span = TimeSpan.FromTicks(drawTime); + gameTime = new GameTime(gameTime.TotalGameTime, span); + drawTime = 0; + + if ((controls != null)) + { + ControlsList list = new ControlsList(); + list.AddRange(controls); + + foreach (Control c in list) + { + c.PrepareTexture(renderer, gameTime); + } + + GraphicsDevice.SetRenderTarget(renderTarget); + GraphicsDevice.Clear(Color.Transparent); + + if (renderer != null) + { + foreach (Control c in list) + { + c.Render(renderer, gameTime); + } + } + } + + if (softwareCursor && Cursor != null) + { + if (this.cursor.CursorTexture == null) + { + this.cursor.CursorTexture = Texture2D.FromStream(GraphicsDevice, new FileStream( + this.cursor.cursorPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)); + } + renderer.SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); + MouseState mstate = Mouse.GetState(); + Rectangle rect = new Rectangle(mstate.X, mstate.Y, Cursor.Width, Cursor.Height); + renderer.SpriteBatch.Draw(Cursor.CursorTexture, rect, null, Color.White, 0f, Cursor.HotSpot, SpriteEffects.None, 0f); + renderer.SpriteBatch.End(); + } + + GraphicsDevice.SetRenderTarget(null); + //} + } + else + { + throw new Exception("Manager.RenderTarget has to be specified. Assign a render target or set Manager.AutoCreateRenderTarget property to true."); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Draws texture resolved from RenderTarget used for rendering. + /// + //////////////////////////////////////////////////////////////////////////// + public virtual void EndDraw() + { + EndDraw(new Rectangle(0, 0, ScreenWidth, ScreenHeight)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + /// Draws texture resolved from RenderTarget to specified rectangle. + /// + //////////////////////////////////////////////////////////////////////////// + public virtual void EndDraw(Rectangle rect) + { + if (renderTarget != null && !deviceReset) + { + renderer.Begin(BlendingMode.Default); + renderer.Draw(RenderTarget, rect, Color.White); + renderer.End(); + } + else if (deviceReset) + { + deviceReset = false; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Control GetControl(string name) + { + foreach (Control c in Controls) + { + if (c.Name.ToLower() == name.ToLower()) + { + return c; + } + } + return null; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void HandleUnhadledExceptions(object sender, UnhandledExceptionEventArgs e) + { + if (LogUnhandledExceptions) + { + LogException(e.ExceptionObject as Exception); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void GraphicsDevice_DeviceReset(object sender, System.EventArgs e) + { + deviceReset = true; + InvalidateRenderTarget(); + /*if (AutoCreateRenderTarget) + { + if (renderTarget != null) RenderTarget.Dispose(); + RenderTarget = CreateRenderTarget(); + } + }*/ + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void LogException(Exception e) + { +#if (!XBOX && !XBOX_FAKE) + string an = Assembly.GetEntryAssembly().Location; + Assembly asm = Assembly.GetAssembly(typeof(Manager)); + string path = Path.GetDirectoryName(an); + string fn = path + "\\" + Path.GetFileNameWithoutExtension(asm.Location) + ".log"; + + File.AppendAllText(fn, "////////////////////////////////////////////////////////////////\n" + + " Date: " + DateTime.Now.ToString() + "\n" + + "Assembly: " + Path.GetFileName(asm.Location) + "\n" + + " Version: " + asm.GetName().Version.ToString() + "\n" + + " Message: " + e.Message + "\n" + + "////////////////////////////////////////////////////////////////\n" + + e.StackTrace + "\n" + + "////////////////////////////////////////////////////////////////\n\n", Encoding.Default); +#endif + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Input ///////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckParent(Control control, Point pos) + { + if (control.Parent != null && !CheckDetached(control)) + { + Control parent = control.Parent; + Control root = control.Root; + + Rectangle pr = new Rectangle(parent.AbsoluteLeft, + parent.AbsoluteTop, + parent.Width, + parent.Height); + + Margins margins = root.Skin.ClientMargins; + Rectangle rr = new Rectangle(root.AbsoluteLeft + margins.Left, + root.AbsoluteTop + margins.Top, + root.OriginWidth - margins.Horizontal, + root.OriginHeight - margins.Vertical); + + + return (rr.Contains(pos) && pr.Contains(pos)); + } + + return true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckState(Control control) + { + bool modal = (ModalWindow == null) ? true : (ModalWindow == control.Root); + + return (control != null && !control.Passive && control.Visible && control.Enabled && modal); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckOrder(Control control, Point pos) + { + if (!CheckPosition(control, pos)) return false; + + for (int i = OrderList.Count - 1; i > OrderList.IndexOf(control); i--) + { + Control c = OrderList[i]; + + if (!c.Passive && CheckPosition(c, pos) && CheckParent(c, pos)) + { + return false; + } + } + + return true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckDetached(Control control) + { + bool ret = control.Detached; + if (control.Parent != null) + { + if (CheckDetached(control.Parent)) ret = true; + } + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckPosition(Control control, Point pos) + { + return (control.AbsoluteLeft <= pos.X && + control.AbsoluteTop <= pos.Y && + control.AbsoluteLeft + control.Width >= pos.X && + control.AbsoluteTop + control.Height >= pos.Y && + CheckParent(control, pos)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool CheckButtons(int index) + { + for (int i = 0; i < states.Buttons.Length; i++) + { + if (i == index) continue; + if (states.Buttons[i] != null) return false; + } + + return true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void TabNextControl(Control control) + { + int start = OrderList.IndexOf(control); + int i = start; + + do + { + if (i < OrderList.Count - 1) i += 1; + else i = 0; + } + while ((OrderList[i].Root != control.Root || !OrderList[i].CanFocus || OrderList[i].IsRoot || !OrderList[i].Enabled) && i != start); + + OrderList[i].Focused = true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void TabPrevControl(Control control) + { + int start = OrderList.IndexOf(control); + int i = start; + + do + { + if (i > 0) i -= 1; + else i = OrderList.Count - 1; + } + while ((OrderList[i].Root != control.Root || !OrderList[i].CanFocus || OrderList[i].IsRoot || !OrderList[i].Enabled) && i != start); + OrderList[i].Focused = true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ProcessArrows(Control control, KeyEventArgs kbe, GamePadEventArgs gpe) + { + Control c = control; + if (c.Parent != null && c.Parent.Controls != null) + { + int index = -1; + + if ((kbe.Key == Microsoft.Xna.Framework.Input.Keys.Left && !kbe.Handled) || + (gpe.Button == c.GamePadActions.Left && !gpe.Handled)) + { + int miny = int.MaxValue; + int minx = int.MinValue; + for (int i = 0; i < (c.Parent.Controls as ControlsList).Count; i++) + { + Control cx = (c.Parent.Controls as ControlsList)[i]; + if (cx == c || !cx.Visible || !cx.Enabled || cx.Passive || !cx.CanFocus) continue; + + int cay = (int)(c.Top + (c.Height / 2)); + int cby = (int)(cx.Top + (cx.Height / 2)); + + if (Math.Abs(cay - cby) <= miny && (cx.Left + cx.Width) >= minx && (cx.Left + cx.Width) <= c.Left) + { + miny = Math.Abs(cay - cby); + minx = cx.Left + cx.Width; + index = i; + } + } + } + else if ((kbe.Key == Microsoft.Xna.Framework.Input.Keys.Right && !kbe.Handled) || + (gpe.Button == c.GamePadActions.Right && !gpe.Handled)) + { + int miny = int.MaxValue; + int minx = int.MaxValue; + for (int i = 0; i < (c.Parent.Controls as ControlsList).Count; i++) + { + Control cx = (c.Parent.Controls as ControlsList)[i]; + if (cx == c || !cx.Visible || !cx.Enabled || cx.Passive || !cx.CanFocus) continue; + + int cay = (int)(c.Top + (c.Height / 2)); + int cby = (int)(cx.Top + (cx.Height / 2)); + + if (Math.Abs(cay - cby) <= miny && cx.Left <= minx && cx.Left >= (c.Left + c.Width)) + { + miny = Math.Abs(cay - cby); + minx = cx.Left; + index = i; + } + } + } + else if ((kbe.Key == Microsoft.Xna.Framework.Input.Keys.Up && !kbe.Handled) || + (gpe.Button == c.GamePadActions.Up && !gpe.Handled)) + { + int miny = int.MinValue; + int minx = int.MaxValue; + for (int i = 0; i < (c.Parent.Controls as ControlsList).Count; i++) + { + Control cx = (c.Parent.Controls as ControlsList)[i]; + if (cx == c || !cx.Visible || !cx.Enabled || cx.Passive || !cx.CanFocus) continue; + + int cax = (int)(c.Left + (c.Width / 2)); + int cbx = (int)(cx.Left + (cx.Width / 2)); + + if (Math.Abs(cax - cbx) <= minx && (cx.Top + cx.Height) >= miny && (cx.Top + cx.Height) <= c.Top) + { + minx = Math.Abs(cax - cbx); + miny = cx.Top + cx.Height; + index = i; + } + } + } + else if ((kbe.Key == Microsoft.Xna.Framework.Input.Keys.Down && !kbe.Handled) || + (gpe.Button == c.GamePadActions.Down && !gpe.Handled)) + { + int miny = int.MaxValue; + int minx = int.MaxValue; + for (int i = 0; i < (c.Parent.Controls as ControlsList).Count; i++) + { + Control cx = (c.Parent.Controls as ControlsList)[i]; + if (cx == c || !cx.Visible || !cx.Enabled || cx.Passive || !cx.CanFocus) continue; + + int cax = (int)(c.Left + (c.Width / 2)); + int cbx = (int)(cx.Left + (cx.Width / 2)); + + if (Math.Abs(cax - cbx) <= minx && cx.Top <= miny && cx.Top >= (c.Top + c.Height)) + { + minx = Math.Abs(cax - cbx); + miny = cx.Top; + index = i; + } + } + } + + if (index != -1) + { + (c.Parent.Controls as ControlsList)[index].Focused = true; + kbe.Handled = true; + gpe.Handled = true; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void MouseDownProcess(object sender, MouseEventArgs e) + { + ControlsList c = new ControlsList(); + c.AddRange(OrderList); + + if (autoUnfocus && focusedControl != null && focusedControl.Root != modalWindow) + { + bool hit = false; + + foreach (Control cx in Controls) + { + if (cx.AbsoluteRect.Contains(e.Position)) + { + hit = true; + break; + } + } + if (!hit) + { + for (int i = 0; i < Control.Stack.Count; i++) + { + if (Control.Stack[i].Visible && Control.Stack[i].Detached && Control.Stack[i].AbsoluteRect.Contains(e.Position)) + { + hit = true; + break; + } + } + } + if (!hit) focusedControl.Focused = false; + } + + for (int i = c.Count - 1; i >= 0; i--) + { + if (CheckState(c[i]) && CheckPosition(c[i], e.Position)) + { + states.Buttons[(int)e.Button] = c[i]; + c[i].SendMessage(Message.MouseDown, e); + + if (states.Click == -1) + { + states.Click = (int)e.Button; + + if (FocusedControl != null) + { + FocusedControl.Invalidate(); + } + c[i].Focused = true; + } + return; + } + } + + if (ModalWindow != null) + { +#if (!XBOX && !XBOX_FAKE) + SystemSounds.Beep.Play(); +#endif + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void MouseUpProcess(object sender, MouseEventArgs e) + { + Control c = states.Buttons[(int)e.Button]; + if (c != null) + { + if (CheckPosition(c, e.Position) && CheckOrder(c, e.Position) && states.Click == (int)e.Button && CheckButtons((int)e.Button)) + { + c.SendMessage(Message.Click, e); + } + states.Click = -1; + c.SendMessage(Message.MouseUp, e); + states.Buttons[(int)e.Button] = null; + MouseMoveProcess(sender, e); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void MousePressProcess(object sender, MouseEventArgs e) + { + Control c = states.Buttons[(int)e.Button]; + if (c != null) + { + if (CheckPosition(c, e.Position)) + { + c.SendMessage(Message.MousePress, e); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void MouseMoveProcess(object sender, MouseEventArgs e) + { + ControlsList c = new ControlsList(); + c.AddRange(OrderList); + + for (int i = c.Count - 1; i >= 0; i--) + { + bool chpos = CheckPosition(c[i], e.Position); + bool chsta = CheckState(c[i]); + + if (chsta && ((chpos && states.Over == c[i]) || (states.Buttons[(int)e.Button] == c[i]))) + { + c[i].SendMessage(Message.MouseMove, e); + break; + } + } + + for (int i = c.Count - 1; i >= 0; i--) + { + bool chpos = CheckPosition(c[i], e.Position); + bool chsta = CheckState(c[i]) || (c[i].ToolTip.Text != "" && c[i].ToolTip.Text != null && c[i].Visible); + + if (chsta && !chpos && states.Over == c[i] && states.Buttons[(int)e.Button] == null) + { + states.Over = null; + c[i].SendMessage(Message.MouseOut, e); + break; + } + } + + for (int i = c.Count - 1; i >= 0; i--) + { + bool chpos = CheckPosition(c[i], e.Position); + bool chsta = CheckState(c[i]) || (c[i].ToolTip.Text != "" && c[i].ToolTip.Text != null && c[i].Visible); + + if (chsta && chpos && states.Over != c[i] && states.Buttons[(int)e.Button] == null) + { + if (states.Over != null) + { + states.Over.SendMessage(Message.MouseOut, e); + } + states.Over = c[i]; + c[i].SendMessage(Message.MouseOver, e); + break; + } + else if (states.Over == c[i]) break; + } + } + //////////////////////////////////////////////////////////////////////////// + + /// + /// Processes mouse scroll events for the manager. + /// + /// + /// + private void MouseScrollProcess(object sender, MouseEventArgs e) + { + ControlsList c = new ControlsList(); + c.AddRange(OrderList); + + for (int i = c.Count - 1; i >= 0; i--) + { + bool chpos = CheckPosition(c[i], e.Position); + bool chsta = CheckState(c[i]); + + if (chsta && chpos && states.Over == c[i]) + { + c[i].SendMessage(Message.MouseScroll, e); + break; + } + } + } + + //////////////////////////////////////////////////////////////////////////// + void GamePadDownProcess(object sender, GamePadEventArgs e) + { + Control c = FocusedControl; + + if (c != null && CheckState(c)) + { + if (states.Click == -1) + { + states.Click = (int)e.Button; + } + states.Buttons[(int)e.Button] = c; + c.SendMessage(Message.GamePadDown, e); + + if (e.Button == c.GamePadActions.Click) + { + c.SendMessage(Message.Click, new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void GamePadUpProcess(object sender, GamePadEventArgs e) + { + Control c = states.Buttons[(int)e.Button]; + + if (c != null) + { + if (e.Button == c.GamePadActions.Press) + { + c.SendMessage(Message.Click, new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + states.Click = -1; + states.Buttons[(int)e.Button] = null; + c.SendMessage(Message.GamePadUp, e); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void GamePadPressProcess(object sender, GamePadEventArgs e) + { + Control c = states.Buttons[(int)e.Button]; + if (c != null) + { + c.SendMessage(Message.GamePadPress, e); + + if ((e.Button == c.GamePadActions.Right || + e.Button == c.GamePadActions.Left || + e.Button == c.GamePadActions.Up || + e.Button == c.GamePadActions.Down) && !e.Handled && CheckButtons((int)e.Button)) + { + ProcessArrows(c, new KeyEventArgs(), e); + GamePadDownProcess(sender, e); + } + else if (e.Button == c.GamePadActions.NextControl && !e.Handled && CheckButtons((int)e.Button)) + { + TabNextControl(c); + GamePadDownProcess(sender, e); + } + else if (e.Button == c.GamePadActions.PrevControl && !e.Handled && CheckButtons((int)e.Button)) + { + TabPrevControl(c); + GamePadDownProcess(sender, e); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void KeyDownProcess(object sender, KeyEventArgs e) + { + Control c = FocusedControl; + + if (c != null && CheckState(c)) + { + if (states.Click == -1) + { + states.Click = (int)MouseButton.None; + } + states.Buttons[(int)MouseButton.None] = c; + c.SendMessage(Message.KeyDown, e); + + if (e.Key == Microsoft.Xna.Framework.Input.Keys.Enter) + { + c.SendMessage(Message.Click, new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void KeyUpProcess(object sender, KeyEventArgs e) + { + Control c = states.Buttons[(int)MouseButton.None]; + + if (c != null) + { + if (e.Key == Microsoft.Xna.Framework.Input.Keys.Space) + { + c.SendMessage(Message.Click, new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero)); + } + states.Click = -1; + states.Buttons[(int)MouseButton.None] = null; + c.SendMessage(Message.KeyUp, e); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void KeyPressProcess(object sender, KeyEventArgs e) + { + Control c = states.Buttons[(int)MouseButton.None]; + if (c != null) + { + c.SendMessage(Message.KeyPress, e); + + if ((e.Key == Microsoft.Xna.Framework.Input.Keys.Right || + e.Key == Microsoft.Xna.Framework.Input.Keys.Left || + e.Key == Microsoft.Xna.Framework.Input.Keys.Up || + e.Key == Microsoft.Xna.Framework.Input.Keys.Down) && !e.Handled && CheckButtons((int)MouseButton.None)) + { + ProcessArrows(c, e, new GamePadEventArgs(PlayerIndex.One)); + KeyDownProcess(sender, e); + } + else if (e.Key == Microsoft.Xna.Framework.Input.Keys.Tab && !e.Shift && !e.Handled && CheckButtons((int)MouseButton.None)) + { + TabNextControl(c); + KeyDownProcess(sender, e); + } + else if (e.Key == Microsoft.Xna.Framework.Input.Keys.Tab && e.Shift && !e.Handled && CheckButtons((int)MouseButton.None)) + { + TabPrevControl(c); + KeyDownProcess(sender, e); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} diff --git a/Neoforce/MenuBase.cs b/Neoforce/MenuBase.cs new file mode 100644 index 0000000..e3fd4b2 --- /dev/null +++ b/Neoforce/MenuBase.cs @@ -0,0 +1,141 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: MenuBase.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class MenuItem: Unknown + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public string Text = "MenuItem"; + public List Items = new List(); + public bool Separated = false; + public Texture2D Image = null; + public bool Enabled = true; + public object Tag { get; set; } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public MenuItem() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public MenuItem(string text): this() + { + Text = text; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public MenuItem(string text, bool separated): this(text) + { + Separated = separated; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler Click; + public event EventHandler Selected; + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal void ClickInvoke(EventArgs e) + { + if (Click != null) Click.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal void SelectedInvoke(EventArgs e) + { + if (Selected != null) Selected.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + + public abstract class MenuBase: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private int itemIndex = -1; + private List items = new List(); + private MenuBase childMenu = null; + private MenuBase rootMenu = null; + private MenuBase parentMenu = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + protected internal int ItemIndex { get { return itemIndex; } set { itemIndex = value; } } + protected internal MenuBase ChildMenu { get { return childMenu; } set { childMenu = value; } } + protected internal MenuBase RootMenu { get { return rootMenu; } set { rootMenu = value; } } + protected internal MenuBase ParentMenu { get { return parentMenu; } set { parentMenu = value; } } + public List Items { get { return items; } } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public MenuBase(Manager manager): base(manager) + { + rootMenu = this; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/ModalContainer.cs b/Neoforce/ModalContainer.cs new file mode 100644 index 0000000..46c7c9a --- /dev/null +++ b/Neoforce/ModalContainer.cs @@ -0,0 +1,194 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ModalContainer.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ModalContainer: Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private ModalResult modalResult = ModalResult.None; + private ModalContainer lastModal = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public override bool Visible + { + get + { + return base.Visible; + } + set + { + if (value) Focused = true; + base.Visible = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool IsModal + { + get { return Manager.ModalWindow == this; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ModalResult ModalResult + { + get + { + return modalResult; + } + set + { + modalResult = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event WindowClosingEventHandler Closing; + public event WindowClosedEventHandler Closed; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Consructors /////// + + //////////////////////////////////////////////////////////////////////////// + public ModalContainer(Manager manager): base(manager) + { + Manager.Input.GamePadDown += new GamePadEventHandler(Input_GamePadDown); + GamePadActions = new WindowGamePadActions(); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void ShowModal() + { + lastModal = Manager.ModalWindow; + Manager.ModalWindow = this; + Manager.Input.KeyDown += new KeyEventHandler(Input_KeyDown); + Manager.Input.GamePadDown += new GamePadEventHandler(Input_GamePadDown); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Close() + { + WindowClosingEventArgs ex = new WindowClosingEventArgs(); + OnClosing(ex); + if (!ex.Cancel) + { + Manager.Input.KeyDown -= Input_KeyDown; + Manager.Input.GamePadDown -= Input_GamePadDown; + Manager.ModalWindow = lastModal; + if (lastModal != null) lastModal.Focused = true; + Hide(); + WindowClosedEventArgs ev = new WindowClosedEventArgs(); + OnClosed(ev); + + if (ev.Dispose) + { + this.Dispose(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Close(ModalResult modalResult) + { + ModalResult = modalResult; + Close(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnClosing(WindowClosingEventArgs e) + { + if (Closing != null) Closing.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnClosed(WindowClosedEventArgs e) + { + if (Closed != null) Closed.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void Input_KeyDown(object sender, KeyEventArgs e) + { + if (Visible && (Manager.FocusedControl != null && Manager.FocusedControl.Root == this) && + e.Key == Microsoft.Xna.Framework.Input.Keys.Escape) + { + //Close(ModalResult.Cancel); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void Input_GamePadDown(object sender, GamePadEventArgs e) + { + if (Visible && (Manager.FocusedControl != null && Manager.FocusedControl.Root == this)) + { + if (e.Button == (GamePadActions as WindowGamePadActions).Accept) + { + Close(ModalResult.Ok); + } + else if (e.Button == (GamePadActions as WindowGamePadActions).Cancel) + { + Close(ModalResult.Cancel); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/NativeMethods.cs b/Neoforce/NativeMethods.cs new file mode 100644 index 0000000..84bafd1 --- /dev/null +++ b/Neoforce/NativeMethods.cs @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: NativeMethods.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Runtime.InteropServices; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + [Obsolete("Native methods should be avoided at all times")] + internal static class NativeMethods + { + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + [Obsolete] + [DllImport("User32.dll", CharSet = CharSet.Unicode)] + internal static extern IntPtr LoadImage(IntPtr instance, string fileName, uint type, int width, int height, uint load); + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + [Obsolete] + [DllImport("User32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DestroyCursor(IntPtr cursor); + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + [Obsolete] + internal static IntPtr LoadCursor(string fileName) + { + return LoadImage(IntPtr.Zero, fileName, 2, 0, 0, 0x0010); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + [Obsolete] + [DllImport("user32.dll")] + internal static extern short GetKeyState(int key); + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Panel.cs b/Neoforce/Panel.cs new file mode 100644 index 0000000..cbbe504 --- /dev/null +++ b/Neoforce/Panel.cs @@ -0,0 +1,280 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Panel.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class Panel: Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Bevel bevel = null; + private BevelStyle bevelStyle = BevelStyle.None; + private BevelBorder bevelBorder = BevelBorder.None; + private int bevelMargin = 0; + private Color bevelColor = Color.Transparent; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public BevelStyle BevelStyle + { + get { return bevelStyle; } + set + { + if (bevelStyle != value) + { + bevelStyle = bevel.Style = value; + AdjustMargins(); + if (!Suspended) OnBevelStyleChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public BevelBorder BevelBorder + { + get { return bevelBorder; } + set + { + if (bevelBorder != value) + { + bevelBorder = bevel.Border = value; + bevel.Visible = bevelBorder != BevelBorder.None; + AdjustMargins(); + if (!Suspended) OnBevelBorderChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public int BevelMargin + { + get { return bevelMargin; } + set + { + if (bevelMargin != value) + { + bevelMargin = value; + AdjustMargins(); + if (!Suspended) OnBevelMarginChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Color BevelColor + { + get { return bevelColor; } + set + { + bevel.Color = bevelColor = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler BevelBorderChanged; + public event EventHandler BevelStyleChanged; + public event EventHandler BevelMarginChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public Panel(Manager manager): base(manager) + { + Passive = false; + CanFocus = false; + Width = 64; + Height = 64; + + bevel = new Bevel(Manager); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + bevel.Init(); + bevel.Style = bevelStyle; + bevel.Border = bevelBorder; + bevel.Left = 0; + bevel.Top = 0; + bevel.Width = Width; + bevel.Height = Height; + bevel.Color = bevelColor; + bevel.Visible = (bevelBorder != BevelBorder.None); + bevel.Anchor = Anchors.Left | Anchors.Top | Anchors.Right | Anchors.Bottom; + Add(bevel, false); + AdjustMargins(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["Panel"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void AdjustMargins() + { + int l = 0; + int t = 0; + int r = 0; + int b = 0; + int s = bevelMargin; + + if (bevelBorder != BevelBorder.None) + { + if (bevelStyle != BevelStyle.Flat) + { + s += 2; + } + else + { + s += 1; + } + + if (bevelBorder == BevelBorder.Left || bevelBorder == BevelBorder.All) + { + l = s; + } + if (bevelBorder == BevelBorder.Top || bevelBorder == BevelBorder.All) + { + t = s; + } + if (bevelBorder == BevelBorder.Right || bevelBorder == BevelBorder.All) + { + r = s; + } + if (bevelBorder == BevelBorder.Bottom || bevelBorder == BevelBorder.All) + { + b = s; + } + } + ClientMargins = new Margins(Skin.ClientMargins.Left + l, Skin.ClientMargins.Top + t, Skin.ClientMargins.Right + r, Skin.ClientMargins.Bottom + b); + + base.AdjustMargins(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + int x = rect.Left; + int y = rect.Top; + int w = rect.Width; + int h = rect.Height; + int s = bevelMargin; + + if (bevelBorder != BevelBorder.None) + { + if (bevelStyle != BevelStyle.Flat) + { + s += 2; + } + else + { + s += 1; + } + + if (bevelBorder == BevelBorder.Left || bevelBorder == BevelBorder.All) + { + x += s; + w -= s; + } + if (bevelBorder == BevelBorder.Top || bevelBorder == BevelBorder.All) + { + y += s; + h -= s; + } + if (bevelBorder == BevelBorder.Right || bevelBorder == BevelBorder.All) + { + w -= s; + } + if (bevelBorder == BevelBorder.Bottom || bevelBorder == BevelBorder.All) + { + h -= s; + } + } + + base.DrawControl(renderer, new Rectangle(x, y, w, h), gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnBevelBorderChanged(EventArgs e) + { + if (BevelBorderChanged != null) BevelBorderChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnBevelStyleChanged(EventArgs e) + { + if (BevelStyleChanged != null) BevelStyleChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnBevelMarginChanged(EventArgs e) + { + if (BevelMarginChanged != null) BevelMarginChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/ProgressBar.cs b/Neoforce/ProgressBar.cs new file mode 100644 index 0000000..3e46ef8 --- /dev/null +++ b/Neoforce/ProgressBar.cs @@ -0,0 +1,264 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ProgressBar.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using System; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + public enum ProgressBarMode + { + Default, + Infinite + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + public class ProgressBar : Control + { + + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private int range = 100; + private int value = 0; + private double time = 0; + private int sign = 1; + private ProgressBarMode mode = ProgressBarMode.Default; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public int Value + { + get { return this.value; } + set + { + if (mode == ProgressBarMode.Default) + { + if (this.value != value) + { + this.value = value; + if (this.value > range) this.value = range; + if (this.value < 0) this.value = 0; + Invalidate(); + + if (!Suspended) OnValueChanged(new EventArgs()); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public ProgressBarMode Mode + { + get { return mode; } + set + { + if (mode != value) + { + mode = value; + if (mode == ProgressBarMode.Infinite) + { + range = 100; + this.value = 0; + time = 0; + sign = 1; + } + else + { + this.value = 0; + range = 100; + } + Invalidate(); + + if (!Suspended) OnModeChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public int Range + { + get { return range; } + set + { + if (range != value) + { + if (mode == ProgressBarMode.Default) + { + range = value; + if (range < 0) range = 0; + if (range < this.value) this.value = range; + Invalidate(); + + if (!Suspended) OnRangeChanged(new EventArgs()); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler ValueChanged; + public event EventHandler RangeChanged; + public event EventHandler ModeChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ProgressBar(Manager manager) + : base(manager) + { + Width = 128; + Height = 16; + MinimumHeight = 8; + MinimumWidth = 32; + Passive = true; + CanFocus = false; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + CheckLayer(Skin, "Control"); + CheckLayer(Skin, "Scale"); + + base.DrawControl(renderer, rect, gameTime); + + if (Value > 0 || mode == ProgressBarMode.Infinite) + { + SkinLayer p = Skin.Layers["Control"]; + SkinLayer l = Skin.Layers["Scale"]; + Rectangle r = new Rectangle(rect.Left + p.ContentMargins.Left, + rect.Top + p.ContentMargins.Top, + rect.Width - p.ContentMargins.Vertical, + rect.Height - p.ContentMargins.Horizontal); + + float perc = ((float)value / range) * 100; + int w = (int)((perc / 100) * r.Width); + Rectangle rx; + if (mode == ProgressBarMode.Default) + { + if (w < l.SizingMargins.Vertical) w = l.SizingMargins.Vertical; + rx = new Rectangle(r.Left, r.Top, w, r.Height); + } + else + { + int s = r.Left + w; + if (s > r.Left + p.ContentMargins.Left + r.Width - (r.Width / 4)) s = r.Left + p.ContentMargins.Left + r.Width - (r.Width / 4); + rx = new Rectangle(s, r.Top, (r.Width / 4), r.Height); + } + + renderer.DrawLayer(this, l, rx); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + + if (mode == ProgressBarMode.Infinite && Enabled && Visible) + { + time += gameTime.ElapsedGameTime.TotalMilliseconds; + if (time >= 33f) + { + value += sign * (int)Math.Ceiling(time / 20f); + if (value >= Range - (Range / 4)) + { + value = Range - (Range / 4); + sign = -1; + } + else if (value <= 0) + { + value = 0; + sign = 1; + } + time = 0; + Invalidate(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnValueChanged(EventArgs e) + { + if (ValueChanged != null) ValueChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnRangeChanged(EventArgs e) + { + if (RangeChanged != null) RangeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnModeChanged(EventArgs e) + { + if (ModeChanged != null) ModeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Properties/AssemblyInfo.cs b/Neoforce/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4c52ffd --- /dev/null +++ b/Neoforce/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MonoGame.Controls")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MonoGame.Controls")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("581cd203-a917-4a56-87f8-79ef3cbe4f5e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Neoforce/RadioButton.cs b/Neoforce/RadioButton.cs new file mode 100644 index 0000000..c15d47a --- /dev/null +++ b/Neoforce/RadioButton.cs @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: RadioButton.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using System.Collections.Generic; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + public enum RadioButtonMode + { + Auto, + Manual + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + public class RadioButton: CheckBox + { + + #region //// Consts //////////// + + //////////////////////////////////////////////////////////////////////////// + private const string skRadioButton = "RadioButton"; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private RadioButtonMode mode = RadioButtonMode.Auto; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public RadioButtonMode Mode + { + get { return mode; } + set { mode = value; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public RadioButton(Manager manager): base(manager) + { + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls[skRadioButton]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnClick(EventArgs e) + { + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs(); + + if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None) + { + if (mode == RadioButtonMode.Auto) + { + if (Parent != null) + { + ControlsList lst = Parent.Controls as ControlsList; + for (int i = 0; i < lst.Count; i++) + { + if (lst[i] is RadioButton) + { + (lst[i] as RadioButton).Checked = false; + } + } + } + else if (Parent == null && Manager != null) + { + ControlsList lst = Manager.Controls as ControlsList; + + for (int i = 0; i < lst.Count; i++) + { + if (lst[i] is RadioButton) + { + (lst[i] as RadioButton).Checked = false; + } + } + } + } + } + base.OnClick(e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Renderer.cs b/Neoforce/Renderer.cs new file mode 100644 index 0000000..8da72bf --- /dev/null +++ b/Neoforce/Renderer.cs @@ -0,0 +1,767 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Renderer.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + public enum BlendingMode + { + Default, + None, + } + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + public class DeviceStates + { + public readonly BlendState BlendState; + public readonly RasterizerState RasterizerState; + public readonly DepthStencilState DepthStencilState; + public readonly SamplerState SamplerState; + + public DeviceStates() + { + BlendState = new BlendState(); + BlendState.AlphaBlendFunction = BlendState.AlphaBlend.AlphaBlendFunction; + BlendState.AlphaDestinationBlend = BlendState.AlphaBlend.AlphaDestinationBlend; + BlendState.AlphaSourceBlend = BlendState.AlphaBlend.AlphaSourceBlend; + BlendState.BlendFactor = BlendState.AlphaBlend.BlendFactor; + BlendState.ColorBlendFunction = BlendState.AlphaBlend.ColorBlendFunction; + BlendState.ColorDestinationBlend = BlendState.AlphaBlend.ColorDestinationBlend; + BlendState.ColorSourceBlend = BlendState.AlphaBlend.ColorSourceBlend; + BlendState.ColorWriteChannels = BlendState.AlphaBlend.ColorWriteChannels; + BlendState.ColorWriteChannels1 = BlendState.AlphaBlend.ColorWriteChannels1; + BlendState.ColorWriteChannels2 = BlendState.AlphaBlend.ColorWriteChannels2; + BlendState.ColorWriteChannels3 = BlendState.AlphaBlend.ColorWriteChannels3; + BlendState.MultiSampleMask = BlendState.AlphaBlend.MultiSampleMask; + + RasterizerState = new RasterizerState(); + RasterizerState.CullMode = RasterizerState.CullNone.CullMode; + RasterizerState.DepthBias = RasterizerState.CullNone.DepthBias; + RasterizerState.FillMode = RasterizerState.CullNone.FillMode; + RasterizerState.MultiSampleAntiAlias = RasterizerState.CullNone.MultiSampleAntiAlias; + RasterizerState.ScissorTestEnable = RasterizerState.CullNone.ScissorTestEnable; + RasterizerState.SlopeScaleDepthBias = RasterizerState.CullNone.SlopeScaleDepthBias; + + RasterizerState.ScissorTestEnable = true; + + SamplerState = new SamplerState(); + SamplerState.AddressU = SamplerState.AnisotropicClamp.AddressU; + SamplerState.AddressV = SamplerState.AnisotropicClamp.AddressV; + SamplerState.AddressW = SamplerState.AnisotropicClamp.AddressW; + SamplerState.Filter = SamplerState.AnisotropicClamp.Filter; + SamplerState.MaxAnisotropy = SamplerState.AnisotropicClamp.MaxAnisotropy; + SamplerState.MaxMipLevel = SamplerState.AnisotropicClamp.MaxMipLevel; + SamplerState.MipMapLevelOfDetailBias = SamplerState.AnisotropicClamp.MipMapLevelOfDetailBias; + + DepthStencilState = new DepthStencilState(); + DepthStencilState = DepthStencilState.None; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + public class Renderer : Component + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private SpriteBatch sb = null; + private DeviceStates states = new DeviceStates(); + private BlendingMode bmode = BlendingMode.Default; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual SpriteBatch SpriteBatch + { + get + { + return sb; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public Renderer(Manager manager) + : base(manager) + { + sb = new SpriteBatch(Manager.GraphicsDevice); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (sb != null) + { + sb.Dispose(); + sb = null; + } + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Begin(BlendingMode mode) + { + bmode = mode; + if (mode != BlendingMode.None) + { + sb.Begin(SpriteSortMode.Immediate, states.BlendState, states.SamplerState, states.DepthStencilState, states.RasterizerState); + } + else + { + sb.Begin(SpriteSortMode.Immediate, BlendState.Opaque, states.SamplerState, states.DepthStencilState, states.RasterizerState); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void End() + { + sb.End(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Draw(Texture2D texture, Rectangle destination, Color color) + { + if (destination.Width > 0 && destination.Height > 0) + { + sb.Draw(texture, destination, null, color, 0.0f, Vector2.Zero, SpriteEffects.None, Manager.GlobalDepth); + } + } + //////////////////////////////////////////////////////////////////////////// + + public virtual void DrawTileTexture(Texture2D texture, Rectangle destination, Color color) + { + if (destination.Width > 0 && destination.Height > 0) + { + End(); + + sb.Begin(SpriteSortMode.Texture, BlendState.AlphaBlend, SamplerState.LinearWrap, DepthStencilState.Default, RasterizerState.CullNone); + + sb.Draw(texture, new Vector2(destination.X,destination.Y), destination, color, 0, Vector2.Zero, 1, SpriteEffects.None, 0); + + End(); + Begin(bmode); + } + } + + //////////////////////////////////////////////////////////////////////////// + public virtual void Draw(Texture2D texture, Rectangle destination, Rectangle source, Color color) + { + if (source.Width > 0 && source.Height > 0 && destination.Width > 0 && destination.Height > 0) + { + sb.Draw(texture, destination, source, color, 0.0f, Vector2.Zero, SpriteEffects.None, Manager.GlobalDepth); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Draw(Texture2D texture, int left, int top, Color color) + { + sb.Draw(texture, new Vector2(left, top), null, color, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, Manager.GlobalDepth); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Draw(Texture2D texture, int left, int top, Rectangle source, Color color) + { + if (source.Width > 0 && source.Height > 0) + { + sb.Draw(texture, new Vector2(left, top), source, color, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, Manager.GlobalDepth); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(SpriteFont font, string text, int left, int top, Color color) + { + sb.DrawString(font, text, new Vector2(left, top), color, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, Manager.GlobalDepth); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(SpriteFont font, string text, Rectangle rect, Color color, Alignment alignment) + { + DrawString(font, text, rect, color, alignment, 0, 0, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(SpriteFont font, string text, Rectangle rect, Color color, Alignment alignment, bool ellipsis) + { + DrawString(font, text, rect, color, alignment, 0, 0, ellipsis); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect) + { + DrawString(control, layer, text, rect, true, 0, 0, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect, ControlState state) + { + DrawString(control, layer, text, rect, state, true, 0, 0, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect, bool margins) + { + DrawString(control, layer, text, rect, margins, 0, 0, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect, ControlState state, bool margins) + { + DrawString(control, layer, text, rect, state, margins, 0, 0, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect, bool margins, int ox, int oy) + { + DrawString(control, layer, text, rect, margins, ox, oy, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect, ControlState state, bool margins, int ox, int oy) + { + DrawString(control, layer, text, rect, state, margins, ox, oy, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect, bool margins, int ox, int oy, bool ellipsis) + { + DrawString(control, layer, text, rect, control.ControlState, margins, ox, oy, ellipsis); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(Control control, SkinLayer layer, string text, Rectangle rect, ControlState state, bool margins, int ox, int oy, bool ellipsis) + { + Color col = Color.White; + + if (layer.Text != null) + { + if (margins) + { + Margins m = layer.ContentMargins; + rect = new Rectangle(rect.Left + m.Left, rect.Top + m.Top, rect.Width - m.Horizontal, rect.Height - m.Vertical); + } + + if (state == ControlState.Hovered && (layer.States.Hovered.Index != -1)) + { + col = layer.Text.Colors.Hovered; + } + else if (state == ControlState.Pressed) + { + col = layer.Text.Colors.Pressed; + } + else if (state == ControlState.Focused || (control.Focused && state == ControlState.Hovered && layer.States.Hovered.Index == -1)) + { + col = layer.Text.Colors.Focused; + } + else if (state == ControlState.Disabled) + { + col = layer.Text.Colors.Disabled; + } + else + { + col = layer.Text.Colors.Enabled; + } + + if (text != null && text != "") + { + SkinText font = layer.Text; + if (control.TextColor != Control.UndefinedColor && control.ControlState != ControlState.Disabled) col = control.TextColor; + DrawString(font.Font.Resource, text, rect, col, font.Alignment, font.OffsetX + ox, font.OffsetY + oy, ellipsis); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawString(SpriteFont font, string text, Rectangle rect, Color color, Alignment alignment, int offsetX, int offsetY, bool ellipsis) + { + + if (ellipsis) + { + const string elli = "..."; + int size = (int)Math.Ceiling(font.MeasureString(text).X); + if (size > rect.Width) + { + int es = (int)Math.Ceiling(font.MeasureString(elli).X); + for (int i = text.Length - 1; i > 0; i--) + { + int c = 1; + if (char.IsWhiteSpace(text[i - 1])) + { + c = 2; + i--; + } + text = text.Remove(i, c); + size = (int)Math.Ceiling(font.MeasureString(text).X); + if (size + es <= rect.Width) + { + break; + } + } + text += elli; + } + } + + if (rect.Width > 0 && rect.Height > 0) + { + Vector2 pos = new Vector2(rect.Left, rect.Top); + Vector2 size = font.MeasureString(text); + + int x = 0; int y = 0; + + switch (alignment) + { + case Alignment.TopLeft: + break; + case Alignment.TopCenter: + x = GetTextCenter(rect.Width, size.X); + break; + case Alignment.TopRight: + x = rect.Width - (int)size.X; + break; + case Alignment.MiddleLeft: + y = GetTextCenter(rect.Height, size.Y); + break; + case Alignment.MiddleRight: + x = rect.Width - (int)size.X; + y = GetTextCenter(rect.Height, size.Y); + break; + case Alignment.BottomLeft: + y = rect.Height - (int)size.Y; + break; + case Alignment.BottomCenter: + x = GetTextCenter(rect.Width, size.X); + y = rect.Height - (int)size.Y; + break; + case Alignment.BottomRight: + x = rect.Width - (int)size.X; + y = rect.Height - (int)size.Y; + break; + + default: + x = GetTextCenter(rect.Width, size.X); + y = GetTextCenter(rect.Height, size.Y); + break; + } + + pos.X = (int)(pos.X + x); + pos.Y = (int)(pos.Y + y); + + DrawString(font, text, (int)pos.X + offsetX, (int)pos.Y + offsetY, color); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private static int GetTextCenter(float size1, float size2) + { + return (int)Math.Ceiling((size1 / 2) - (size2 / 2)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawLayer(SkinLayer layer, Rectangle rect, Color color, int index) + { + Size imageSize = new Size(layer.Image.Resource.Width, layer.Image.Resource.Height); + Size partSize = new Size(layer.Width, layer.Height); + + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.TopLeft), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.TopLeft, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.TopCenter), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.TopCenter, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.TopRight), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.TopRight, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.MiddleLeft), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.MiddleLeft, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.MiddleCenter), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.MiddleCenter, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.MiddleRight), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.MiddleRight, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.BottomLeft), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.BottomLeft, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.BottomCenter), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.BottomCenter, index), color); + Draw(layer.Image.Resource, GetDestinationArea(rect, layer.SizingMargins, Alignment.BottomRight), GetSourceArea(imageSize, partSize, layer.SizingMargins, Alignment.BottomRight, index), color); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private static Rectangle GetSourceArea(Size imageSize, Size partSize, Margins margins, Alignment alignment, int index) + { + Rectangle rect = new Rectangle(); + int xc = (int)((float)imageSize.Width / partSize.Width); + int yc = (int)((float)imageSize.Height / partSize.Height); + + int xm = (index) % xc; + int ym = (index) / xc; + + int adj = 1; + margins.Left += margins.Left > 0 ? adj : 0; + margins.Top += margins.Top > 0 ? adj : 0; + margins.Right += margins.Right > 0 ? adj : 0; + margins.Bottom += margins.Bottom > 0 ? adj : 0; + + margins = new Margins(margins.Left, margins.Top, margins.Right, margins.Bottom); + switch (alignment) + { + case Alignment.TopLeft: + { + rect = new Rectangle((0 + (xm * partSize.Width)), + (0 + (ym * partSize.Height)), + margins.Left, + margins.Top); + break; + } + case Alignment.TopCenter: + { + rect = new Rectangle((0 + (xm * partSize.Width)) + margins.Left, + (0 + (ym * partSize.Height)), + partSize.Width - margins.Left - margins.Right, + margins.Top); + break; + } + case Alignment.TopRight: + { + rect = new Rectangle((partSize.Width + (xm * partSize.Width)) - margins.Right, + (0 + (ym * partSize.Height)), + margins.Right, + margins.Top); + break; + } + case Alignment.MiddleLeft: + { + rect = new Rectangle((0 + (xm * partSize.Width)), + (0 + (ym * partSize.Height)) + margins.Top, + margins.Left, + partSize.Height - margins.Top - margins.Bottom); + break; + } + case Alignment.MiddleCenter: + { + rect = new Rectangle((0 + (xm * partSize.Width)) + margins.Left, + (0 + (ym * partSize.Height)) + margins.Top, + partSize.Width - margins.Left - margins.Right, + partSize.Height - margins.Top - margins.Bottom); + break; + } + case Alignment.MiddleRight: + { + rect = new Rectangle((partSize.Width + (xm * partSize.Width)) - margins.Right, + (0 + (ym * partSize.Height)) + margins.Top, + margins.Right, + partSize.Height - margins.Top - margins.Bottom); + break; + } + case Alignment.BottomLeft: + { + rect = new Rectangle((0 + (xm * partSize.Width)), + (partSize.Height + (ym * partSize.Height)) - margins.Bottom, + margins.Left, + margins.Bottom); + break; + } + case Alignment.BottomCenter: + { + rect = new Rectangle((0 + (xm * partSize.Width)) + margins.Left, + (partSize.Height + (ym * partSize.Height)) - margins.Bottom, + partSize.Width - margins.Left - margins.Right, + margins.Bottom); + break; + } + case Alignment.BottomRight: + { + rect = new Rectangle((partSize.Width + (xm * partSize.Width)) - margins.Right, + (partSize.Height + (ym * partSize.Height)) - margins.Bottom, + margins.Right, + margins.Bottom); + break; + } + } + + return rect; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public static Rectangle GetDestinationArea(Rectangle area, Margins margins, Alignment alignment) + { + Rectangle rect = new Rectangle(); + + int adj = 1; + margins.Left += margins.Left > 0 ? adj : 0; + margins.Top += margins.Top > 0 ? adj : 0; + margins.Right += margins.Right > 0 ? adj : 0; + margins.Bottom += margins.Bottom > 0 ? adj : 0; + + margins = new Margins(margins.Left, margins.Top, margins.Right, margins.Bottom); + + switch (alignment) + { + case Alignment.TopLeft: + { + rect = new Rectangle(area.Left + 0, + area.Top + 0, + margins.Left, + margins.Top); + break; + + } + case Alignment.TopCenter: + { + rect = new Rectangle(area.Left + margins.Left, + area.Top + 0, + area.Width - margins.Left - margins.Right, + margins.Top); + break; + + } + case Alignment.TopRight: + { + rect = new Rectangle(area.Left + area.Width - margins.Right, + area.Top + 0, + margins.Right, + margins.Top); + break; + + } + case Alignment.MiddleLeft: + { + rect = new Rectangle(area.Left + 0, + area.Top + margins.Top, + margins.Left, + area.Height - margins.Top - margins.Bottom); + break; + } + case Alignment.MiddleCenter: + { + rect = new Rectangle(area.Left + margins.Left, + area.Top + margins.Top, + area.Width - margins.Left - margins.Right, + area.Height - margins.Top - margins.Bottom); + break; + } + case Alignment.MiddleRight: + { + rect = new Rectangle(area.Left + area.Width - margins.Right, + area.Top + margins.Top, + margins.Right, + area.Height - margins.Top - margins.Bottom); + break; + } + case Alignment.BottomLeft: + { + rect = new Rectangle(area.Left + 0, + area.Top + area.Height - margins.Bottom, + margins.Left, + margins.Bottom); + break; + } + case Alignment.BottomCenter: + { + rect = new Rectangle(area.Left + margins.Left, + area.Top + area.Height - margins.Bottom, + area.Width - margins.Left - margins.Right, + margins.Bottom); + break; + } + case Alignment.BottomRight: + { + rect = new Rectangle(area.Left + area.Width - margins.Right, + area.Top + area.Height - margins.Bottom, + margins.Right, + margins.Bottom); + break; + } + } + + return rect; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public void DrawGlyph(Glyph glyph, Rectangle rect) + { + Size imageSize = new Size(glyph.Image.Width, glyph.Image.Height); + + if (!glyph.SourceRect.IsEmpty) + { + imageSize = new Size(glyph.SourceRect.Width, glyph.SourceRect.Height); + } + + if (glyph.SizeMode == SizeMode.Centered) + { + rect = new Rectangle((rect.X + (rect.Width - imageSize.Width) / 2) + glyph.Offset.X, + (rect.Y + (rect.Height - imageSize.Height) / 2) + glyph.Offset.Y, + imageSize.Width, + imageSize.Height); + } + else if (glyph.SizeMode == SizeMode.Normal) + { + rect = new Rectangle(rect.X + glyph.Offset.X, rect.Y + glyph.Offset.Y, imageSize.Width, imageSize.Height); + } + else if (glyph.SizeMode == SizeMode.Auto) + { + rect = new Rectangle(rect.X + glyph.Offset.X, rect.Y + glyph.Offset.Y, imageSize.Width, imageSize.Height); + } + + if (glyph.SourceRect.IsEmpty) + { + Draw(glyph.Image, rect, glyph.Color); + } + else + { + Draw(glyph.Image, rect, glyph.SourceRect, glyph.Color); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawLayer(Control control, SkinLayer layer, Rectangle rect) + { + DrawLayer(control, layer, rect, control.ControlState); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void DrawLayer(Control control, SkinLayer layer, Rectangle rect, ControlState state) + { + Color c = Color.White; + Color oc = Color.White; + int i = 0; + int oi = -1; + SkinLayer l = layer; + + if (state == ControlState.Hovered && (layer.States.Hovered.Index != -1)) + { + c = l.States.Hovered.Color; + i = l.States.Hovered.Index; + + if (l.States.Hovered.Overlay) + { + oc = l.Overlays.Hovered.Color; + oi = l.Overlays.Hovered.Index; + } + } + else if (state == ControlState.Focused || (control.Focused && state == ControlState.Hovered && layer.States.Hovered.Index == -1)) + { + c = l.States.Focused.Color; + i = l.States.Focused.Index; + + if (l.States.Focused.Overlay) + { + oc = l.Overlays.Focused.Color; + oi = l.Overlays.Focused.Index; + } + } + else if (state == ControlState.Pressed) + { + c = l.States.Pressed.Color; + i = l.States.Pressed.Index; + + if (l.States.Pressed.Overlay) + { + oc = l.Overlays.Pressed.Color; + oi = l.Overlays.Pressed.Index; + } + } + else if (state == ControlState.Disabled) + { + c = l.States.Disabled.Color; + i = l.States.Disabled.Index; + + if (l.States.Disabled.Overlay) + { + oc = l.Overlays.Disabled.Color; + oi = l.Overlays.Disabled.Index; + } + } + else + { + c = l.States.Enabled.Color; + i = l.States.Enabled.Index; + + if (l.States.Enabled.Overlay) + { + oc = l.Overlays.Enabled.Color; + oi = l.Overlays.Enabled.Index; + } + } + + if (control.Color != Control.UndefinedColor) c = control.Color * (control.Color.A / 255f); + DrawLayer(l, rect, c, i); + + if (oi != -1) + { + DrawLayer(l, rect, oc, oi); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } +} \ No newline at end of file diff --git a/Neoforce/ScrollBar.cs b/Neoforce/ScrollBar.cs new file mode 100644 index 0000000..922757a --- /dev/null +++ b/Neoforce/ScrollBar.cs @@ -0,0 +1,493 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ScrollBar.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class ScrollBar: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private int range = 100; + private int value = 0; + private int pageSize = 50; + private int stepSize = 1; + private Orientation orientation = Orientation.Vertical; + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private Button btnMinus = null; + private Button btnPlus = null; + private Button btnSlider = null; + + private string strButton = "ScrollBar.ButtonVert"; + private string strRail = "ScrollBar.RailVert"; + private string strSlider = "ScrollBar.SliderVert"; + private string strGlyph = "ScrollBar.GlyphVert"; + private string strMinus = "ScrollBar.ArrowUp"; + private string strPlus = "ScrollBar.ArrowDown"; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int Value + { + get { return this.value; } + set + { + if (this.value != value) + { + this.value = value; + if (this.value < 0) this.value = 0; + if (this.value > range - pageSize) this.value = range - pageSize; + Invalidate(); + if (!Suspended) OnValueChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int Range + { + get { return range; } + set + { + if (range != value) + { + range = value; + if (pageSize > range) pageSize = range; + RecalcParams(); + if (!Suspended) OnRangeChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int PageSize + { + get { return pageSize; } + set + { + if (pageSize != value) + { + pageSize = value; + if (pageSize > range) pageSize = range; + RecalcParams(); + if (!Suspended) OnPageSizeChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int StepSize + { + get { return stepSize; } + set + { + if (stepSize != value) + { + stepSize = value; + if (!Suspended) OnStepSizeChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler ValueChanged; + public event EventHandler RangeChanged; + public event EventHandler StepSizeChanged; + public event EventHandler PageSizeChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ScrollBar(Manager manager, Orientation orientation): base(manager) + { + this.orientation = orientation; + CanFocus = false; + + + if (orientation == Orientation.Horizontal) + { + strButton = "ScrollBar.ButtonHorz"; + strRail = "ScrollBar.RailHorz"; + strSlider = "ScrollBar.SliderHorz"; + strGlyph = "ScrollBar.GlyphHorz"; + strMinus = "ScrollBar.ArrowLeft"; + strPlus = "ScrollBar.ArrowRight"; + + MinimumHeight = 16; + MinimumWidth = 46; + Width = 64; + Height = 16; + } + else + { + strButton = "ScrollBar.ButtonVert"; + strRail = "ScrollBar.RailVert"; + strSlider = "ScrollBar.SliderVert"; + strGlyph = "ScrollBar.GlyphVert"; + strMinus = "ScrollBar.ArrowUp"; + strPlus = "ScrollBar.ArrowDown"; + + MinimumHeight = 46; + MinimumWidth = 16; + Width = 16; + Height = 64; + } + + btnMinus = new Button(Manager); + btnMinus.Init(); + btnMinus.Text = ""; + btnMinus.MousePress += new MouseEventHandler(ArrowPress); + btnMinus.CanFocus = false; + + btnSlider = new Button(Manager); + btnSlider.Init(); + btnSlider.Text = ""; + btnSlider.CanFocus = false; + btnSlider.MinimumHeight = 16; + btnSlider.MinimumWidth = 16; + + btnPlus = new Button(Manager); + btnPlus.Init(); + btnPlus.Text = ""; + btnPlus.MousePress += new MouseEventHandler(ArrowPress); + btnPlus.CanFocus = false; + + btnSlider.Move += new MoveEventHandler(btnSlider_Move); + + Add(btnMinus); + Add(btnSlider); + Add(btnPlus); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + + public void ScrollUp() + { + Value -= stepSize; + if (Value < 0) Value = 0; + } + + public void ScrollDown() + { + Value += stepSize; + if (Value > range - pageSize) Value = range - pageSize - 1; + } + + public void ScrollUp(bool alot) + { + if (alot) + { + Value -= pageSize; + if (Value < 0) Value = 0; + } + else + ScrollUp(); + } + + public void ScrollDown(bool alot) + { + if (alot) + { + Value += pageSize; + if (Value > range - pageSize) Value = range - pageSize - 1; + } + else + ScrollDown(); + } + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + SkinControl sc = new SkinControl(btnPlus.Skin); + sc.Layers["Control"] = new SkinLayer(Skin.Layers[strButton]); + sc.Layers[strButton].Name = "Control"; + btnPlus.Skin = btnMinus.Skin = sc; + + SkinControl ss = new SkinControl(btnSlider.Skin); + ss.Layers["Control"] = new SkinLayer(Skin.Layers[strSlider]); + ss.Layers[strSlider].Name = "Control"; + btnSlider.Skin = ss; + + btnMinus.Glyph = new Glyph(Skin.Layers[strMinus].Image.Resource); + btnMinus.Glyph.SizeMode = SizeMode.Centered; + btnMinus.Glyph.Color = Manager.Skin.Controls["Button"].Layers["Control"].Text.Colors.Enabled; + + btnPlus.Glyph = new Glyph(Skin.Layers[strPlus].Image.Resource); + btnPlus.Glyph.SizeMode = SizeMode.Centered; + btnPlus.Glyph.Color = Manager.Skin.Controls["Button"].Layers["Control"].Text.Colors.Enabled; + + btnSlider.Glyph = new Glyph(Skin.Layers[strGlyph].Image.Resource); + btnSlider.Glyph.SizeMode = SizeMode.Centered; + + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["ScrollBar"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + RecalcParams(); + + SkinLayer bg = Skin.Layers[strRail]; + renderer.DrawLayer(bg, rect, Color.White, bg.States.Enabled.Index); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void ArrowPress(object sender, MouseEventArgs e) + { + if (e.Button == MouseButton.Left) + { + if (sender == btnMinus) + { + ScrollUp(); + } + else if (sender == btnPlus) + { + ScrollDown(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + RecalcParams(); + if (Value + PageSize > Range) Value = Range - PageSize; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void RecalcParams() + { + if (btnMinus != null && btnPlus != null && btnSlider != null) + { + if (orientation == Orientation.Horizontal) + { + btnMinus.Width = Height; + btnMinus.Height = Height; + + btnPlus.Width = Height; + btnPlus.Height = Height; + btnPlus.Left = Width - Height; + btnPlus.Top = 0; + + btnSlider.Movable = true; + int size = btnMinus.Width + Skin.Layers[strSlider].OffsetX; + + btnSlider.MinimumWidth = Height; + int w = (Width - 2 * size); + btnSlider.Width = (int)Math.Ceiling((pageSize * w) / (float)range); + btnSlider.Height = Height; + + + float px = (float)(Range - PageSize) / (float)(w - btnSlider.Width); + int pos = (int)(Math.Ceiling(Value / (float)px)); + btnSlider.SetPosition(size + pos, 0); + if (btnSlider.Left < size) btnSlider.SetPosition(size, 0); + if (btnSlider.Left + btnSlider.Width + size > Width) btnSlider.SetPosition(Width - size - btnSlider.Width, 0); + } + else + { + btnMinus.Width = Width; + btnMinus.Height = Width; + + btnPlus.Width = Width; + btnPlus.Height = Width; + btnPlus.Top = Height - Width; + + btnSlider.Movable = true; + int size = btnMinus.Height + Skin.Layers[strSlider].OffsetY; + + btnSlider.MinimumHeight = Width; + int h = (Height - 2 * size); + btnSlider.Height = (int)Math.Ceiling((pageSize * h) / (float)range); + btnSlider.Width = Width; + + float px = (float)(Range - PageSize) / (float)(h - btnSlider.Height); + int pos = (int)(Math.Ceiling(Value / (float)px)); + btnSlider.SetPosition(0, size + pos); + if (btnSlider.Top < size) btnSlider.SetPosition(0, size); + if (btnSlider.Top + btnSlider.Height + size > Height) btnSlider.SetPosition(0, Height - size - btnSlider.Height); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void btnSlider_Move(object sender, MoveEventArgs e) + { + if (orientation == Orientation.Horizontal) + { + int size = btnMinus.Width + Skin.Layers[strSlider].OffsetX; + btnSlider.SetPosition(e.Left, 0); + if (btnSlider.Left < size) btnSlider.SetPosition(size, 0); + if (btnSlider.Left + btnSlider.Width + size > Width) btnSlider.SetPosition(Width - size - btnSlider.Width, 0); + } + else + { + int size = btnMinus.Height + Skin.Layers[strSlider].OffsetY; + btnSlider.SetPosition(0, e.Top); + if (btnSlider.Top < size) btnSlider.SetPosition(0, size); + if (btnSlider.Top + btnSlider.Height + size > Height) btnSlider.SetPosition(0, Height - size - btnSlider.Height); + } + + if (orientation == Orientation.Horizontal) + { + int size = btnMinus.Width + Skin.Layers[strSlider].OffsetX; + int w = (Width - 2 * size) - btnSlider.Width; + float px = (float)(Range - PageSize) / (float)w; + Value = (int)(Math.Ceiling((btnSlider.Left - size) * px)); + } + else + { + int size = btnMinus.Height + Skin.Layers[strSlider].OffsetY; + int h = (Height - 2 * size) - btnSlider.Height; + float px = (float)(Range - PageSize) / (float)h; + Value = (int)(Math.Ceiling((btnSlider.Top - size) * px)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseUp(MouseEventArgs e) + { + btnSlider.Passive = false; + base.OnMouseUp(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + btnSlider.Passive = true; + + if (e.Button == MouseButton.Left) + { + if (orientation == Orientation.Horizontal) + { + int pos = e.Position.X; + + if (pos < btnSlider.Left) + { + ScrollUp(true); + } + else if (pos >= btnSlider.Left + btnSlider.Width) + { + ScrollDown(true); + } + } + else + { + int pos = e.Position.Y; + + if (pos < btnSlider.Top) + { + ScrollUp(true); + } + else if (pos >= btnSlider.Top + btnSlider.Height) + { + ScrollDown(true); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnValueChanged(EventArgs e) + { + if (ValueChanged != null) ValueChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnRangeChanged(EventArgs e) + { + if (RangeChanged != null) RangeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnPageSizeChanged(EventArgs e) + { + if (PageSizeChanged != null) PageSizeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnStepSizeChanged(EventArgs e) + { + if (StepSizeChanged != null) StepSizeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Sidebar.cs b/Neoforce/Sidebar.cs new file mode 100644 index 0000000..c8fcae6 --- /dev/null +++ b/Neoforce/Sidebar.cs @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: SideBar.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + + public class SideBar: Panel + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public SideBar(Manager manager): base(manager) + { + // CanFocus = true; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["SideBar"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/SidebarPanel.cs b/Neoforce/SidebarPanel.cs new file mode 100644 index 0000000..44ed8c9 --- /dev/null +++ b/Neoforce/SidebarPanel.cs @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: SideBarPanel.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class SideBarPanel: Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public SideBarPanel(Manager manager): base(manager) + { + CanFocus = false; + Passive = true; + Width = 64; + Height = 64; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Skin.cs b/Neoforce/Skin.cs new file mode 100644 index 0000000..92ec378 --- /dev/null +++ b/Neoforce/Skin.cs @@ -0,0 +1,1259 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Skin.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Structs /////////// + + //////////////////////////////////////////////////////////////////////////// + public struct SkinStates + { + public T Enabled; + public T Hovered; + public T Pressed; + public T Focused; + public T Disabled; + + public SkinStates(T enabled, T hovered, T pressed, T focused, T disabled) + { + Enabled = enabled; + Hovered = hovered; + Pressed = pressed; + Focused = focused; + Disabled = disabled; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public struct LayerStates + { + public int Index; + public Color Color; + public bool Overlay; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public struct LayerOverlays + { + public int Index; + public Color Color; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public struct SkinInfo + { + public string Name; + public string Description; + public string Author; + public string Version; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinList : List + { + #region //// Indexers ////////// + + //////////////////////////////////////////////////////////////////////////// + public T this[string index] + { + get + { + for (int i = 0; i < this.Count; i++) + { + SkinBase s = (SkinBase)(object)this[i]; + if (s.Name.ToLower() == index.ToLower()) + { + return this[i]; + } + } + return default(T); + } + + set + { + for (int i = 0; i < this.Count; i++) + { + SkinBase s = (SkinBase)(object)this[i]; + if (s.Name.ToLower() == index.ToLower()) + { + this[i] = value; + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + + //////////////////////////////////////////////////////////////////////////// + public SkinList() + : base() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinList(SkinList source) + : base() + { + for (int i = 0; i < source.Count; i++) + { + Type[] t = new Type[1]; + t[0] = typeof(T); + + object[] p = new object[1]; + p[0] = source[i]; + + this.Add((T)t[0].GetConstructor(t).Invoke(p)); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinBase + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public string Name; + public bool Archive; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinBase() + : base() + { + Archive = false; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinBase(SkinBase source) + : base() + { + if (source != null) + { + this.Name = source.Name; + this.Archive = source.Archive; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinLayer : SkinBase + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinImage Image = new SkinImage(); + public int Width; + public int Height; + public int OffsetX; + public int OffsetY; + public Alignment Alignment; + public Margins SizingMargins; + public Margins ContentMargins; + public SkinStates States; + public SkinStates Overlays; + public SkinText Text = new SkinText(); + public SkinList Attributes = new SkinList(); + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinLayer() + : base() + { + States.Enabled.Color = Color.White; + States.Pressed.Color = Color.White; + States.Focused.Color = Color.White; + States.Hovered.Color = Color.White; + States.Disabled.Color = Color.White; + + Overlays.Enabled.Color = Color.White; + Overlays.Pressed.Color = Color.White; + Overlays.Focused.Color = Color.White; + Overlays.Hovered.Color = Color.White; + Overlays.Disabled.Color = Color.White; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinLayer(SkinLayer source) + : base(source) + { + if (source != null) + { + this.Image = new SkinImage(source.Image); + this.Width = source.Width; + this.Height = source.Height; + this.OffsetX = source.OffsetX; + this.OffsetY = source.OffsetY; + this.Alignment = source.Alignment; + this.SizingMargins = source.SizingMargins; + this.ContentMargins = source.ContentMargins; + this.States = source.States; + this.Overlays = source.Overlays; + this.Text = new SkinText(source.Text); + this.Attributes = new SkinList(source.Attributes); + } + else + { + throw new Exception("Parameter for SkinLayer copy constructor cannot be null."); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinText : SkinBase + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinFont Font; + public int OffsetX; + public int OffsetY; + public Alignment Alignment; + public SkinStates Colors; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinText() + : base() + { + Colors.Enabled = Color.White; + Colors.Pressed = Color.White; + Colors.Focused = Color.White; + Colors.Hovered = Color.White; + Colors.Disabled = Color.White; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinText(SkinText source) + : base(source) + { + if (source != null) + { + this.Font = new SkinFont(source.Font); + this.OffsetX = source.OffsetX; + this.OffsetY = source.OffsetY; + this.Alignment = source.Alignment; + this.Colors = source.Colors; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinFont : SkinBase + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public SpriteFont Resource = null; + public string Asset = null; + public string Addon = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public int Height + { + get + { + if (Resource != null) + { + return (int)Resource.MeasureString("AaYy").Y; + } + return 0; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinFont() + : base() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinFont(SkinFont source) + : base(source) + { + if (source != null) + { + this.Resource = source.Resource; + this.Asset = source.Asset; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinImage : SkinBase + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public Texture2D Resource = null; + public string Asset = null; + public string Addon = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinImage() + : base() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinImage(SkinImage source) + : base(source) + { + this.Resource = source.Resource; + this.Asset = source.Asset; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinCursor : SkinBase + { + #region //// Fields //////////// + + + public Cursor Resource = null; + + public string Asset = null; + public string Addon = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinCursor() + : base() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinCursor(SkinCursor source) + : base(source) + { + this.Resource = source.Resource; + + this.Asset = source.Asset; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinControl : SkinBase + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public string Inherits = null; + public Size DefaultSize; + public int ResizerSize; + public Size MinimumSize; + public Margins OriginMargins; + public Margins ClientMargins; + public SkinList Layers = new SkinList(); + public SkinList Attributes = new SkinList(); + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinControl() + : base() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinControl(SkinControl source) + : base(source) + { + this.Inherits = source.Inherits; + this.DefaultSize = source.DefaultSize; + this.MinimumSize = source.MinimumSize; + this.OriginMargins = source.OriginMargins; + this.ClientMargins = source.ClientMargins; + this.ResizerSize = source.ResizerSize; + this.Layers = new SkinList(source.Layers); + this.Attributes = new SkinList(source.Attributes); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class SkinAttribute : SkinBase + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + public string Value; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public SkinAttribute() + : base() + { + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public SkinAttribute(SkinAttribute source) + : base(source) + { + this.Value = source.Value; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class Skin : Component + { + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + SkinXmlDocument doc = null; + private string name = null; + private Version version = null; + private SkinInfo info; + private SkinList controls = null; + private SkinList fonts = null; + private SkinList cursors = null; + private SkinList images = null; + private SkinList attributes = null; + private ArchiveManager content = null; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual string Name { get { return name; } } + public virtual Version Version { get { return version; } } + public virtual SkinInfo Info { get { return info; } } + public virtual SkinList Controls { get { return controls; } } + public virtual SkinList Fonts { get { return fonts; } } + public virtual SkinList Cursors { get { return cursors; } } + public virtual SkinList Images { get { return images; } } + public virtual SkinList Attributes { get { return attributes; } } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public Skin(Manager manager, string name) + : base(manager) + { + this.name = name; + content = new ArchiveManager(Manager.Game.Services, GetArchiveLocation(name + Manager.SkinExtension)); + content.RootDirectory = GetFolder(); + doc = new SkinXmlDocument(); + controls = new SkinList(); + fonts = new SkinList(); + images = new SkinList(); + cursors = new SkinList(); + attributes = new SkinList(); + + LoadSkin(null, content.UseArchive); + + string folder = GetAddonsFolder(); + if (folder == "") + { + content.UseArchive = true; + folder = "Addons\\"; + } + else + { + content.UseArchive = false; + } + + string[] addons = content.GetDirectories(folder); + + if (addons != null && addons.Length > 0) + { + for (int i = 0; i < addons.Length; i++) + { + DirectoryInfo d = new DirectoryInfo(GetAddonsFolder() + addons[i]); + if (!((d.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) || content.UseArchive) + { + LoadSkin(addons[i].Replace("\\", ""), content.UseArchive); + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Destructors /////// + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (content != null) + { + content.Unload(); + content.Dispose(); + content = null; + } + } + + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + private string GetArchiveLocation(string name) + { + string path = Path.GetFullPath(Manager.SkinDirectory) + Path.GetFileNameWithoutExtension(name) + "\\"; + if (!Directory.Exists(path) || !File.Exists(path + "Skin.xnb")) + { + path = Path.GetFullPath(Manager.SkinDirectory) + name; + return path; + } + + return null; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string GetFolder() + { + string path = Path.GetFullPath(Manager.SkinDirectory) + name + "\\"; + if (!Directory.Exists(path) || !File.Exists(path + "Skin.xnb")) + { + path = ""; + } + + return path; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string GetAddonsFolder() + { + string path = Path.GetFullPath(Manager.SkinDirectory) + name + "\\Addons\\"; + if (!Directory.Exists(path)) + { + path = Path.GetFullPath(".\\Content\\Skins\\") + name + "\\Addons\\"; + if (!Directory.Exists(path)) + { + path = Path.GetFullPath(".\\Skins\\") + name + "\\Addons\\"; + } + } + + return path; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string GetFolder(string type) + { + return GetFolder() + type + "\\"; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string GetAsset(string type, string asset, string addon) + { + string ret = GetFolder(type) + asset; + if (addon != null && addon != "") + { + ret = GetAddonsFolder() + addon + "\\" + type + "\\" + asset; + } + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + for (int i = 0; i < fonts.Count; i++) + { + content.UseArchive = fonts[i].Archive; + string asset = GetAsset("Fonts", fonts[i].Asset, fonts[i].Addon); + asset = content.UseArchive ? asset : Path.GetFullPath(asset); + (fonts[i].Resource) = content.Load(asset); + } + +#if (!XBOX && !XBOX_FAKE) + for (int i = 0; i < cursors.Count; i++) + { + content.UseArchive = cursors[i].Archive; + string asset = GetAsset("Cursors", cursors[i].Asset, cursors[i].Addon); + asset = content.UseArchive ? asset : Path.GetFullPath(asset); + cursors[i].Resource = content.Load(asset); + } +#endif + + for (int i = 0; i < images.Count; i++) + { + content.UseArchive = images[i].Archive; + string asset = GetAsset("Images", images[i].Asset, images[i].Addon); + asset = content.UseArchive ? asset : Path.GetFullPath(asset); + images[i].Resource = content.Load(asset); + } + + for (int i = 0; i < controls.Count; i++) + { + for (int j = 0; j < controls[i].Layers.Count; j++) + { + if (controls[i].Layers[j].Image.Name != null) + { + controls[i].Layers[j].Image = images[controls[i].Layers[j].Image.Name]; + } + else + { + controls[i].Layers[j].Image = images[0]; + } + + if (controls[i].Layers[j].Text.Name != null) + { + controls[i].Layers[j].Text.Font = fonts[controls[i].Layers[j].Text.Name]; + } + else + { + controls[i].Layers[j].Text.Font = fonts[0]; + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string ReadAttribute(XmlElement element, string attrib, string defval, bool needed) + { + if (element != null && element.HasAttribute(attrib)) + { + return element.Attributes[attrib].Value; + } + else if (needed) + { + throw new Exception("Missing required attribute \"" + attrib + "\" in the skin file."); + } + return defval; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ReadAttribute(ref string retval, bool inherited, XmlElement element, string attrib, string defval, bool needed) + { + if (element != null && element.HasAttribute(attrib)) + { + retval = element.Attributes[attrib].Value; + } + else if (inherited) + { + } + else if (needed) + { + throw new Exception("Missing required attribute \"" + attrib + "\" in the skin file."); + } + else + { + retval = defval; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int ReadAttributeInt(XmlElement element, string attrib, int defval, bool needed) + { + return int.Parse(ReadAttribute(element, attrib, defval.ToString(), needed)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ReadAttributeInt(ref int retval, bool inherited, XmlElement element, string attrib, int defval, bool needed) + { + string tmp = retval.ToString(); + ReadAttribute(ref tmp, inherited, element, attrib, defval.ToString(), needed); + retval = int.Parse(tmp); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private bool ReadAttributeBool(XmlElement element, string attrib, bool defval, bool needed) + { + return bool.Parse(ReadAttribute(element, attrib, defval.ToString(), needed)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ReadAttributeBool(ref bool retval, bool inherited, XmlElement element, string attrib, bool defval, bool needed) + { + string tmp = retval.ToString(); + ReadAttribute(ref tmp, inherited, element, attrib, defval.ToString(), needed); + retval = bool.Parse(tmp); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private byte ReadAttributeByte(XmlElement element, string attrib, byte defval, bool needed) + { + return byte.Parse(ReadAttribute(element, attrib, defval.ToString(), needed)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ReadAttributeByte(ref byte retval, bool inherited, XmlElement element, string attrib, byte defval, bool needed) + { + string tmp = retval.ToString(); + ReadAttribute(ref tmp, inherited, element, attrib, defval.ToString(), needed); + retval = byte.Parse(tmp); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string ColorToString(Color c) + { + return string.Format("{0};{1};{2};{3}", c.R, c.G, c.B, c.A); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ReadAttributeColor(ref Color retval, bool inherited, XmlElement element, string attrib, Color defval, bool needed) + { + string tmp = ColorToString(retval); + ReadAttribute(ref tmp, inherited, element, attrib, ColorToString(defval), needed); + retval = Utilities.ParseColor(tmp); + } + //////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////// + private void LoadSkin(string addon, bool archive) + { + try + { + bool isaddon = addon != null && addon != ""; + string file = GetFolder(); + if (isaddon) + { + file = GetAddonsFolder() + addon + "\\"; + } + file += "Skin"; + + file = archive ? file : Path.GetFullPath(file); + doc = content.Load(file); + + XmlElement e = doc["Skin"]; + if (e != null) + { + string xname = ReadAttribute(e, "Name", null, true); + if (!isaddon) + { + if (name.ToLower() != xname.ToLower()) + { + throw new Exception("Skin name defined in the skin file doesn't match requested skin."); + } + else + { + name = xname; + } + } + else + { + if (addon.ToLower() != xname.ToLower()) + { + throw new Exception("Skin name defined in the skin file doesn't match addon name."); + } + } + + Version xversion = null; + try + { + xversion = new Version(ReadAttribute(e, "Version", "0.0.0.0", false)); + } + catch (Exception x) + { + throw new Exception("Unable to resolve skin file version. " + x.Message); + } + + if (xversion != Manager._SkinVersion) + { + throw new Exception("This version of Neoforce Controls can only read skin files in version of " + Manager._SkinVersion.ToString() + "."); + } + else if (!isaddon) + { + version = xversion; + } + + if (!isaddon) + { + XmlElement ei = e["Info"]; + if (ei != null) + { + if (ei["Name"] != null) info.Name = ei["Name"].InnerText; + if (ei["Description"] != null) info.Description = ei["Description"].InnerText; + if (ei["Author"] != null) info.Author = ei["Author"].InnerText; + if (ei["Version"] != null) info.Version = ei["Version"].InnerText; + } + } + + LoadImages(addon, archive); + LoadFonts(addon, archive); + LoadCursors(addon, archive); + LoadSkinAttributes(); + LoadControls(); + } + } + catch (Exception x) + { + throw new Exception("Unable to load skin file. " + x.Message); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadSkinAttributes() + { + if (doc["Skin"]["Attributes"] == null) return; + + XmlNodeList l = doc["Skin"]["Attributes"].GetElementsByTagName("Attribute"); + + if (l != null && l.Count > 0) + { + foreach (XmlElement e in l) + { + SkinAttribute sa = new SkinAttribute(); + sa.Name = ReadAttribute(e, "Name", null, true); + sa.Value = ReadAttribute(e, "Value", null, true); + attributes.Add(sa); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadControls() + { + if (doc["Skin"]["Controls"] == null) return; + + + XmlNodeList l = doc["Skin"]["Controls"].GetElementsByTagName("Control"); + + if (l != null && l.Count > 0) + { + foreach (XmlElement e in l) + { + SkinControl sc = null; + string parent = ReadAttribute(e, "Inherits", null, false); + bool inh = false; + + if (parent != null) + { + sc = new SkinControl(controls[parent]); + sc.Inherits = parent; + inh = true; + } + else + { + sc = new SkinControl(); + } + + ReadAttribute(ref sc.Name, inh, e, "Name", null, true); + + ReadAttributeInt(ref sc.DefaultSize.Width, inh, e["DefaultSize"], "Width", 0, false); + ReadAttributeInt(ref sc.DefaultSize.Height, inh, e["DefaultSize"], "Height", 0, false); + + ReadAttributeInt(ref sc.MinimumSize.Width, inh, e["MinimumSize"], "Width", 0, false); + ReadAttributeInt(ref sc.MinimumSize.Height, inh, e["MinimumSize"], "Height", 0, false); + + ReadAttributeInt(ref sc.OriginMargins.Left, inh, e["OriginMargins"], "Left", 0, false); + ReadAttributeInt(ref sc.OriginMargins.Top, inh, e["OriginMargins"], "Top", 0, false); + ReadAttributeInt(ref sc.OriginMargins.Right, inh, e["OriginMargins"], "Right", 0, false); + ReadAttributeInt(ref sc.OriginMargins.Bottom, inh, e["OriginMargins"], "Bottom", 0, false); + + ReadAttributeInt(ref sc.ClientMargins.Left, inh, e["ClientMargins"], "Left", 0, false); + ReadAttributeInt(ref sc.ClientMargins.Top, inh, e["ClientMargins"], "Top", 0, false); + ReadAttributeInt(ref sc.ClientMargins.Right, inh, e["ClientMargins"], "Right", 0, false); + ReadAttributeInt(ref sc.ClientMargins.Bottom, inh, e["ClientMargins"], "Bottom", 0, false); + + ReadAttributeInt(ref sc.ResizerSize, inh, e["ResizerSize"], "Value", 0, false); + + if (e["Layers"] != null) + { + XmlNodeList l2 = e["Layers"].GetElementsByTagName("Layer"); + if (l2 != null && l2.Count > 0) + { + LoadLayers(sc, l2); + } + } + if (e["Attributes"] != null) + { + XmlNodeList l3 = e["Attributes"].GetElementsByTagName("Attribute"); + if (l3 != null && l3.Count > 0) + { + LoadControlAttributes(sc, l3); + } + } + controls.Add(sc); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadFonts(string addon, bool archive) + { + if (doc["Skin"]["Fonts"] == null) return; + + XmlNodeList l = doc["Skin"]["Fonts"].GetElementsByTagName("Font"); + if (l != null && l.Count > 0) + { + foreach (XmlElement e in l) + { + SkinFont sf = new SkinFont(); + sf.Name = ReadAttribute(e, "Name", null, true); + sf.Archive = archive; + sf.Asset = ReadAttribute(e, "Asset", null, true); + sf.Addon = addon; + fonts.Add(sf); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadCursors(string addon, bool archive) + { + if (doc["Skin"]["Cursors"] == null) return; + + XmlNodeList l = doc["Skin"]["Cursors"].GetElementsByTagName("Cursor"); + if (l != null && l.Count > 0) + { + foreach (XmlElement e in l) + { + SkinCursor sc = new SkinCursor(); + sc.Name = ReadAttribute(e, "Name", null, true); + sc.Archive = archive; + sc.Asset = ReadAttribute(e, "Asset", null, true); + sc.Addon = addon; + cursors.Add(sc); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadImages(string addon, bool archive) + { + if (doc["Skin"]["Images"] == null) return; + XmlNodeList l = doc["Skin"]["Images"].GetElementsByTagName("Image"); + if (l != null && l.Count > 0) + { + foreach (XmlElement e in l) + { + SkinImage si = new SkinImage(); + si.Name = ReadAttribute(e, "Name", null, true); + si.Archive = archive; + si.Asset = ReadAttribute(e, "Asset", null, true); + si.Addon = addon; + images.Add(si); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadLayers(SkinControl sc, XmlNodeList l) + { + foreach (XmlElement e in l) + { + string name = ReadAttribute(e, "Name", null, true); + bool over = ReadAttributeBool(e, "Override", false, false); + SkinLayer sl = sc.Layers[name]; + bool inh = true; + + if (sl == null) + { + sl = new SkinLayer(); + inh = false; + } + + if (inh && over) + { + sl = new SkinLayer(); + sc.Layers[name] = sl; + } + + ReadAttribute(ref sl.Name, inh, e, "Name", null, true); + ReadAttribute(ref sl.Image.Name, inh, e, "Image", "Control", false); + ReadAttributeInt(ref sl.Width, inh, e, "Width", 0, false); + ReadAttributeInt(ref sl.Height, inh, e, "Height", 0, false); + + string tmp = sl.Alignment.ToString(); + ReadAttribute(ref tmp, inh, e, "Alignment", "MiddleCenter", false); + sl.Alignment = (Alignment)Enum.Parse(typeof(Alignment), tmp, true); + + ReadAttributeInt(ref sl.OffsetX, inh, e, "OffsetX", 0, false); + ReadAttributeInt(ref sl.OffsetY, inh, e, "OffsetY", 0, false); + + ReadAttributeInt(ref sl.SizingMargins.Left, inh, e["SizingMargins"], "Left", 0, false); + ReadAttributeInt(ref sl.SizingMargins.Top, inh, e["SizingMargins"], "Top", 0, false); + ReadAttributeInt(ref sl.SizingMargins.Right, inh, e["SizingMargins"], "Right", 0, false); + ReadAttributeInt(ref sl.SizingMargins.Bottom, inh, e["SizingMargins"], "Bottom", 0, false); + + ReadAttributeInt(ref sl.ContentMargins.Left, inh, e["ContentMargins"], "Left", 0, false); + ReadAttributeInt(ref sl.ContentMargins.Top, inh, e["ContentMargins"], "Top", 0, false); + ReadAttributeInt(ref sl.ContentMargins.Right, inh, e["ContentMargins"], "Right", 0, false); + ReadAttributeInt(ref sl.ContentMargins.Bottom, inh, e["ContentMargins"], "Bottom", 0, false); + + if (e["States"] != null) + { + ReadAttributeInt(ref sl.States.Enabled.Index, inh, e["States"]["Enabled"], "Index", 0, false); + int di = sl.States.Enabled.Index; + ReadAttributeInt(ref sl.States.Hovered.Index, inh, e["States"]["Hovered"], "Index", di, false); + ReadAttributeInt(ref sl.States.Pressed.Index, inh, e["States"]["Pressed"], "Index", di, false); + ReadAttributeInt(ref sl.States.Focused.Index, inh, e["States"]["Focused"], "Index", di, false); + ReadAttributeInt(ref sl.States.Disabled.Index, inh, e["States"]["Disabled"], "Index", di, false); + + ReadAttributeColor(ref sl.States.Enabled.Color, inh, e["States"]["Enabled"], "Color", Color.White, false); + Color dc = sl.States.Enabled.Color; + ReadAttributeColor(ref sl.States.Hovered.Color, inh, e["States"]["Hovered"], "Color", dc, false); + ReadAttributeColor(ref sl.States.Pressed.Color, inh, e["States"]["Pressed"], "Color", dc, false); + ReadAttributeColor(ref sl.States.Focused.Color, inh, e["States"]["Focused"], "Color", dc, false); + ReadAttributeColor(ref sl.States.Disabled.Color, inh, e["States"]["Disabled"], "Color", dc, false); + + ReadAttributeBool(ref sl.States.Enabled.Overlay, inh, e["States"]["Enabled"], "Overlay", false, false); + bool dv = sl.States.Enabled.Overlay; + ReadAttributeBool(ref sl.States.Hovered.Overlay, inh, e["States"]["Hovered"], "Overlay", dv, false); + ReadAttributeBool(ref sl.States.Pressed.Overlay, inh, e["States"]["Pressed"], "Overlay", dv, false); + ReadAttributeBool(ref sl.States.Focused.Overlay, inh, e["States"]["Focused"], "Overlay", dv, false); + ReadAttributeBool(ref sl.States.Disabled.Overlay, inh, e["States"]["Disabled"], "Overlay", dv, false); + } + + if (e["Overlays"] != null) + { + ReadAttributeInt(ref sl.Overlays.Enabled.Index, inh, e["Overlays"]["Enabled"], "Index", 0, false); + int di = sl.Overlays.Enabled.Index; + ReadAttributeInt(ref sl.Overlays.Hovered.Index, inh, e["Overlays"]["Hovered"], "Index", di, false); + ReadAttributeInt(ref sl.Overlays.Pressed.Index, inh, e["Overlays"]["Pressed"], "Index", di, false); + ReadAttributeInt(ref sl.Overlays.Focused.Index, inh, e["Overlays"]["Focused"], "Index", di, false); + ReadAttributeInt(ref sl.Overlays.Disabled.Index, inh, e["Overlays"]["Disabled"], "Index", di, false); + + ReadAttributeColor(ref sl.Overlays.Enabled.Color, inh, e["Overlays"]["Enabled"], "Color", Color.White, false); + Color dc = sl.Overlays.Enabled.Color; + ReadAttributeColor(ref sl.Overlays.Hovered.Color, inh, e["Overlays"]["Hovered"], "Color", dc, false); + ReadAttributeColor(ref sl.Overlays.Pressed.Color, inh, e["Overlays"]["Pressed"], "Color", dc, false); + ReadAttributeColor(ref sl.Overlays.Focused.Color, inh, e["Overlays"]["Focused"], "Color", dc, false); + ReadAttributeColor(ref sl.Overlays.Disabled.Color, inh, e["Overlays"]["Disabled"], "Color", dc, false); + } + + if (e["Text"] != null) + { + ReadAttribute(ref sl.Text.Name, inh, e["Text"], "Font", null, true); + ReadAttributeInt(ref sl.Text.OffsetX, inh, e["Text"], "OffsetX", 0, false); + ReadAttributeInt(ref sl.Text.OffsetY, inh, e["Text"], "OffsetY", 0, false); + + tmp = sl.Text.Alignment.ToString(); + ReadAttribute(ref tmp, inh, e["Text"], "Alignment", "MiddleCenter", false); + sl.Text.Alignment = (Alignment)Enum.Parse(typeof(Alignment), tmp, true); + + LoadColors(inh, e["Text"], ref sl.Text.Colors); + } + if (e["Attributes"] != null) + { + XmlNodeList l2 = e["Attributes"].GetElementsByTagName("Attribute"); + if (l2 != null && l2.Count > 0) + { + LoadLayerAttributes(sl, l2); + } + } + if (!inh) sc.Layers.Add(sl); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadColors(bool inherited, XmlElement e, ref SkinStates colors) + { + if (e != null) + { + ReadAttributeColor(ref colors.Enabled, inherited, e["Colors"]["Enabled"], "Color", Color.White, false); + ReadAttributeColor(ref colors.Hovered, inherited, e["Colors"]["Hovered"], "Color", colors.Enabled, false); + ReadAttributeColor(ref colors.Pressed, inherited, e["Colors"]["Pressed"], "Color", colors.Enabled, false); + ReadAttributeColor(ref colors.Focused, inherited, e["Colors"]["Focused"], "Color", colors.Enabled, false); + ReadAttributeColor(ref colors.Disabled, inherited, e["Colors"]["Disabled"], "Color", colors.Enabled, false); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadControlAttributes(SkinControl sc, XmlNodeList l) + { + foreach (XmlElement e in l) + { + string name = ReadAttribute(e, "Name", null, true); + SkinAttribute sa = sc.Attributes[name]; + bool inh = true; + + if (sa == null) + { + sa = new SkinAttribute(); + inh = false; + } + + sa.Name = name; + ReadAttribute(ref sa.Value, inh, e, "Value", null, true); + + if (!inh) sc.Attributes.Add(sa); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void LoadLayerAttributes(SkinLayer sl, XmlNodeList l) + { + foreach (XmlElement e in l) + { + string name = ReadAttribute(e, "Name", null, true); + SkinAttribute sa = sl.Attributes[name]; + bool inh = true; + + if (sa == null) + { + sa = new SkinAttribute(); + inh = false; + } + + sa.Name = name; + ReadAttribute(ref sa.Value, inh, e, "Value", null, true); + + if (!inh) sl.Attributes.Add(sa); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} diff --git a/Neoforce/SpinBox.cs b/Neoforce/SpinBox.cs new file mode 100644 index 0000000..fc358c4 --- /dev/null +++ b/Neoforce/SpinBox.cs @@ -0,0 +1,391 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: SpinBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using System.Collections.Generic; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + public enum SpinBoxMode + { + Range, + List + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + public class SpinBox: TextBox + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Button btnUp = null; + private Button btnDown = null; + private SpinBoxMode mode = SpinBoxMode.List; + private List items = new List(); + private float value = 0; + private float minimum = 0; + private float maximum = 100; + private float step = 0.25f; + private int rounding = 2; + private int itemIndex = -1; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public new virtual SpinBoxMode Mode + { + get { return mode; } + set { mode = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override bool ReadOnly + { + get { return base.ReadOnly; } + set + { + base.ReadOnly = value; + CaretVisible = !value; + if (value) + { + #if (!XBOX && !XBOX_FAKE) + Cursor = Manager.Skin.Cursors["Default"].Resource; + #endif + } + else + { + #if (!XBOX && !XBOX_FAKE) + Cursor = Manager.Skin.Cursors["Text"].Resource; + #endif + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual List Items + { + get { return items; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public float Value + { + get { return this.value; } + set + { + if (this.value != value) + { + this.value = value; + Invalidate(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public float Minimum + { + get { return minimum; } + set + { + if (minimum != value) + { + minimum = value; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public float Maximum + { + get { return maximum; } + set + { + if (maximum != value) + { + maximum = value; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public float Step + { + get { return step; } + set + { + if (step != value) + { + step = value; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public int ItemIndex + { + get { return itemIndex; } + set + { + if (mode == SpinBoxMode.List) + { + itemIndex = value; + Text = items[itemIndex].ToString(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public int Rounding + { + get { return rounding; } + set + { + if (rounding != value) + { + rounding = value; + Invalidate(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public SpinBox(Manager manager, SpinBoxMode mode): base(manager) + { + this.mode = mode; + ReadOnly = true; + + Height = 20; + Width = 64; + + btnUp = new Button(Manager); + btnUp.Init(); + btnUp.CanFocus = false; + btnUp.MousePress += new MouseEventHandler(btn_MousePress); + Add(btnUp, false); + + btnDown = new Button(Manager); + btnDown.Init(); + btnDown.CanFocus = false; + btnDown.MousePress += new MouseEventHandler(btn_MousePress); + Add(btnDown, false); + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + SkinControl sc = new SkinControl(btnUp.Skin); + sc.Layers["Control"] = new SkinLayer(Skin.Layers["Button"]); + sc.Layers["Button"].Name = "Control"; + btnUp.Skin = btnDown.Skin = sc; + + btnUp.Glyph = new Glyph(Manager.Skin.Images["Shared.ArrowUp"].Resource); + btnUp.Glyph.SizeMode = SizeMode.Centered; + btnUp.Glyph.Color = Manager.Skin.Controls["Button"].Layers["Control"].Text.Colors.Enabled; + + btnDown.Glyph = new Glyph(Manager.Skin.Images["Shared.ArrowDown"].Resource); + btnDown.Glyph.SizeMode = SizeMode.Centered; + btnDown.Glyph.Color = Manager.Skin.Controls["Button"].Layers["Control"].Text.Colors.Enabled; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["SpinBox"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + + if (ReadOnly && Focused) + { + SkinLayer lr = Skin.Layers[0]; + Rectangle rc = new Rectangle(rect.Left + lr.ContentMargins.Left, + rect.Top + lr.ContentMargins.Top, + Width - lr.ContentMargins.Horizontal - btnDown.Width - btnUp.Width, + Height - lr.ContentMargins.Vertical); + renderer.Draw(Manager.Skin.Images["ListBox.Selection"].Resource, rc, Color.FromNonPremultiplied(255, 255, 255, 128)); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + + if (btnUp != null) + { + btnUp.Width = 16; + btnUp.Height = Height - Skin.Layers["Control"].ContentMargins.Vertical; + btnUp.Top = Skin.Layers["Control"].ContentMargins.Top; + btnUp.Left = Width - 16 - 2 - 16 - 1; + } + if (btnDown != null) + { + btnDown.Width = 16; + btnDown.Height = Height - Skin.Layers["Control"].ContentMargins.Vertical; + btnDown.Top = Skin.Layers["Control"].ContentMargins.Top; ; + btnDown.Left = Width - 16 - 2; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ShiftIndex(bool direction) + { + if (mode == SpinBoxMode.List) + { + if (items.Count > 0) + { + if (direction) + { + itemIndex += 1; + } + else + { + itemIndex -= 1; + } + + if (itemIndex < 0) itemIndex = 0; + if (itemIndex > items.Count - 1) itemIndex = itemIndex = items.Count - 1; + + Text = items[itemIndex].ToString(); + } + } + else + { + if (direction) + { + value += step; + } + else + { + value -= step; + } + + if (value < minimum) value = minimum; + if (value > maximum) value = maximum; + + Text = value.ToString("n" + rounding.ToString()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void btn_MousePress(object sender, MouseEventArgs e) + { + Focused = true; + if (sender == btnUp) ShiftIndex(true); + else if (sender == btnDown) ShiftIndex(false); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnKeyPress(KeyEventArgs e) + { + if (e.Key == Keys.Up) + { + e.Handled = true; + ShiftIndex(true); + } + else if (e.Key == Keys.Down) + { + e.Handled = true; + ShiftIndex(false); + } + + base.OnKeyPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnGamePadPress(GamePadEventArgs e) + { + if (e.Button == GamePadActions.Up) + { + e.Handled = true; + ShiftIndex(true); + } + else if (e.Button == GamePadActions.Down) + { + e.Handled = true; + ShiftIndex(false); + } + + base.OnGamePadPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnGamePadDown(GamePadEventArgs e) + { + base.OnGamePadDown(e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/StackPanel.cs b/Neoforce/StackPanel.cs new file mode 100644 index 0000000..1a7e4b6 --- /dev/null +++ b/Neoforce/StackPanel.cs @@ -0,0 +1,160 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: StackPanel.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; +using System; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class StackPanel: Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Orientation orientation; + public Orientation Orientation + { + get { return this.orientation; } + set + { + this.orientation = value; + this.CalcLayout(); + } + } + private bool autoRefresh; + + /// + /// Should the stack panel refresh itself, when a control is added + /// + public bool AutoRefresh + { + get { return autoRefresh; } + set { autoRefresh = value; } + } + //////////////////////////////////////////////////////////////////////////// + + private TimeSpan refreshTimer; + private const int refreshTime = 300; //ms + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public StackPanel(Manager manager, Orientation orientation): base(manager) + { + this.orientation = orientation; + this.Color = Color.Transparent; + this.autoRefresh = true; + refreshTimer = new TimeSpan(0, 0, 0, 0, refreshTime); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + private void CalcLayout() + { + int top = Top; + int left = Left; + + foreach (Control c in ClientArea.Controls) + { + Margins m = c.Margins; + + if (orientation == Orientation.Vertical) + { + top += m.Top; + c.Top = top; + top += c.Height; + top += m.Bottom; + c.Left = left; + } + + if (orientation == Orientation.Horizontal) + { + left += m.Left; + c.Left = left; + left += c.Width; + left += m.Right; + c.Top = top; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + CalcLayout(); + base.OnResize(e); + } + //////////////////////////////////////////////////////////////////////////// + + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + + if (autoRefresh) + { + refreshTimer = refreshTimer.Subtract(TimeSpan.FromMilliseconds(gameTime.ElapsedGameTime.TotalMilliseconds)); + if (refreshTimer.TotalMilliseconds <= 0.00) + { + Refresh(); + refreshTimer = new TimeSpan(0, 0, 0, 0, refreshTime); + } + } + } + + public override void Add(Control control) + { + base.Add(control); + if (autoRefresh) Refresh(); + } + + public override void Add(Control control, bool client) + { + base.Add(control, client); + if (autoRefresh) Refresh(); + } + + #endregion + + } + +} diff --git a/Neoforce/StatusBar.cs b/Neoforce/StatusBar.cs new file mode 100644 index 0000000..9373c36 --- /dev/null +++ b/Neoforce/StatusBar.cs @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: StatusBar.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + + public class StatusBar: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public StatusBar(Manager manager): base(manager) + { + Left = 0; + Top = 0; + Width = 64; + Height = 24; + CanFocus = false; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["StatusBar"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/TabControl.cs b/Neoforce/TabControl.cs new file mode 100644 index 0000000..d72b98d --- /dev/null +++ b/Neoforce/TabControl.cs @@ -0,0 +1,390 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: TabControl.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + //////////////////////////////////////////////////////////////////////////// + public class TabControlGamePadActions: GamePadActions + { + public GamePadButton NextTab = GamePadButton.RightTrigger; + public GamePadButton PrevTab = GamePadButton.LeftTrigger; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class TabPage: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Rectangle headerRect = Rectangle.Empty; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + protected internal Rectangle HeaderRect + { + get { return headerRect; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public TabPage(Manager manager): base(manager) + { + Color = Color.Transparent; + Passive = true; + CanFocus = false; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal void CalcRect(Rectangle prev, SpriteFont font, Margins margins, Point offset, bool first) + { + int size = (int)Math.Ceiling(font.MeasureString(Text).X) + margins.Horizontal; + + if (first) offset.X = 0; + + headerRect = new Rectangle(prev.Right + offset.X, prev.Top, size, prev.Height); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public class TabControl: Container + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private List tabPages = new List(); + private int selectedIndex = 0; + private int hoveredIndex = -1; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public TabPage[] TabPages + { + get { return tabPages.ToArray(); } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int SelectedIndex + { + get { return selectedIndex; } + set + { + if (selectedIndex >= 0 && selectedIndex < tabPages.Count && value >= 0 && value < tabPages.Count) + { + TabPages[selectedIndex].Visible = false; + } + if (value >= 0 && value < tabPages.Count) + { + TabPages[value].Visible = true; + ControlsList c = TabPages[value].Controls as ControlsList; + if (c.Count > 0) c[0].Focused = true; + selectedIndex = value; + if (!Suspended) OnPageChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual TabPage SelectedPage + { + get { return tabPages[SelectedIndex]; } + set + { + for (int i = 0; i < tabPages.Count; i++) + { + if (tabPages[i] == value) + { + SelectedIndex = i; + break; + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler PageChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public TabControl(Manager manager): base(manager) + { + GamePadActions = new TabControlGamePadActions(); + Manager.Input.GamePadDown += new GamePadEventHandler(Input_GamePadDown); + this.CanFocus = false; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + SkinLayer l1 = Skin.Layers["Control"]; + SkinLayer l2 = Skin.Layers["Header"]; + Color col = this.Color != UndefinedColor ? this.Color : Color.White; + + Rectangle r1 = new Rectangle(rect.Left, rect.Top + l1.OffsetY, rect.Width, rect.Height - l1.OffsetY); + if (tabPages.Count <= 0) + { + r1 = rect; + } + + base.DrawControl(renderer, r1, gameTime); + + if (tabPages.Count > 0) + { + + Rectangle prev = new Rectangle(rect.Left, rect.Top + l2.OffsetY, 0, l2.Height); + for (int i = 0; i < tabPages.Count; i++) + { + SpriteFont font = l2.Text.Font.Resource; + Margins margins = l2.ContentMargins; + Point offset = new Point(l2.OffsetX, l2.OffsetY); + if (i > 0) prev = tabPages[i - 1].HeaderRect; + + tabPages[i].CalcRect(prev, font, margins, offset, i==0); + } + + for (int i = tabPages.Count - 1; i >= 0; i--) + { + int li = tabPages[i].Enabled ? l2.States.Enabled.Index : l2.States.Disabled.Index; + Color lc = tabPages[i].Enabled ? l2.Text.Colors.Enabled : l2.Text.Colors.Disabled; + if (i == hoveredIndex) + { + li = l2.States.Hovered.Index; + lc = l2.Text.Colors.Hovered; + } + + + Margins m = l2.ContentMargins; + Rectangle rx = tabPages[i].HeaderRect; + Rectangle sx = new Rectangle(rx.Left + m.Left, rx.Top + m.Top, rx.Width - m.Horizontal, rx.Height - m.Vertical); + if (i != selectedIndex) + { + renderer.DrawLayer(l2, rx, col, li); + renderer.DrawString(l2.Text.Font.Resource, tabPages[i].Text, sx, lc, l2.Text.Alignment); + } + } + + Margins mi = l2.ContentMargins; + Rectangle ri = tabPages[selectedIndex].HeaderRect; + Rectangle si = new Rectangle(ri.Left + mi.Left, ri.Top + mi.Top, ri.Width - mi.Horizontal, ri.Height - mi.Vertical); + renderer.DrawLayer(l2, ri, col, l2.States.Focused.Index); + renderer.DrawString(l2.Text.Font.Resource, tabPages[selectedIndex].Text, si, l2.Text.Colors.Focused, l2.Text.Alignment, l2.Text.OffsetX, l2.Text.OffsetY, false); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual TabPage AddPage(string text) + { + TabPage p = AddPage(); + p.Text = text; + + return p; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual TabPage AddPage() + { + TabPage page = new TabPage(Manager); + page.Init(); + page.Left = 0; + page.Top = 0; + page.Width = ClientWidth; + page.Height = ClientHeight; + page.Anchor = Anchors.All; + page.Text = "Tab " + (tabPages.Count + 1).ToString(); + page.Visible = false; + Add(page, true); + tabPages.Add(page); + tabPages[0].Visible = true; + + return page; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void RemovePage(TabPage page, bool dispose) + { + tabPages.Remove(page); + if (dispose) + { + page.Dispose(); + page = null; + } + SelectedIndex = 0; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void RemovePage(TabPage page) + { + RemovePage(page, true); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (tabPages.Count > 1) + { + Point p = new Point(e.State.X - Root.AbsoluteLeft, e.State.Y - Root.AbsoluteTop); + for (int i = 0; i < tabPages.Count; i++) + { + Rectangle r = tabPages[i].HeaderRect; + if (p.X >= r.Left && p.X <= r.Right && p.Y >= r.Top && p.Y <= r.Bottom) + { + SelectedIndex = i; + break; + } + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + if (tabPages.Count > 1) + { + int index = hoveredIndex; + Point p = new Point(e.State.X - Root.AbsoluteLeft, e.State.Y - Root.AbsoluteTop); + for (int i = 0; i < tabPages.Count; i++) + { + Rectangle r = tabPages[i].HeaderRect; + if (p.X >= r.Left && p.X <= r.Right && p.Y >= r.Top && p.Y <= r.Bottom && tabPages[i].Enabled) + { + index = i; + break; + } + else + { + index = -1; + } + } + if (index != hoveredIndex) + { + hoveredIndex = index; + Invalidate(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void Input_GamePadDown(object sender, GamePadEventArgs e) + { + if (this.Contains(Manager.FocusedControl, true)) + { + if (e.Button == (GamePadActions as TabControlGamePadActions).NextTab) + { + e.Handled = true; + SelectedIndex += 1; + } + else if (e.Button == (GamePadActions as TabControlGamePadActions).PrevTab) + { + e.Handled = true; + SelectedIndex -= 1; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnPageChanged(EventArgs e) + { + if (PageChanged != null) PageChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + + } + +} diff --git a/Neoforce/TextBox.cs b/Neoforce/TextBox.cs new file mode 100644 index 0000000..caf6d87 --- /dev/null +++ b/Neoforce/TextBox.cs @@ -0,0 +1,1445 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: TextBox.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework.GamerServices; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + public enum TextBoxMode + { + Normal, + Password, + Multiline + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + public class TextBox : ClipControl + { + + #region //// Structs /////////// + + //////////////////////////////////////////////////////////////////////////// + private struct Selection + { + private int start; + private int end; + + public int Start + { + get + { + if (start > end && start != -1 && end != -1) return end; + else return start; + } + set + { + start = value; + } + } + + public int End + { + get + { + if (end < start && start != -1 && end != -1) return start; + else return end; + } + set + { + end = value; + } + } + + public bool IsEmpty + { + get { return Start == -1 && End == -1; } + } + + public int Length + { + get { return IsEmpty ? 0 : (End - Start); } + } + + public Selection(int start, int end) + { + this.start = start; + this.end = end; + } + + public void Clear() + { + Start = -1; + End = -1; + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Consts //////////// + + //////////////////////////////////////////////////////////////////////////// + private const string skTextBox = "TextBox"; + private const string lrTextBox = "Control"; + private const string lrCursor = "Cursor"; + + private const string crDefault = "Default"; + private const string crText = "Text"; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private bool showCursor = false; + private double flashTime = 0; + private int posx = 0; + private int posy = 0; + private char passwordChar = ''; + private TextBoxMode mode = TextBoxMode.Normal; + private string shownText = ""; + private bool readOnly = false; + private bool drawBorders = true; + private Selection selection = new Selection(-1, -1); + private bool caretVisible = true; + private ScrollBar horz = null; + private ScrollBar vert = null; + private List lines = new List(); + private int linesDrawn = 0; + private int charsDrawn = 0; + private SpriteFont font = null; + private bool wordWrap = false; + private ScrollBars scrollBars = ScrollBars.Both; + private string Separator = "\n"; + private string text = ""; + private string buffer = ""; + private bool autoSelection = true; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + private int PosX + { + get + { + return posx; + } + set + { + posx = value; + + if (posx < 0) posx = 0; + if (posx > Lines[PosY].Length) posx = Lines[PosY].Length; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int PosY + { + get + { + return posy; + } + set + { + posy = value; + + if (posy < 0) posy = 0; + if (posy > Lines.Count - 1) posy = Lines.Count - 1; + + PosX = PosX; + } + } + //////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////// + private int Pos + { + get + { + return GetPos(PosX, PosY); + } + set + { + PosY = GetPosY(value); + PosX = GetPosX(value); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + //>>>> + + public virtual bool WordWrap + { + get { return wordWrap; } + set + { + wordWrap = value; + if (ClientArea != null) ClientArea.Invalidate(); + SetupBars(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual ScrollBars ScrollBars + { + get { return scrollBars; } + set + { + scrollBars = value; + SetupBars(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual char PasswordChar + { + get { return passwordChar; } + set { passwordChar = value; if (ClientArea != null) ClientArea.Invalidate(); } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool CaretVisible + { + get { return caretVisible; } + set { caretVisible = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual TextBoxMode Mode + { + get { return mode; } + set + { + if (value != TextBoxMode.Multiline) + { + Text = Text.Replace(Separator, ""); + } + mode = value; + selection.Clear(); + + if (ClientArea != null) ClientArea.Invalidate(); + SetupBars(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool ReadOnly + { + get { return readOnly; } + set { readOnly = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool DrawBorders + { + get { return drawBorders; } + set { drawBorders = value; if (ClientArea != null) ClientArea.Invalidate(); } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int CursorPosition + { + get { return Pos; } + set + { + Pos = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual string SelectedText + { + get + { + if (selection.IsEmpty) + { + return ""; + } + else + { + return Text.Substring(selection.Start, selection.Length); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int SelectionStart + { + get + { + if (selection.IsEmpty) + { + return Pos; + } + else + { + return selection.Start; + } + } + set + { + Pos = value; + if (Pos < 0) Pos = 0; + if (Pos > Text.Length) Pos = Text.Length; + selection.Start = Pos; + if (selection.End == -1) selection.End = Pos; + ClientArea.Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool AutoSelection + { + get { return autoSelection; } + set { autoSelection = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int SelectionLength + { + get + { + return selection.Length; + } + set + { + if (value == 0) + { + selection.End = selection.Start; + } + else if (selection.IsEmpty) + { + selection.Start = 0; + selection.End = value; + } + else if (!selection.IsEmpty) + { + selection.End = selection.Start + value; + } + + if (!selection.IsEmpty) + { + if (selection.Start < 0) selection.Start = 0; + if (selection.Start > Text.Length) selection.Start = Text.Length; + if (selection.End < 0) selection.End = 0; + if (selection.End > Text.Length) selection.End = Text.Length; + } + ClientArea.Invalidate(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private List Lines + { + get + { + return lines; + } + set + { + lines = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public override string Text + { + get + { + return text; + } + set + { + if (wordWrap) + value = WrapWords(value, ClientWidth); + + if (mode != TextBoxMode.Multiline && value != null) + { + value = value.Replace(Separator, ""); + } + + text = value; + + if (!Suspended) OnTextChanged(new EventArgs()); + + lines = SplitLines(text); + if (ClientArea != null) ClientArea.Invalidate(); + + SetupBars(); + ProcessScrolling(); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public TextBox(Manager manager) + : base(manager) + { + CheckLayer(Skin, lrCursor); + + SetDefaultSize(128, 20); + Lines.Add(""); + + ClientArea.Draw += new DrawEventHandler(ClientArea_Draw); + + vert = new ScrollBar(manager, Orientation.Vertical); + horz = new ScrollBar(manager, Orientation.Horizontal); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + vert.Init(); + vert.Range = 1; + vert.PageSize = 1; + vert.Value = 0; + vert.Anchor = Anchors.Top | Anchors.Right | Anchors.Bottom; + vert.ValueChanged += new EventHandler(sb_ValueChanged); + + horz.Init(); + horz.Range = ClientArea.Width; + horz.PageSize = ClientArea.Width; + horz.Value = 0; + horz.Anchor = Anchors.Right | Anchors.Left | Anchors.Bottom; + horz.ValueChanged += new EventHandler(sb_ValueChanged); + + horz.Visible = false; + vert.Visible = false; + + Add(vert, false); + Add(horz, false); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls[skTextBox]); + + #if (!XBOX && !XBOX_FAKE) + Cursor = Manager.Skin.Cursors[crText].Resource; + #endif + + font = (Skin.Layers[lrTextBox].Text != null) ? Skin.Layers[lrTextBox].Text.Font.Resource : null; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + if (drawBorders) + { + base.DrawControl(renderer, rect, gameTime); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int GetFitChars(string text, int width) + { + int ret = text.Length; + int size = 0; + + for (int i = 0; i < text.Length; i++) + { + size = (int)font.MeasureString(text.Substring(0, i)).X; + if (size > width) + { + ret = i; + break; + } + } + + return ret; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DeterminePages() + { + if (ClientArea != null) + { + int sizey = (int)font.LineSpacing; + linesDrawn = (int)(ClientArea.Height / sizey); + if (linesDrawn > Lines.Count) linesDrawn = Lines.Count; + + charsDrawn = ClientArea.Width - 1; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string GetMaxLine() + { + int max = 0; + int x = 0; + + for (int i = 0; i < Lines.Count; i++) + { + if (Lines[i].Length > max) + { + max = Lines[i].Length; + x = i; + } + } + return Lines.Count > 0 ? Lines[x] : ""; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void ClientArea_Draw(object sender, DrawEventArgs e) + { + SkinLayer layer = Skin.Layers[lrTextBox]; + Color col = Skin.Layers[lrTextBox].Text.Colors.Enabled; + SkinLayer cursor = Skin.Layers[lrCursor]; + Alignment al = mode == TextBoxMode.Multiline ? Alignment.TopLeft : Alignment.MiddleLeft; + Renderer renderer = e.Renderer; + Rectangle r = e.Rectangle; + bool drawsel = !selection.IsEmpty; + string tmpText = ""; + + font = (Skin.Layers[lrTextBox].Text != null) ? Skin.Layers[lrTextBox].Text.Font.Resource : null; + + if (Text != null && font != null) + { + DeterminePages(); + + if (mode == TextBoxMode.Multiline) + { + shownText = Text; + tmpText = Lines[PosY]; + } + else if (mode == TextBoxMode.Password) + { + shownText = ""; + for (int i = 0; i < Text.Length; i++) + { + shownText = shownText + passwordChar; + } + tmpText = shownText; + } + else + { + shownText = Text; + tmpText = Lines[PosY]; + } + + if (TextColor != UndefinedColor && ControlState != ControlState.Disabled) + { + col = TextColor; + } + + if (mode != TextBoxMode.Multiline) + { + linesDrawn = 0; + vert.Value = 0; + } + + if (drawsel) + { + DrawSelection(e.Renderer, r); +/* + renderer.End(); + renderer.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); + renderer.SpriteBatch.GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = true; + renderer.SpriteBatch.GraphicsDevice.RenderState.SourceBlend = Blend.DestinationColor; + renderer.SpriteBatch.GraphicsDevice.RenderState.DestinationBlend = Blend.SourceColor; + renderer.SpriteBatch.GraphicsDevice.RenderState.BlendFunction = BlendFunction.Subtract; + //renderer.SpriteBatch.GraphicsDevice.RenderState.AlphaFunction = CompareFunction.Equal; + //renderer.SpriteBatch.GraphicsDevice.RenderState.AlphaSourceBlend = Blend.One; + //renderer.SpriteBatch.GraphicsDevice.RenderState.AlphaDestinationBlend = Blend.DestinationAlpha; + */ + } + + int sizey = (int)font.LineSpacing; + + if (showCursor && caretVisible) + { + Vector2 size = Vector2.Zero; + if (PosX > 0 && PosX <= tmpText.Length) + { + size = font.MeasureString(tmpText.Substring(0, PosX)); + } + if (size.Y == 0) + { + size = font.MeasureString(" "); + size.X = 0; + } + + int m = r.Height - font.LineSpacing; + + Rectangle rc = new Rectangle(r.Left - horz.Value + (int)size.X, r.Top + m / 2, cursor.Width, font.LineSpacing); + + if (mode == TextBoxMode.Multiline) + { + rc = new Rectangle(r.Left + (int)size.X - horz.Value, r.Top + (int)((PosY - vert.Value) * font.LineSpacing), cursor.Width, font.LineSpacing); + } + cursor.Alignment = al; + renderer.DrawLayer(cursor, rc, col, 0); + } + + for (int i = 0; i < linesDrawn + 1; i++) + { + int ii = i + vert.Value; + if (ii >= Lines.Count || ii < 0) break; + + if (Lines[ii] != "") + { + if (mode == TextBoxMode.Multiline) + { + renderer.DrawString(font, Lines[ii], r.Left - horz.Value, r.Top + (i * sizey), col); + } + else + { + Rectangle rx = new Rectangle(r.Left - horz.Value, r.Top, r.Width, r.Height); + renderer.DrawString(font, shownText, rx, col, al, false); + } + } + } + /* if (drawsel) + { + renderer.End(); + renderer.Begin(BlendingMode.Premultiplied); + }*/ + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int GetStringWidth(string text, int count) + { + if (count > text.Length) count = text.Length; + return (int)font.MeasureString(text.Substring(0, count)).X; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void ProcessScrolling() + { + if (vert != null && horz != null) + { + vert.PageSize = linesDrawn; + horz.PageSize = charsDrawn; + + if (horz.PageSize > horz.Range) horz.PageSize = horz.Range; + + if (PosY >= vert.Value + vert.PageSize) + { + vert.Value = (PosY + 1) - vert.PageSize; + } + else if (PosY < vert.Value) + { + vert.Value = PosY; + } + + if (GetStringWidth(Lines[PosY], PosX) >= horz.Value + horz.PageSize) + { + horz.Value = (GetStringWidth(Lines[PosY], PosX) + 1) - horz.PageSize; + } + else if (GetStringWidth(Lines[PosY], PosX) < horz.Value) + { + horz.Value = GetStringWidth(Lines[PosY], PosX) - horz.PageSize; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void DrawSelection(Renderer renderer, Rectangle rect) + { + if (!selection.IsEmpty) + { + int s = selection.Start; + int e = selection.End; + + int sl = GetPosY(s); + int el = GetPosY(e); + int sc = GetPosX(s); + int ec = GetPosX(e); + + int hgt = font.LineSpacing; + + int start = sl; + int end = el; + + if (start < vert.Value) start = vert.Value; + if (end > vert.Value + linesDrawn) end = vert.Value + linesDrawn; + + for (int i = start; i <= end; i++) + { + Rectangle r = Rectangle.Empty; + + if (mode == TextBoxMode.Normal) + { + int m = ClientArea.Height - font.LineSpacing; + r = new Rectangle(rect.Left - horz.Value + (int)font.MeasureString(Lines[i].Substring(0, sc)).X, rect.Top + m / 2, + (int)font.MeasureString(Lines[i].Substring(0, ec + 0)).X - (int)font.MeasureString(Lines[i].Substring(0, sc)).X, hgt); + } + else if (sl == el) + { + r = new Rectangle(rect.Left - horz.Value + (int)font.MeasureString(Lines[i].Substring(0, sc)).X, rect.Top + (i - vert.Value) * hgt, + (int)font.MeasureString(Lines[i].Substring(0, ec + 0)).X - (int)font.MeasureString(Lines[i].Substring(0, sc)).X, hgt); + } + else + { + if (i == sl) r = new Rectangle(rect.Left - horz.Value + (int)font.MeasureString(Lines[i].Substring(0, sc)).X, rect.Top + (i - vert.Value) * hgt, (int)font.MeasureString(Lines[i]).X - (int)font.MeasureString(Lines[i].Substring(0, sc)).X, hgt); + else if (i == el) r = new Rectangle(rect.Left - horz.Value, rect.Top + (i - vert.Value) * hgt, (int)font.MeasureString(Lines[i].Substring(0, ec + 0)).X, hgt); + else r = new Rectangle(rect.Left - horz.Value, rect.Top + (i - vert.Value) * hgt, (int)font.MeasureString(Lines[i]).X, hgt); + } + + renderer.Draw(Manager.Skin.Images["Control"].Resource, r, Color.FromNonPremultiplied(160, 160, 160, 128)); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + + bool sc = showCursor; + + showCursor = Focused; + + if (Focused) + { + flashTime += gameTime.ElapsedGameTime.TotalSeconds; + showCursor = flashTime < 0.5; + if (flashTime > 1) flashTime = 0; + } + if (sc != showCursor) ClientArea.Invalidate(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int FindPrevWord(string text) + { + bool letter = false; + + int p = Pos - 1; + if (p < 0) p = 0; + if (p >= text.Length) p = text.Length - 1; + + + for (int i = p; i >= 0; i--) + { + if (char.IsLetterOrDigit(text[i])) + { + letter = true; + continue; + } + if (letter && !char.IsLetterOrDigit(text[i])) + { + return i + 1; + } + } + + return 0; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int FindNextWord(string text) + { + bool space = false; + + for (int i = Pos; i < text.Length - 1; i++) + { + if (!char.IsLetterOrDigit(text[i])) + { + space = true; + continue; + } + if (space && char.IsLetterOrDigit(text[i])) + { + return i; + } + } + + return text.Length; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int GetPosY(int pos) + { + if (pos >= Text.Length) return Lines.Count - 1; + + int p = pos; + for (int i = 0; i < Lines.Count; i++) + { + p -= Lines[i].Length + Separator.Length; + if (p < 0) + { + p = p + Lines[i].Length + Separator.Length; + return i; + } + } + return 0; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int GetPosX(int pos) + { + if (pos >= Text.Length) return Lines[Lines.Count - 1].Length; + + int p = pos; + for (int i = 0; i < Lines.Count; i++) + { + p -= Lines[i].Length + Separator.Length; + if (p < 0) + { + p = p + Lines[i].Length + Separator.Length; + return p; + } + } + return 0; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int GetPos(int x, int y) + { + int p = 0; + + for (int i = 0; i < y; i++) + { + p += Lines[i].Length + Separator.Length; + } + p += x; + + return p; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private int CharAtPos(Point pos) + { + int x = pos.X; + int y = pos.Y; + int px = 0; + int py = 0; + + if (mode == TextBoxMode.Multiline) + { + py = vert.Value + (int)((y - ClientTop) / font.LineSpacing); + if (py < 0) py = 0; + if (py >= Lines.Count) py = Lines.Count - 1; + } + else + { + py = 0; + } + + string str = mode == TextBoxMode.Multiline ? Lines[py] : shownText; + + if (str != null && str != "") + { + for (int i = 1; i <= Lines[py].Length; i++) + { + Vector2 v = font.MeasureString(str.Substring(0, i)) - (font.MeasureString(str[i - 1].ToString()) / 3); + if (x <= (ClientLeft + (int)v.X) - horz.Value) + { + px = i - 1; + break; + } + } + if (x > ClientLeft + ((int)font.MeasureString(str).X) - horz.Value - (font.MeasureString(str[str.Length - 1].ToString()).X / 3)) px = str.Length; + } + + return GetPos(px, py); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + flashTime = 0; + + Pos = CharAtPos(e.Position); + selection.Clear(); + + if (e.Button == MouseButton.Left && caretVisible && mode != TextBoxMode.Password) + { + selection.Start = Pos; + selection.End = Pos; + } + ClientArea.Invalidate(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (e.Button == MouseButton.Left && !selection.IsEmpty && mode != TextBoxMode.Password && selection.Length < Text.Length) + { + int pos = CharAtPos(e.Position); + selection.End = CharAtPos(e.Position); + Pos = pos; + + ClientArea.Invalidate(); + + ProcessScrolling(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (e.Button == MouseButton.Left && !selection.IsEmpty && mode != TextBoxMode.Password) + { + if (selection.Length == 0) selection.Clear(); + } + } + //////////////////////////////////////////////////////////////////////////// + + protected override void OnMouseScroll(MouseEventArgs e) + { + if (Mode != TextBoxMode.Multiline) + { + base.OnMouseScroll(e); + return; + } + + if (e.ScrollDirection == MouseScrollDirection.Down) + vert.ScrollDown(); + else + vert.ScrollUp(); + + base.OnMouseScroll(e); + } + + //////////////////////////////////////////////////////////////////////////// + protected override void OnKeyPress(KeyEventArgs e) + { + flashTime = 0; + + if (Manager.UseGuide && Guide.IsVisible) return; + + if (!e.Handled) + { + if (e.Key == Keys.A && e.Control && mode != TextBoxMode.Password) + { + SelectAll(); + } + if (e.Key == Keys.Up) + { + e.Handled = true; + + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + PosY -= 1; + } + } + else if (e.Key == Keys.Down) + { + e.Handled = true; + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + PosY += 1; + } + } + else if (e.Key == Keys.Back && !readOnly) + { + e.Handled = true; + if (!selection.IsEmpty) + { + Text = Text.Remove(selection.Start, selection.Length); + Pos = selection.Start; + } + else if (Text.Length > 0 && Pos > 0) + { + Pos -= 1; + Text = Text.Remove(Pos, 1); + } + selection.Clear(); + } + else if (e.Key == Keys.Delete && !readOnly) + { + e.Handled = true; + if (!selection.IsEmpty) + { + Text = Text.Remove(selection.Start, selection.Length); + Pos = selection.Start; + } + else if (Pos < Text.Length) + { + Text = Text.Remove(Pos, 1); + } + selection.Clear(); + } + else if (e.Key == Keys.Left) + { + e.Handled = true; + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + Pos -= 1; + } + if (e.Control) + { + Pos = FindPrevWord(shownText); + } + } + else if (e.Key == Keys.Right) + { + e.Handled = true; + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + Pos += 1; + } + if (e.Control) + { + Pos = FindNextWord(shownText); + } + } + else if (e.Key == Keys.Home) + { + e.Handled = true; + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + PosX = 0; + } + if (e.Control) + { + Pos = 0; + } + } + else if (e.Key == Keys.End) + { + e.Handled = true; + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + PosX = Lines[PosY].Length; + } + if (e.Control) + { + Pos = Text.Length; + } + } + else if (e.Key == Keys.PageUp) + { + e.Handled = true; + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + PosY -= linesDrawn; + } + } + else if (e.Key == Keys.PageDown) + { + e.Handled = true; + if (e.Shift && selection.IsEmpty && mode != TextBoxMode.Password) + { + selection.Start = Pos; + } + if (!e.Control) + { + PosY += linesDrawn; + } + } + else if (e.Key == Keys.Enter && mode == TextBoxMode.Multiline && !readOnly) + { + e.Handled = true; + Text = Text.Insert(Pos, Separator); + PosX = 0; + PosY += 1; + } + else if (e.Key == Keys.Tab) + { + } + else if (!readOnly && !e.Control) + { + string c = Manager.KeyboardLayout.GetKey(e); + if (selection.IsEmpty) + { + Text = Text.Insert(Pos, c); + if (c != "") PosX += 1; + } + else + { + if (Text.Length > 0) + { + Text = Text.Remove(selection.Start, selection.Length); + Text = Text.Insert(selection.Start, c); + Pos = selection.Start + 1; + } + selection.Clear(); + } + } + + if (e.Shift && !selection.IsEmpty) + { + selection.End = Pos; + } + + /* + * TODO: Fix + * MONOTODO: Fix + if (e.Control && e.Key == Keys.C && mode != TextBoxMode.Password) + { +#if (!XBOX && !XBOX_FAKE) + System.Windows.Forms.Clipboard.Clear(); + if (mode != TextBoxMode.Password && !selection.IsEmpty) + { + System.Windows.Forms.Clipboard.SetText((Text.Substring(selection.Start, selection.Length)).Replace("\n", Environment.NewLine)); + } +#endif + } + else if (e.Control && e.Key == Keys.V && !readOnly && mode != TextBoxMode.Password) + { +#if (!XBOX && !XBOX_FAKE) + string t = System.Windows.Forms.Clipboard.GetText().Replace(Environment.NewLine, "\n"); + if (selection.IsEmpty) + { + Text = Text.Insert(Pos, t); + Pos = Pos + t.Length; + } + else + { + Text = Text.Remove(selection.Start, selection.Length); + Text = Text.Insert(selection.Start, t); + PosX = selection.Start + t.Length; + selection.Clear(); + } +#endif + } + */ + if ((!e.Shift && !e.Control) || Text.Length <= 0) + { + selection.Clear(); + } + + if (e.Control && e.Key == Keys.Down) + { + e.Handled = true; + HandleGuide(PlayerIndex.One); + } + flashTime = 0; + if (ClientArea != null) ClientArea.Invalidate(); + + DeterminePages(); + ProcessScrolling(); + } + base.OnKeyPress(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnGamePadDown(GamePadEventArgs e) + { + if (Manager.UseGuide && Guide.IsVisible) return; + + if (!e.Handled) + { + if (e.Button == GamePadActions.Click) + { + e.Handled = true; + HandleGuide(e.PlayerIndex); + } + } + base.OnGamePadDown(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void HandleGuide(PlayerIndex pi) + { + if (Manager.UseGuide && !Guide.IsVisible) + { + Guide.BeginShowKeyboardInput(pi, "Enter Text", "", Text, GetText, pi.ToString()); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void GetText(IAsyncResult result) + { + string res = Guide.EndShowKeyboardInput(result); + Text = res != null ? res : ""; + Pos = text.Length; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void SetupBars() + { + DeterminePages(); + + if (vert != null) vert.Range = Lines.Count; + if (horz != null) + { + horz.Range = (int)font.MeasureString(GetMaxLine()).X; + if (horz.Range == 0) horz.Range = ClientArea.Width; + } + + if (vert != null) + { + vert.Left = Width - 16 - 2; + vert.Top = 2; + vert.Height = Height - 4 - 16; + + if (Height < 50 || (scrollBars != ScrollBars.Both && scrollBars != ScrollBars.Vertical)) vert.Visible = false; + else if ((scrollBars == ScrollBars.Vertical || scrollBars == ScrollBars.Both) && mode == TextBoxMode.Multiline) vert.Visible = true; + } + if (horz != null) + { + horz.Left = 2; + horz.Top = Height - 16 - 2; + horz.Width = Width - 4 - 16; + + if (Width < 50 || wordWrap || (scrollBars != ScrollBars.Both && scrollBars != ScrollBars.Horizontal)) horz.Visible = false; + else if ((scrollBars == ScrollBars.Horizontal || scrollBars == ScrollBars.Both) && mode == TextBoxMode.Multiline && !wordWrap) horz.Visible = true; + } + + AdjustMargins(); + + if (vert != null) vert.PageSize = linesDrawn; + if (horz != null) horz.PageSize = charsDrawn; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void AdjustMargins() + { + if (horz != null && !horz.Visible) + { + vert.Height = Height - 4; + ClientMargins = new Margins(ClientMargins.Left, ClientMargins.Top, ClientMargins.Right, Skin.ClientMargins.Bottom); + } + else + { + ClientMargins = new Margins(ClientMargins.Left, ClientMargins.Top, ClientMargins.Right, 18 + Skin.ClientMargins.Bottom); + } + + if (vert != null && !vert.Visible) + { + horz.Width = Width - 4; + ClientMargins = new Margins(ClientMargins.Left, ClientMargins.Top, Skin.ClientMargins.Right, ClientMargins.Bottom); + } + else + { + ClientMargins = new Margins(ClientMargins.Left, ClientMargins.Top, 18 + Skin.ClientMargins.Right, ClientMargins.Bottom); + } + base.AdjustMargins(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + selection.Clear(); + SetupBars(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private string WrapWords(string text, int size) + { + string ret = ""; + string line = ""; + + string[] words = text.Replace("\v", "").Split(" ".ToCharArray()); + + for (int i = 0; i < words.Length; i++) + { + if (font.MeasureString(line + words[i]).X > size) + { + ret += line + "\n"; + line = words[i] + " "; + } + else + { + line += words[i] + " "; + } + } + + ret += line; + + return ret.Remove(ret.Length - 1, 1); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void SelectAll() + { + if (text.Length > 0) + { + selection.Start = 0; + selection.End = Text.Length; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private List SplitLines(string text) + { + if (buffer != text) + { + buffer = text; + List list = new List(); + string[] s = text.Split(new char[] { Separator[0] }); + list.Clear(); + + //Before adding the lines back in, we will want to first, measure the lines, and split words if needed... + + list.AddRange(s); + + if (posy < 0) posy = 0; + if (posy > list.Count - 1) posy = list.Count - 1; + + if (posx < 0) posx = 0; + if (posx > list[PosY].Length) posx = list[PosY].Length; + + return list; + } + else return lines; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void sb_ValueChanged(object sender, EventArgs e) + { + ClientArea.Invalidate(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnFocusLost(EventArgs e) + { + selection.Clear(); + ClientArea.Invalidate(); + base.OnFocusLost(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnFocusGained(EventArgs e) + { + if (!readOnly && autoSelection) + { + SelectAll(); + ClientArea.Invalidate(); + } + + base.OnFocusGained(e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} \ No newline at end of file diff --git a/Neoforce/TomShane.Neoforce.Controls.csproj b/Neoforce/TomShane.Neoforce.Controls.csproj new file mode 100644 index 0000000..77a1c46 --- /dev/null +++ b/Neoforce/TomShane.Neoforce.Controls.csproj @@ -0,0 +1,115 @@ + + + + + Debug + AnyCPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841} + Library + Properties + TomShane.Neoforce.Controls + TomShane.Neoforce.Controls + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + C:\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\MonoGame.Framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Neoforce/ToolBar.cs b/Neoforce/ToolBar.cs new file mode 100644 index 0000000..4833d75 --- /dev/null +++ b/Neoforce/ToolBar.cs @@ -0,0 +1,113 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ToolBar.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + + public class ToolBar: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private int row = 0; + private bool fullRow = false; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int Row + { + get { return row; } + set + { + row = value; + if (row < 0) row = 0; + if (row > 7) row = 7; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool FullRow + { + get { return fullRow; } + set { fullRow = value; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ToolBar(Manager manager): base(manager) + { + Left = 0; + Top = 0; + Width = 64; + Height = 24; + CanFocus = false; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["ToolBar"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/ToolBarButton.cs b/Neoforce/ToolBarButton.cs new file mode 100644 index 0000000..1134156 --- /dev/null +++ b/Neoforce/ToolBarButton.cs @@ -0,0 +1,90 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ToolBarButton.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + + public class ToolBarButton: Button + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ToolBarButton(Manager manager): base(manager) + { + CanFocus = false; + Text = ""; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["ToolBarButton"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/ToolBarPanel.cs b/Neoforce/ToolBarPanel.cs new file mode 100644 index 0000000..62a2b7f --- /dev/null +++ b/Neoforce/ToolBarPanel.cs @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ToolBarPanel.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + + public class ToolBarPanel: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ToolBarPanel(Manager manager): base(manager) + { + Width = 64; + Height = 25; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["ToolBarPanel"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + base.DrawControl(renderer, rect, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void Update(GameTime gameTime) + { + base.Update(gameTime); + AlignBars(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void AlignBars() + { + int[] rx = new int[8]; + int h = 0; + int rm = -1; + + foreach (Control c in Controls) + { + if (c is ToolBar) + { + ToolBar t = c as ToolBar; + if (t.FullRow) t.Width = Width; + t.Left = rx[t.Row]; + t.Top = (t.Row * t.Height) + (t.Row > 0 ? 1 : 0); + rx[t.Row] += t.Width + 1; + + if (t.Row > rm) + { + rm = t.Row; + h = t.Top + t.Height + 1; + } + } + } + + Height = h; + } + //////////////////////////////////////////////////////////////////////////// + + + #endregion + + } + +} diff --git a/Neoforce/ToolTip.cs b/Neoforce/ToolTip.cs new file mode 100644 index 0000000..cec409b --- /dev/null +++ b/Neoforce/ToolTip.cs @@ -0,0 +1,110 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: ToolTip.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + public class ToolTip: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public override bool Visible + { + set + { + if (value && Text != null && Text != "" && Skin != null && Skin.Layers[0] != null) + { + Vector2 size = Skin.Layers[0].Text.Font.Resource.MeasureString(Text); + Width = (int)size.X + Skin.Layers[0].ContentMargins.Horizontal; + Height = (int)size.Y + Skin.Layers[0].ContentMargins.Vertical; + Left = Mouse.GetState().X; + Top = Mouse.GetState().Y + 24; + base.Visible = value; + } + else + { + base.Visible = false; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public ToolTip(Manager manager): base(manager) + { + Text = ""; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + CanFocus = false; + Passive = true; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = Manager.Skin.Controls["ToolTip"]; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + renderer.DrawLayer(this, Skin.Layers[0], rect); + renderer.DrawString(this, Skin.Layers[0], Text, rect, true); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/TrackBar.cs b/Neoforce/TrackBar.cs new file mode 100644 index 0000000..46c7c8a --- /dev/null +++ b/Neoforce/TrackBar.cs @@ -0,0 +1,341 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: TrackBar.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework.Graphics; +using System; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public class TrackBar: Control + { + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private int range = 100; + private int value = 0; + private int stepSize = 1; + private int pageSize = 5; + private bool scale = true; + private Button btnSlider; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int Value + { + get { return this.value; } + set + { + if (this.value != value) + { + this.value = value; + if (this.value < 0) this.value = 0; + if (this.value > range) this.value = range; + Invalidate(); + if (!Suspended) OnValueChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int Range + { + get { return range; } + set + { + if (range != value) + { + range = value; + range = value; + if (pageSize > range) pageSize = range; + RecalcParams(); + if (!Suspended) OnRangeChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int PageSize + { + get { return pageSize; } + set + { + if (pageSize != value) + { + pageSize = value; + if (pageSize > range) pageSize = range; + RecalcParams(); + if (!Suspended) OnPageSizeChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual int StepSize + { + get { return stepSize; } + set + { + if (stepSize != value) + { + stepSize = value; + if (stepSize > range) stepSize = range; + if (!Suspended) OnStepSizeChanged(new EventArgs()); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool Scale + { + get { return scale; } + set { scale = value; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + public event EventHandler ValueChanged; + public event EventHandler RangeChanged; + public event EventHandler StepSizeChanged; + public event EventHandler PageSizeChanged; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Construstors ////// + + //////////////////////////////////////////////////////////////////////////// + public TrackBar(Manager manager): base(manager) + { + Width = 64; + Height = 20; + CanFocus = false; + + btnSlider = new Button(Manager); + btnSlider.Init(); + btnSlider.Text = ""; + btnSlider.CanFocus = true; + btnSlider.Parent = this; + btnSlider.Anchor = Anchors.Left | Anchors.Top | Anchors.Bottom; + btnSlider.Detached = true; + btnSlider.Movable = true; + btnSlider.Move += new MoveEventHandler(btnSlider_Move); + btnSlider.KeyPress += new KeyEventHandler(btnSlider_KeyPress); + btnSlider.GamePadPress += new GamePadEventHandler(btnSlider_GamePadPress); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + btnSlider.Skin = new SkinControl(Manager.Skin.Controls["TrackBar.Button"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls["TrackBar"]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + RecalcParams(); + + SkinLayer p = Skin.Layers["Control"]; + SkinLayer l = Skin.Layers["Scale"]; + + float ratio = 0.66f; + int h = (int)(ratio * rect.Height); + int t = rect.Top + (Height - h) / 2; + + float px = ((float)value / (float)range); + int w = (int)Math.Ceiling(px * (rect.Width - p.ContentMargins.Horizontal - btnSlider.Width)) + 2; + + if (w < l.SizingMargins.Vertical) w = l.SizingMargins.Vertical; + if (w > rect.Width - p.ContentMargins.Horizontal) w = rect.Width - p.ContentMargins.Horizontal; + + Rectangle r1 = new Rectangle(rect.Left + p.ContentMargins.Left, t + p.ContentMargins.Top, w, h - p.ContentMargins.Vertical); + + base.DrawControl(renderer, new Rectangle(rect.Left, t, rect.Width, h), gameTime); + if (scale) renderer.DrawLayer(this, l, r1); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void btnSlider_Move(object sender, MoveEventArgs e) + { + SkinLayer p = Skin.Layers["Control"]; + int size = btnSlider.Width; + int w = Width - p.ContentMargins.Horizontal - size; + int pos = e.Left; + + if (pos < p.ContentMargins.Left) pos = p.ContentMargins.Left; + if (pos > w + p.ContentMargins.Left) pos = w + p.ContentMargins.Left; + + btnSlider.SetPosition(pos, 0); + + float px = (float)range / (float)w; + Value = (int)(Math.Ceiling((pos - p.ContentMargins.Left) * px)); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void RecalcParams() + { + if (btnSlider != null) + { + if (btnSlider.Width > 12) + { + btnSlider.Glyph = new Glyph(Manager.Skin.Images["Shared.Glyph"].Resource); + btnSlider.Glyph.SizeMode = SizeMode.Centered; + } + else + { + btnSlider.Glyph = null; + } + + SkinLayer p = Skin.Layers["Control"]; + btnSlider.Width = (int)(Height * 0.8); + btnSlider.Height = Height; + int size = btnSlider.Width; + int w = Width - p.ContentMargins.Horizontal - size; + + float px = (float)range / (float)w; + int pos = p.ContentMargins.Left + (int)(Math.Ceiling(Value / (float)px)); + + if (pos < p.ContentMargins.Left) pos = p.ContentMargins.Left; + if (pos > w + p.ContentMargins.Left) pos = w + p.ContentMargins.Left; + + btnSlider.SetPosition(pos, 0); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMousePress(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (e.Button == MouseButton.Left) + { + int pos = e.Position.X; + + if (pos < btnSlider.Left) + { + Value -= pageSize; + } + else if (pos >= btnSlider.Left + btnSlider.Width) + { + Value += pageSize; + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void btnSlider_GamePadPress(object sender, GamePadEventArgs e) + { + if (e.Button == GamePadActions.Left || e.Button == GamePadActions.Down) Value -= stepSize; + if (e.Button == GamePadActions.Right || e.Button == GamePadActions.Up) Value += stepSize; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void btnSlider_KeyPress(object sender, KeyEventArgs e) + { + if (e.Key == Microsoft.Xna.Framework.Input.Keys.Left || e.Key == Microsoft.Xna.Framework.Input.Keys.Down) Value -= stepSize; + else if (e.Key == Microsoft.Xna.Framework.Input.Keys.Right || e.Key == Microsoft.Xna.Framework.Input.Keys.Up) Value += stepSize; + else if (e.Key == Microsoft.Xna.Framework.Input.Keys.PageDown) Value -= pageSize; + else if (e.Key == Microsoft.Xna.Framework.Input.Keys.PageUp) Value += pageSize; + else if (e.Key == Microsoft.Xna.Framework.Input.Keys.Home) Value = 0; + else if (e.Key == Microsoft.Xna.Framework.Input.Keys.End) Value = Range; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + RecalcParams(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnValueChanged(EventArgs e) + { + if (ValueChanged != null) ValueChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnRangeChanged(EventArgs e) + { + if (RangeChanged != null) RangeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnPageSizeChanged(EventArgs e) + { + if (PageSizeChanged != null) PageSizeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected virtual void OnStepSizeChanged(EventArgs e) + { + if (StepSizeChanged != null) StepSizeChanged.Invoke(this, e); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} diff --git a/Neoforce/Types.cs b/Neoforce/Types.cs new file mode 100644 index 0000000..2f961ec --- /dev/null +++ b/Neoforce/Types.cs @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Types.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Enums ///////////// + + //////////////////////////////////////////////////////////////////////////// + public enum Message + { + Click, + MouseDown, + MouseUp, + MousePress, + MouseMove, + MouseOver, + MouseOut, + MouseScroll, + KeyDown, + KeyUp, + KeyPress, + GamePadDown, + GamePadUp, + GamePadPress + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum ControlState + { + Enabled, + Hovered, + Pressed, + Focused, + Disabled + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum Alignment + { + None, + TopLeft, + TopCenter, + TopRight, + MiddleLeft, + MiddleCenter, + MiddleRight, + BottomLeft, + BottomCenter, + BottomRight + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum ModalResult + { + None, + Ok, + Cancel, + Yes, + No, + Abort, + Retry, + Ignore + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum Orientation + { + Horizontal, + Vertical + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public enum ScrollBars + { + None, + Vertical, + Horizontal, + Both + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + [Flags] + public enum Anchors + { + None = 0x00, + Left = 0x01, + Top = 0x02, + Right = 0x04, + Bottom = 0x08, + Horizontal = Left | Right, + Vertical = Top | Bottom, + All = Left | Top | Right | Bottom + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Structs /////////// + + //////////////////////////////////////////////////////////////////////////// + public struct Margins + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public int Vertical { get { return (Top + Bottom); } } + public int Horizontal { get { return (Left + Right); } } + + public Margins(int left, int top, int right, int bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public struct Size + { + public int Width; + public int Height; + + public Size(int width, int height) + { + Width = width; + Height = height; + } + + public static Size Zero + { + get + { + return new Size(0, 0); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} + \ No newline at end of file diff --git a/Neoforce/Unknown.cs b/Neoforce/Unknown.cs new file mode 100644 index 0000000..a6da660 --- /dev/null +++ b/Neoforce/Unknown.cs @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Unknown.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + public abstract class Unknown + { + + #region //// Fields //////////// + + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + ////////////////////////////////////////////////////////////////////////// + protected Unknown() + { + } + ////////////////////////////////////////////////////////////////////////// + + #endregion + + } + +} + diff --git a/Neoforce/Utilities.cs b/Neoforce/Utilities.cs new file mode 100644 index 0000000..a71cf47 --- /dev/null +++ b/Neoforce/Utilities.cs @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Utilities.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using System; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + static class Utilities + { + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public static string DeriveControlName(Control control) + { + if (control != null) + { + try + { + string str = control.ToString(); + int i = str.LastIndexOf("."); + return str.Remove(0, i + 1); + } + catch + { + return control.ToString(); + } + } + return control.ToString(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public static Color ParseColor(string str) + { + + string[] val = str.Split(';'); + byte r = 255, g = 255, b = 255, a = 255; + + if (val.Length >= 1) r = byte.Parse(val[0]); + if (val.Length >= 2) g = byte.Parse(val[1]); + if (val.Length >= 3) b = byte.Parse(val[2]); + if (val.Length >= 4) a = byte.Parse(val[3]); + + return Color.FromNonPremultiplied(r, g, b, a); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public static BevelStyle ParseBevelStyle(string str) + { + return (BevelStyle)Enum.Parse(typeof(BevelStyle), str, true); + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + } +} diff --git a/Neoforce/Window.cs b/Neoforce/Window.cs new file mode 100644 index 0000000..5e69c6d --- /dev/null +++ b/Neoforce/Window.cs @@ -0,0 +1,491 @@ +//////////////////////////////////////////////////////////////// +// // +// Neoforce Controls // +// // +//////////////////////////////////////////////////////////////// +// // +// File: Window.cs // +// // +// Version: 0.7 // +// // +// Date: 11/09/2010 // +// // +// Author: Tom Shane // +// // +//////////////////////////////////////////////////////////////// +// // +// Copyright (c) by Tom Shane // +// // +//////////////////////////////////////////////////////////////// + +#region //// Using ///////////// + +//////////////////////////////////////////////////////////////////////////// +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +//////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace TomShane.Neoforce.Controls +{ + + #region //// Classes /////////// + + //////////////////////////////////////////////////////////////////////////// + public class WindowGamePadActions: GamePadActions + { + public GamePadButton Accept = GamePadButton.Start; + public GamePadButton Cancel = GamePadButton.Back; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + /// + public class Window: ModalContainer + { + + #region //// Consts //////////// + + //////////////////////////////////////////////////////////////////////////// + private const string skWindow = "Window"; + private const string lrWindow = "Control"; + private const string lrCaption = "Caption"; + private const string lrFrameTop = "FrameTop"; + private const string lrFrameLeft = "FrameLeft"; + private const string lrFrameRight = "FrameRight"; + private const string lrFrameBottom = "FrameBottom"; + private const string lrIcon = "Icon"; + + private const string skButton = "Window.CloseButton"; + private const string lrButton = "Control"; + + private const string skShadow = "Window.Shadow"; + private const string lrShadow = "Control"; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Fields //////////// + + //////////////////////////////////////////////////////////////////////////// + private Button btnClose; + private bool closeButtonVisible = true; + private bool iconVisible = true; + private Texture2D icon = null; + private bool shadow = true; + private bool captionVisible = true; + private bool borderVisible = true; + private byte oldAlpha = 255; + private byte dragAlpha = 200; + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Events //////////// + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Properties //////// + + //////////////////////////////////////////////////////////////////////////// + public virtual Texture2D Icon + { + get { return icon; } + set { icon = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool Shadow + { + get { return shadow; } + set { shadow = value; } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool CloseButtonVisible + { + get + { + return closeButtonVisible; + } + set + { + closeButtonVisible = value; + if (btnClose != null) btnClose.Visible = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool IconVisible + { + get + { + return iconVisible; + } + set + { + iconVisible = value; + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool CaptionVisible + { + get { return captionVisible; } + set + { + captionVisible = value; + AdjustMargins(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual bool BorderVisible + { + get { return borderVisible; } + set + { + borderVisible = value; + AdjustMargins(); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual byte DragAlpha + { + get { return dragAlpha; } + set { dragAlpha = value; } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + #region //// Constructors ////// + + //////////////////////////////////////////////////////////////////////////// + public Window(Manager manager): base(manager) + { + CheckLayer(Skin, lrWindow); + CheckLayer(Skin, lrCaption); + CheckLayer(Skin, lrFrameTop); + CheckLayer(Skin, lrFrameLeft); + CheckLayer(Skin, lrFrameRight); + CheckLayer(Skin, lrFrameBottom); + CheckLayer(Manager.Skin.Controls[skButton], lrButton); + CheckLayer(Manager.Skin.Controls[skShadow], lrShadow); + + SetDefaultSize(640, 480); + SetMinimumSize(100, 75); + + btnClose = new Button(manager); + btnClose.Skin = new SkinControl(Manager.Skin.Controls[skButton]); + btnClose.Init(); + btnClose.Detached = true; + btnClose.CanFocus = false; + btnClose.Text = null; + btnClose.Click += new EventHandler(btnClose_Click); + btnClose.SkinChanged += new EventHandler(btnClose_SkinChanged); + + AdjustMargins(); + + AutoScroll = true; + Movable = true; + Resizable = true; + Center(); + + Add(btnClose, false); + + oldAlpha = Alpha; + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + + //////////////////////////////////////////////////////////////////////////// + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + base.Dispose(disposing); + } + //////////////////////////////////////////////////////////////////////////// + + #region //// Methods /////////// + + //////////////////////////////////////////////////////////////////////////// + public override void Init() + { + base.Init(); + + SkinLayer l = btnClose.Skin.Layers[lrButton]; + btnClose.Width = l.Width - btnClose.Skin.OriginMargins.Horizontal; + btnClose.Height = l.Height - btnClose.Skin.OriginMargins.Vertical; + btnClose.Left = OriginWidth - Skin.OriginMargins.Right - btnClose.Width + l.OffsetX; + btnClose.Top = Skin.OriginMargins.Top + l.OffsetY; + btnClose.Anchor = Anchors.Top | Anchors.Right; + + //SkinControl sc = new SkinControl(ClientArea.Skin); + //sc.Layers[0] = Skin.Layers[lrWindow]; + //ClientArea.Color = Color.Transparent; + //ClientArea.BackColor = Color.Transparent; + //ClientArea.Skin = sc; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected internal override void InitSkin() + { + base.InitSkin(); + Skin = new SkinControl(Manager.Skin.Controls[skWindow]); + AdjustMargins(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void btnClose_SkinChanged(object sender, EventArgs e) + { + btnClose.Skin = new SkinControl(Manager.Skin.Controls[skButton]); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + internal override void Render(Renderer renderer, GameTime gameTime) + { + if (Visible && Shadow) + { + SkinControl c = Manager.Skin.Controls[skShadow]; + SkinLayer l = c.Layers[lrShadow]; + + Color cl = Color.FromNonPremultiplied(l.States.Enabled.Color.R, l.States.Enabled.Color.G, l.States.Enabled.Color.B, Alpha); + + renderer.Begin(BlendingMode.Default); + renderer.DrawLayer(l, new Rectangle(Left - c.OriginMargins.Left, Top - c.OriginMargins.Top, Width + c.OriginMargins.Horizontal, Height + c.OriginMargins.Vertical), cl, 0); + renderer.End(); + } + base.Render(renderer, gameTime); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private Rectangle GetIconRect() + { + SkinLayer l1 = Skin.Layers[lrCaption]; + SkinLayer l5 = Skin.Layers[lrIcon]; + + int s = l1.Height - l1.ContentMargins.Vertical; + return new Rectangle(DrawingRect.Left + l1.ContentMargins.Left + l5.OffsetX, + DrawingRect.Top + l1.ContentMargins.Top + l5.OffsetY, + s, s); + + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime) + { + SkinLayer l1 = captionVisible ? Skin.Layers[lrCaption] : Skin.Layers[lrFrameTop]; + SkinLayer l2 = Skin.Layers[lrFrameLeft]; + SkinLayer l3 = Skin.Layers[lrFrameRight]; + SkinLayer l4 = Skin.Layers[lrFrameBottom]; + SkinLayer l5 = Skin.Layers[lrIcon]; + LayerStates s1, s2, s3, s4; + SpriteFont f1 = l1.Text.Font.Resource; + Color c1 = l1.Text.Colors.Enabled; + + if ((Focused || (Manager.FocusedControl != null && Manager.FocusedControl.Root == this.Root)) && ControlState != ControlState.Disabled) + { + s1 = l1.States.Focused; + s2 = l2.States.Focused; + s3 = l3.States.Focused; + s4 = l4.States.Focused; + c1 = l1.Text.Colors.Focused; + } + else if (ControlState == ControlState.Disabled) + { + s1 = l1.States.Disabled; + s2 = l2.States.Disabled; + s3 = l3.States.Disabled; + s4 = l4.States.Disabled; + c1 = l1.Text.Colors.Disabled; + } + else + { + s1 = l1.States.Enabled; + s2 = l2.States.Enabled; + s3 = l3.States.Enabled; + s4 = l4.States.Enabled; + c1 = l1.Text.Colors.Enabled; + } + + renderer.DrawLayer(Skin.Layers[lrWindow], rect, Skin.Layers[lrWindow].States.Enabled.Color, Skin.Layers[lrWindow].States.Enabled.Index); + + if (borderVisible) + { + renderer.DrawLayer(l1, new Rectangle(rect.Left, rect.Top, rect.Width, l1.Height), s1.Color, s1.Index); + renderer.DrawLayer(l2, new Rectangle(rect.Left, rect.Top + l1.Height, l2.Width, rect.Height - l1.Height - l4.Height), s2.Color, s2.Index); + renderer.DrawLayer(l3, new Rectangle(rect.Right - l3.Width, rect.Top + l1.Height, l3.Width, rect.Height - l1.Height - l4.Height), s3.Color, s3.Index); + renderer.DrawLayer(l4, new Rectangle(rect.Left, rect.Bottom - l4.Height, rect.Width, l4.Height), s4.Color, s4.Index); + + if (iconVisible && (icon != null || l5 != null) && captionVisible) + { + Texture2D i = (icon != null) ? icon : l5.Image.Resource; + renderer.Draw(i, GetIconRect(), Color.White); + } + + int icosize = 0; + if (l5 != null && iconVisible && captionVisible) + { + icosize = l1.Height - l1.ContentMargins.Vertical + 4 + l5.OffsetX; + } + int closesize = 0; + if (btnClose.Visible) + { + closesize = btnClose.Width - (btnClose.Skin.Layers[lrButton].OffsetX); + } + + Rectangle r = new Rectangle(rect.Left + l1.ContentMargins.Left + icosize, + rect.Top + l1.ContentMargins.Top, + rect.Width - l1.ContentMargins.Horizontal - closesize - icosize, + l1.Height - l1.ContentMargins.Top - l1.ContentMargins.Bottom); + int ox = l1.Text.OffsetX; + int oy = l1.Text.OffsetY; + renderer.DrawString(f1, Text, r, c1, l1.Text.Alignment, ox, oy, true); + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + void btnClose_Click(object sender, EventArgs e) + { + Close(ModalResult = ModalResult.Cancel); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + public virtual void Center() + { + Left = (Manager.ScreenWidth / 2) - (Width / 2); + Top = (Manager.ScreenHeight - Height) / 2; + } + //////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////// + protected override void OnResize(ResizeEventArgs e) + { + SetMovableArea(); + base.OnResize(e); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMoveBegin(EventArgs e) + { + base.OnMoveBegin(e); + + try + { + oldAlpha = Alpha; + Alpha = dragAlpha; + } + catch + { + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnMoveEnd(EventArgs e) + { + base.OnMoveEnd(e); + try + { + Alpha = oldAlpha; + } + catch + { + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void OnDoubleClick(EventArgs e) + { + base.OnDoubleClick(e); + + MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs) e : new MouseEventArgs(); + + if (IconVisible && ex.Button == MouseButton.Left) + { + Rectangle r = GetIconRect(); + r.Offset(AbsoluteLeft, AbsoluteTop); + if (r.Contains(ex.Position)) + { + Close(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + protected override void AdjustMargins() + { + + if (captionVisible && borderVisible) + { + ClientMargins = new Margins(Skin.ClientMargins.Left, Skin.Layers[lrCaption].Height, Skin.ClientMargins.Right, Skin.ClientMargins.Bottom); + } + else if (!captionVisible && borderVisible) + { + ClientMargins = new Margins(Skin.ClientMargins.Left, Skin.ClientMargins.Top, Skin.ClientMargins.Right, Skin.ClientMargins.Bottom); + } + else if (!borderVisible) + { + ClientMargins = new Margins(0, 0, 0, 0); + } + + if (btnClose != null) + { + btnClose.Visible = closeButtonVisible && captionVisible && borderVisible; + } + + SetMovableArea(); + + base.AdjustMargins(); + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + private void SetMovableArea() + { + if (captionVisible && borderVisible) + { + MovableArea = new Rectangle(Skin.OriginMargins.Left, Skin.OriginMargins.Top, Width, Skin.Layers[lrCaption].Height - Skin.OriginMargins.Top); + } + else if (!captionVisible) + { + MovableArea = new Rectangle(0, 0, Width, Height); + } + } + //////////////////////////////////////////////////////////////////////////// + + #endregion + } + + //////////////////////////////////////////////////////////////////////////// + + #endregion + +} diff --git a/SpacePew.Common/Constants.cs b/SpacePew.Common/Constants.cs new file mode 100644 index 0000000..073d85a --- /dev/null +++ b/SpacePew.Common/Constants.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace SpacePew.Common +{ + public static class Constants + { + public const int MasterServerPort = 31315; + public const int GameServerPort = 31316; + } +} diff --git a/SpacePew.Common/Properties/AssemblyInfo.cs b/SpacePew.Common/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..45c8809 --- /dev/null +++ b/SpacePew.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SpacePew.Common")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SpacePew.Common")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("20d7bd02-03b6-44ce-9a48-48f21010d705")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SpacePew.Common/SpacePew.Common.csproj b/SpacePew.Common/SpacePew.Common.csproj new file mode 100644 index 0000000..2c2486e --- /dev/null +++ b/SpacePew.Common/SpacePew.Common.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624} + Library + Properties + SpacePew.Common + SpacePew.Common + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpacePew.Common/UdpNetworkPacketType.cs b/SpacePew.Common/UdpNetworkPacketType.cs new file mode 100644 index 0000000..a64695d --- /dev/null +++ b/SpacePew.Common/UdpNetworkPacketType.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SpacePew.Common +{ + public enum UdpNetworkPacketType + { + PlayerJoining, + PlayerJoined, + PlayerDisconnecting, + PlayerDisconnected, + PlayerUpdate, + PlayerDying, + PlayerDied, + MessageSent, + MessageReceived, + EntityCreated, + EntitiesCreated, + RequestingScoreboard, + SendingScoreBoard, + LevelRequest, + LevelResponse, + RegisterHost, + RequestHostList, + RequestIntroduction + } +} diff --git a/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj new file mode 100644 index 0000000..02adf74 --- /dev/null +++ b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj @@ -0,0 +1,64 @@ + + + + + {944BAED2-53A4-47E9-AE89-7F6C5843DE94} + {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Windows + x86 + Library + Properties + ContentBuilder + IgnoreMe + v4.0 + Client + v4.0 + bin\$(Configuration) + Windows + HiDef + 3ba061e4-8ea0-40ef-a6b6-07a417ea841a + Library + x86 + false + + + Windows + + + Windows8 + + + Android + + + iOS + + + OSX + + + Linux + + + PSM + + + + + + + SpacePew.ContentContent + Content + + + + + + \ No newline at end of file diff --git a/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.Linux.cachefile b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.Linux.cachefile new file mode 100644 index 0000000..0248252 --- /dev/null +++ b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.Linux.cachefile @@ -0,0 +1,27 @@ +Content\Fonts\ConsoleFont.xnb +Content\Fonts\NetFont.xnb +Content\Fonts\ScoreBoardFont.xnb +Content\Fonts\Default.xnb +Content\Music\mosaik-locusfruit.xnb +Content\Audio\Waves\bingo.xnb +Content\Audio\Waves\bullet_hit.xnb +Content\Audio\Waves\bullet_sound.xnb +Content\Audio\Waves\engine.xnb +Content\Audio\Waves\explosion.xnb +Content\Audio\Waves\land.xnb +Content\Audio\Waves\thump.xnb +Content\bullet.xnb +Content\explosion.xnb +Content\explosion_small.xnb +Content\longshot.xnb +Content\ParticleTextures\explosion.xnb +Content\ParticleTextures\smoke.xnb +Content\player.xnb +Content\player_thrusting.xnb +Content\scoreboard.xnb +Content\stars.xnb +Content\transparent_pixel.xnb +Content\Music\mosaik-locusfruit.wma +Content\Audio\SpacePew.xgs +Content\Audio\SpacePew.xwb +Content\Audio\SpacePew.xsb diff --git a/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.PSM.cachefile b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.PSM.cachefile new file mode 100644 index 0000000..73abb50 --- /dev/null +++ b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.PSM.cachefile @@ -0,0 +1,24 @@ +Content\Fonts\ConsoleFont.xnb +Content\Fonts\NetFont.xnb +Content\Fonts\ScoreBoardFont.xnb +Content\Fonts\Default.xnb +Content\Audio\Waves\bingo.xnb +Content\Audio\Waves\bullet_hit.xnb +Content\Audio\Waves\bullet_sound.xnb +Content\Audio\Waves\engine.xnb +Content\Audio\Waves\explosion.xnb +Content\Audio\Waves\land.xnb +Content\Audio\Waves\thump.xnb +Content\Audio\Waves\beep.xnb +Content\bullet.xnb +Content\explosion.xnb +Content\explosion_small.xnb +Content\longshot.xnb +Content\ParticleTextures\explosion.xnb +Content\ParticleTextures\smoke.xnb +Content\player.xnb +Content\player_thrusting.xnb +Content\scoreboard.xnb +Content\stars.xnb +Content\transparent_pixel.xnb +Content\Audio\Waves\message.xnb diff --git a/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.Windows.cachefile b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.Windows.cachefile new file mode 100644 index 0000000..0248252 --- /dev/null +++ b/SpacePew.Content/SpacePew.Content/SpacePew.Content.csproj.Windows.cachefile @@ -0,0 +1,27 @@ +Content\Fonts\ConsoleFont.xnb +Content\Fonts\NetFont.xnb +Content\Fonts\ScoreBoardFont.xnb +Content\Fonts\Default.xnb +Content\Music\mosaik-locusfruit.xnb +Content\Audio\Waves\bingo.xnb +Content\Audio\Waves\bullet_hit.xnb +Content\Audio\Waves\bullet_sound.xnb +Content\Audio\Waves\engine.xnb +Content\Audio\Waves\explosion.xnb +Content\Audio\Waves\land.xnb +Content\Audio\Waves\thump.xnb +Content\bullet.xnb +Content\explosion.xnb +Content\explosion_small.xnb +Content\longshot.xnb +Content\ParticleTextures\explosion.xnb +Content\ParticleTextures\smoke.xnb +Content\player.xnb +Content\player_thrusting.xnb +Content\scoreboard.xnb +Content\stars.xnb +Content\transparent_pixel.xnb +Content\Music\mosaik-locusfruit.wma +Content\Audio\SpacePew.xgs +Content\Audio\SpacePew.xwb +Content\Audio\SpacePew.xsb diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/beep.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/beep.wav new file mode 100644 index 0000000..422b9f9 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/beep.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bingo.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bingo.wav new file mode 100644 index 0000000..2dd82e8 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bingo.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bullet_hit.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bullet_hit.wav new file mode 100644 index 0000000..96798cc Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bullet_hit.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bullet_sound.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bullet_sound.wav new file mode 100644 index 0000000..fc02f45 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/bullet_sound.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/engine.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/engine.wav new file mode 100644 index 0000000..fbba009 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/engine.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/explosion.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/explosion.wav new file mode 100644 index 0000000..6c783ea Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/explosion.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/land.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/land.wav new file mode 100644 index 0000000..d38b65f Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/land.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/message.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/message.wav new file mode 100644 index 0000000..31db131 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/message.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/thump.wav b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/thump.wav new file mode 100644 index 0000000..e99db92 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/Audio/Waves/thump.wav differ diff --git a/SpacePew.Content/SpacePew.ContentContent/Fonts/ConsoleFont.spritefont b/SpacePew.Content/SpacePew.ContentContent/Fonts/ConsoleFont.spritefont new file mode 100644 index 0000000..4e06676 --- /dev/null +++ b/SpacePew.Content/SpacePew.ContentContent/Fonts/ConsoleFont.spritefont @@ -0,0 +1,60 @@ + + + + + + + Kootenay + + + 14 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + diff --git a/SpacePew.Content/SpacePew.ContentContent/Fonts/Default.spritefont b/SpacePew.Content/SpacePew.ContentContent/Fonts/Default.spritefont new file mode 100644 index 0000000..88af8a4 --- /dev/null +++ b/SpacePew.Content/SpacePew.ContentContent/Fonts/Default.spritefont @@ -0,0 +1,66 @@ + + + + + + + Tahoma + + + 8 + + + 1 + + + false + + + + + + ? + + + + + + · + + + + + + + + + \ No newline at end of file diff --git a/SpacePew.Content/SpacePew.ContentContent/Fonts/NetFont.spritefont b/SpacePew.Content/SpacePew.ContentContent/Fonts/NetFont.spritefont new file mode 100644 index 0000000..105adff --- /dev/null +++ b/SpacePew.Content/SpacePew.ContentContent/Fonts/NetFont.spritefont @@ -0,0 +1,60 @@ + + + + + + + Kootenay + + + 10 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + diff --git a/SpacePew.Content/SpacePew.ContentContent/Fonts/ScoreBoardFont.spritefont b/SpacePew.Content/SpacePew.ContentContent/Fonts/ScoreBoardFont.spritefont new file mode 100644 index 0000000..0adaa85 --- /dev/null +++ b/SpacePew.Content/SpacePew.ContentContent/Fonts/ScoreBoardFont.spritefont @@ -0,0 +1,60 @@ + + + + + + + Kootenay + + + 9 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + diff --git a/SpacePew.Content/SpacePew.ContentContent/ParticleTextures/explosion.png b/SpacePew.Content/SpacePew.ContentContent/ParticleTextures/explosion.png new file mode 100644 index 0000000..9714bf1 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/ParticleTextures/explosion.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/ParticleTextures/smoke.bmp b/SpacePew.Content/SpacePew.ContentContent/ParticleTextures/smoke.bmp new file mode 100644 index 0000000..72f6e45 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/ParticleTextures/smoke.bmp differ diff --git a/SpacePew.Content/SpacePew.ContentContent/SpacePew.ContentContent.contentproj b/SpacePew.Content/SpacePew.ContentContent/SpacePew.ContentContent.contentproj new file mode 100644 index 0000000..259cf68 --- /dev/null +++ b/SpacePew.Content/SpacePew.ContentContent/SpacePew.ContentContent.contentproj @@ -0,0 +1,192 @@ + + + + {F5811120-B89A-4A2E-A0BD-551E9F06510D} + {96E2B04D-8817-42c6-938A-82C39BA4D311};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Windows + x86 + Library + Properties + SpacePew.ContentContentContent + v4.0 + v4.0 + Windows + bin\$(MonoGamePlatform)\$(Configuration) + Content + x86 + + + Windows + + + Windows8 + + + Android + + + iOS + + + OSX + + + Linux + + + PSM + + + + + + + + + + $(MSBuildExtensionsPath)\MonoGame\v3.0\MonoGameContentProcessors.dll + + + + + ConsoleFont + FontDescriptionImporter + FontDescriptionProcessor + + + NetFont + FontDescriptionImporter + FontDescriptionProcessor + + + ScoreBoardFont + FontDescriptionImporter + FontDescriptionProcessor + + + + + bingo + WavImporter + SoundEffectProcessor + + + bullet_hit + WavImporter + SoundEffectProcessor + + + bullet_sound + WavImporter + SoundEffectProcessor + + + engine + WavImporter + SoundEffectProcessor + + + explosion + WavImporter + SoundEffectProcessor + + + land + WavImporter + SoundEffectProcessor + + + thump + WavImporter + SoundEffectProcessor + + + bullet + TextureImporter + TextureProcessor + + + explosion + TextureImporter + TextureProcessor + + + explosion_small + TextureImporter + TextureProcessor + + + longshot + TextureImporter + TextureProcessor + + + explosion + TextureImporter + TextureProcessor + + + smoke + TextureImporter + TextureProcessor + + + player + TextureImporter + TextureProcessor + + + player_thrusting + TextureImporter + TextureProcessor + + + scoreboard + TextureImporter + TextureProcessor + + + stars + TextureImporter + TextureProcessor + + + transparent_pixel + TextureImporter + TextureProcessor + + + + + Default + FontDescriptionImporter + FontDescriptionProcessor + + + + + + + + beep + WavImporter + SoundEffectProcessor + + + + + message + WavImporter + SoundEffectProcessor + + + + + \ No newline at end of file diff --git a/SpacePew.Content/SpacePew.ContentContent/bullet.png b/SpacePew.Content/SpacePew.ContentContent/bullet.png new file mode 100644 index 0000000..7449741 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/bullet.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/explosion.png b/SpacePew.Content/SpacePew.ContentContent/explosion.png new file mode 100644 index 0000000..3bce9d8 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/explosion.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/explosion_small.png b/SpacePew.Content/SpacePew.ContentContent/explosion_small.png new file mode 100644 index 0000000..c671d1d Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/explosion_small.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/longshot.png b/SpacePew.Content/SpacePew.ContentContent/longshot.png new file mode 100644 index 0000000..11732c6 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/longshot.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/player.png b/SpacePew.Content/SpacePew.ContentContent/player.png new file mode 100644 index 0000000..5c6226e Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/player.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/player_thrusting.png b/SpacePew.Content/SpacePew.ContentContent/player_thrusting.png new file mode 100644 index 0000000..6c07b7e Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/player_thrusting.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/scoreboard.png b/SpacePew.Content/SpacePew.ContentContent/scoreboard.png new file mode 100644 index 0000000..748a746 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/scoreboard.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/stars.png b/SpacePew.Content/SpacePew.ContentContent/stars.png new file mode 100644 index 0000000..196ad80 Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/stars.png differ diff --git a/SpacePew.Content/SpacePew.ContentContent/transparent_pixel.png b/SpacePew.Content/SpacePew.ContentContent/transparent_pixel.png new file mode 100644 index 0000000..c9ab1fa Binary files /dev/null and b/SpacePew.Content/SpacePew.ContentContent/transparent_pixel.png differ diff --git a/SpacePew.MasterServer/App.config b/SpacePew.MasterServer/App.config new file mode 100644 index 0000000..d0feca6 --- /dev/null +++ b/SpacePew.MasterServer/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/SpacePew.MasterServer/GameServer.cs b/SpacePew.MasterServer/GameServer.cs new file mode 100644 index 0000000..2f85849 --- /dev/null +++ b/SpacePew.MasterServer/GameServer.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace SpacePew.MasterServer +{ + public class GameServer + { + public long Id { get; set; } + public IPEndPoint[] Endpoints { get; set; } + public DateTime Updated { get; set; } + } +} diff --git a/SpacePew.MasterServer/Program.cs b/SpacePew.MasterServer/Program.cs new file mode 100644 index 0000000..0caa2a2 --- /dev/null +++ b/SpacePew.MasterServer/Program.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace SpacePew.MasterServer +{ + public class Program + { + static void Main(string[] args) + { + var server = new Server(); + server.Run(); + } + } +} diff --git a/SpacePew.MasterServer/Properties/AssemblyInfo.cs b/SpacePew.MasterServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8263435 --- /dev/null +++ b/SpacePew.MasterServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SpacePew.MasterServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SpacePew.MasterServer")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d8783d9d-5612-4a01-afdf-57a65560d6fd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SpacePew.MasterServer/Server.cs b/SpacePew.MasterServer/Server.cs new file mode 100644 index 0000000..b086a02 --- /dev/null +++ b/SpacePew.MasterServer/Server.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +using Lidgren.Network; + +using SpacePew.Common; + +namespace SpacePew.MasterServer +{ + public class Server + { + public void Run() + { + var registeredHosts = new List(); + + var config = new NetPeerConfiguration("masterserver"); + config.SetMessageTypeEnabled(NetIncomingMessageType.UnconnectedData, true); + config.Port = Constants.MasterServerPort; + + var peer = new NetPeer(config); + peer.Start(); + + Console.WriteLine("Press ESC to quit"); + while (!Console.KeyAvailable || Console.ReadKey().Key != ConsoleKey.Escape) + { + var removed = registeredHosts.RemoveAll(g => g.Updated <= DateTime.Now.AddSeconds(-50)); + if (removed > 0) + { + Console.WriteLine("Removed {0} hosts from master server list", removed); + } + + NetIncomingMessage msg; + while ((msg = peer.ReadMessage()) != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.UnconnectedData: + switch ((UdpNetworkPacketType)msg.ReadByte()) + { + case UdpNetworkPacketType.RegisterHost: + var id = msg.ReadInt64(); + + Console.WriteLine("Got registration for host " + id); + + var host = registeredHosts.FirstOrDefault(s => s.Id == id); + if (host == null) + { + registeredHosts.Add(new GameServer() + { + Id = id, + Endpoints = new IPEndPoint[] + { + msg.ReadIPEndPoint(), + msg.SenderEndPoint + }, + Updated = DateTime.Now + }); + } + else + { + host.Updated = DateTime.Now; + } + break; + + case UdpNetworkPacketType.RequestHostList: + Console.WriteLine("Sending list of " + registeredHosts.Count + " hosts to client " + msg.SenderEndPoint); + foreach (var server in registeredHosts) + { + var message = peer.CreateMessage(); + message.Write(server.Id); + message.Write(server.Endpoints[0]); + message.Write(server.Endpoints[1]); + peer.SendUnconnectedMessage(message, msg.SenderEndPoint); + } + + break; + case UdpNetworkPacketType.RequestIntroduction: + var clientInternal = msg.ReadIPEndPoint(); + long hostId = msg.ReadInt64(); + string token = msg.ReadString(); + + Console.WriteLine(msg.SenderEndPoint + " requesting introduction to " + hostId + " (token " + token + ")"); + + var host2 = registeredHosts.FirstOrDefault(s => s.Id == hostId); + if (host2 != null) + { + Console.WriteLine("Sending introduction..."); + peer.Introduce( + host2.Endpoints[0], // host internal + host2.Endpoints[1], // host external + clientInternal, // client internal + msg.SenderEndPoint, // client external + token // request token + ); + } + else + { + Console.WriteLine("Client requested introduction to nonlisted host"); + } + break; + } + break; + + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.WarningMessage: + case NetIncomingMessageType.ErrorMessage: + Console.WriteLine(msg.ReadString()); + break; + } + } + } + + peer.Shutdown("bye"); + } + } +} diff --git a/SpacePew.MasterServer/SpacePew.MasterServer.csproj b/SpacePew.MasterServer/SpacePew.MasterServer.csproj new file mode 100644 index 0000000..793928d --- /dev/null +++ b/SpacePew.MasterServer/SpacePew.MasterServer.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C} + Exe + Properties + SpacePew.MasterServer + SpacePew.MasterServer + v4.5.1 + 512 + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + {49ba1c69-6104-41ac-a5d8-b54fa9f696e8} + Lidgren.Network + + + {ba98d4ca-718b-4e50-ad4d-f48e8ca67624} + SpacePew.Common + + + + + \ No newline at end of file diff --git a/SpacePew.sln b/SpacePew.sln new file mode 100644 index 0000000..4ffa17f --- /dev/null +++ b/SpacePew.sln @@ -0,0 +1,561 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31010.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{91E40179-82B4-46A6-ACA4-F1CE7A36C0F6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{E6EF59CA-FDE1-4AE8-8A8F-3C83D8730EE3}" + ProjectSection(SolutionItems) = preProject + ..\Library\ICSharpCode.SharpZipLib.dll = ..\Library\ICSharpCode.SharpZipLib.dll + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpacePew", "SpacePew\SpacePew.csproj", "{1C82F1B3-6F3C-47EC-901A-E656D037F862}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpacePew.Content", "SpacePew.Content\SpacePew.Content\SpacePew.Content.csproj", "{944BAED2-53A4-47E9-AE89-7F6C5843DE94}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpacePew.ContentContent", "SpacePew.Content\SpacePew.ContentContent\SpacePew.ContentContent.contentproj", "{F5811120-B89A-4A2E-A0BD-551E9F06510D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lidgren.Network", "Lidgren.Network\Lidgren.Network.csproj", "{49BA1C69-6104-41AC-A5D8-B54FA9F696E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TomShane.Neoforce.Controls", "Neoforce\TomShane.Neoforce.Controls.csproj", "{AC5F1CD8-AA8E-4DB5-814F-86C214175841}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpacePew.MasterServer", "SpacePew.MasterServer\SpacePew.MasterServer.csproj", "{9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpacePew.Common", "SpacePew.Common\SpacePew.Common.csproj", "{BA98D4CA-718B-4E50-AD4D-F48E8CA67624}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Android|Any CPU = Android|Any CPU + Android|Mixed Platforms = Android|Mixed Platforms + Android|x86 = Android|x86 + Android|Xbox 360 = Android|Xbox 360 + Android|Zune = Android|Zune + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Debug|Xbox 360 = Debug|Xbox 360 + Debug|Zune = Debug|Zune + iOS|Any CPU = iOS|Any CPU + iOS|Mixed Platforms = iOS|Mixed Platforms + iOS|x86 = iOS|x86 + iOS|Xbox 360 = iOS|Xbox 360 + iOS|Zune = iOS|Zune + Linux|Any CPU = Linux|Any CPU + Linux|Mixed Platforms = Linux|Mixed Platforms + Linux|x86 = Linux|x86 + Linux|Xbox 360 = Linux|Xbox 360 + Linux|Zune = Linux|Zune + OSX|Any CPU = OSX|Any CPU + OSX|Mixed Platforms = OSX|Mixed Platforms + OSX|x86 = OSX|x86 + OSX|Xbox 360 = OSX|Xbox 360 + OSX|Zune = OSX|Zune + PSM|Any CPU = PSM|Any CPU + PSM|Mixed Platforms = PSM|Mixed Platforms + PSM|x86 = PSM|x86 + PSM|Xbox 360 = PSM|Xbox 360 + PSM|Zune = PSM|Zune + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + Release|Xbox 360 = Release|Xbox 360 + Release|Zune = Release|Zune + ReleaseWithStatistics|Any CPU = ReleaseWithStatistics|Any CPU + ReleaseWithStatistics|Mixed Platforms = ReleaseWithStatistics|Mixed Platforms + ReleaseWithStatistics|x86 = ReleaseWithStatistics|x86 + ReleaseWithStatistics|Xbox 360 = ReleaseWithStatistics|Xbox 360 + ReleaseWithStatistics|Zune = ReleaseWithStatistics|Zune + Windows|Any CPU = Windows|Any CPU + Windows|Mixed Platforms = Windows|Mixed Platforms + Windows|x86 = Windows|x86 + Windows|Xbox 360 = Windows|Xbox 360 + Windows|Zune = Windows|Zune + Windows8|Any CPU = Windows8|Any CPU + Windows8|Mixed Platforms = Windows8|Mixed Platforms + Windows8|x86 = Windows8|x86 + Windows8|Xbox 360 = Windows8|Xbox 360 + Windows8|Zune = Windows8|Zune + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Android|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Android|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Android|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Android|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Android|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Android|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Android|Zune.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Debug|Any CPU.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Debug|x86.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Debug|x86.Build.0 = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Debug|Xbox 360.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Debug|Zune.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.iOS|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.iOS|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.iOS|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.iOS|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.iOS|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.iOS|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.iOS|Zune.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Linux|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Linux|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Linux|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Linux|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Linux|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Linux|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Linux|Zune.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.OSX|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.OSX|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.OSX|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.OSX|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.OSX|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.OSX|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.OSX|Zune.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.PSM|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.PSM|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.PSM|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.PSM|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.PSM|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.PSM|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.PSM|Zune.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Release|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Release|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Release|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Release|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Release|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Release|Zune.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.ReleaseWithStatistics|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.ReleaseWithStatistics|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.ReleaseWithStatistics|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.ReleaseWithStatistics|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.ReleaseWithStatistics|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.ReleaseWithStatistics|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.ReleaseWithStatistics|Zune.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows|Zune.ActiveCfg = Debug|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows8|Any CPU.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows8|Mixed Platforms.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows8|Mixed Platforms.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows8|x86.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows8|x86.Build.0 = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows8|Xbox 360.ActiveCfg = Release|x86 + {1C82F1B3-6F3C-47EC-901A-E656D037F862}.Windows8|Zune.ActiveCfg = Debug|x86 + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Android|Any CPU.ActiveCfg = Android|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Android|Any CPU.Build.0 = Android|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Android|Mixed Platforms.ActiveCfg = Android|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Android|Mixed Platforms.Build.0 = Android|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Android|x86.ActiveCfg = Android|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Android|Xbox 360.ActiveCfg = Android|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Android|Zune.ActiveCfg = Android|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Debug|Any CPU.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Debug|Any CPU.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Debug|Mixed Platforms.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Debug|Mixed Platforms.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Debug|x86.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Debug|Xbox 360.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Debug|Zune.ActiveCfg = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.iOS|Any CPU.ActiveCfg = iOS|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.iOS|Any CPU.Build.0 = iOS|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.iOS|Mixed Platforms.ActiveCfg = iOS|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.iOS|Mixed Platforms.Build.0 = iOS|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.iOS|x86.ActiveCfg = iOS|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.iOS|Xbox 360.ActiveCfg = iOS|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.iOS|Zune.ActiveCfg = iOS|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Linux|Any CPU.Build.0 = Linux|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Linux|Mixed Platforms.ActiveCfg = Linux|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Linux|Mixed Platforms.Build.0 = Linux|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Linux|x86.ActiveCfg = Linux|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Linux|Xbox 360.ActiveCfg = Linux|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Linux|Zune.ActiveCfg = Linux|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.OSX|Any CPU.ActiveCfg = OSX|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.OSX|Any CPU.Build.0 = OSX|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.OSX|Mixed Platforms.ActiveCfg = OSX|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.OSX|Mixed Platforms.Build.0 = OSX|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.OSX|x86.ActiveCfg = OSX|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.OSX|Xbox 360.ActiveCfg = OSX|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.OSX|Zune.ActiveCfg = OSX|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.PSM|Any CPU.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.PSM|Any CPU.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.PSM|Mixed Platforms.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.PSM|Mixed Platforms.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.PSM|x86.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.PSM|Xbox 360.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.PSM|Zune.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Release|Any CPU.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Release|Any CPU.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Release|Mixed Platforms.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Release|Mixed Platforms.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Release|x86.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Release|Xbox 360.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Release|Zune.ActiveCfg = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.ReleaseWithStatistics|Any CPU.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.ReleaseWithStatistics|Any CPU.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.ReleaseWithStatistics|Mixed Platforms.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.ReleaseWithStatistics|Mixed Platforms.Build.0 = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.ReleaseWithStatistics|x86.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.ReleaseWithStatistics|Xbox 360.ActiveCfg = PSM|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.ReleaseWithStatistics|Zune.ActiveCfg = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows|Any CPU.ActiveCfg = Windows|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows|Any CPU.Build.0 = Windows|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows|Mixed Platforms.ActiveCfg = Windows|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows|Mixed Platforms.Build.0 = Windows|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows|x86.ActiveCfg = Windows|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows|Xbox 360.ActiveCfg = Windows|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows|Zune.ActiveCfg = Windows|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows8|Any CPU.ActiveCfg = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows8|Any CPU.Build.0 = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows8|Mixed Platforms.ActiveCfg = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows8|Mixed Platforms.Build.0 = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows8|x86.ActiveCfg = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows8|Xbox 360.ActiveCfg = Windows8|Any CPU + {944BAED2-53A4-47E9-AE89-7F6C5843DE94}.Windows8|Zune.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Android|Any CPU.ActiveCfg = Android|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Android|Mixed Platforms.ActiveCfg = Android|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Android|x86.ActiveCfg = Android|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Android|Xbox 360.ActiveCfg = Android|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Android|Zune.ActiveCfg = Android|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Debug|Any CPU.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Debug|Mixed Platforms.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Debug|x86.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Debug|Xbox 360.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Debug|Zune.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.iOS|Any CPU.ActiveCfg = iOS|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.iOS|Mixed Platforms.ActiveCfg = iOS|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.iOS|x86.ActiveCfg = iOS|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.iOS|Xbox 360.ActiveCfg = iOS|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.iOS|Zune.ActiveCfg = iOS|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Linux|Mixed Platforms.ActiveCfg = Linux|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Linux|x86.ActiveCfg = Linux|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Linux|Xbox 360.ActiveCfg = Linux|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Linux|Zune.ActiveCfg = Linux|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.OSX|Any CPU.ActiveCfg = OSX|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.OSX|Mixed Platforms.ActiveCfg = OSX|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.OSX|x86.ActiveCfg = OSX|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.OSX|Xbox 360.ActiveCfg = OSX|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.OSX|Zune.ActiveCfg = OSX|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.PSM|Any CPU.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.PSM|Mixed Platforms.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.PSM|x86.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.PSM|Xbox 360.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.PSM|Zune.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Release|Any CPU.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Release|Mixed Platforms.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Release|x86.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Release|Xbox 360.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Release|Zune.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.ReleaseWithStatistics|Any CPU.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.ReleaseWithStatistics|Mixed Platforms.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.ReleaseWithStatistics|x86.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.ReleaseWithStatistics|Xbox 360.ActiveCfg = PSM|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.ReleaseWithStatistics|Zune.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows|Any CPU.ActiveCfg = Windows|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows|Mixed Platforms.ActiveCfg = Windows|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows|x86.ActiveCfg = Windows|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows|Xbox 360.ActiveCfg = Windows|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows|Zune.ActiveCfg = Windows|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows8|Any CPU.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows8|Mixed Platforms.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows8|x86.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows8|Xbox 360.ActiveCfg = Windows8|Any CPU + {F5811120-B89A-4A2E-A0BD-551E9F06510D}.Windows8|Zune.ActiveCfg = Windows8|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Android|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Android|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Android|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Android|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Android|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Android|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Android|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Debug|x86.ActiveCfg = Debug|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Debug|Xbox 360.ActiveCfg = Debug|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Debug|Zune.ActiveCfg = Debug|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.iOS|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.iOS|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.iOS|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.iOS|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.iOS|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.iOS|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.iOS|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Linux|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Linux|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Linux|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Linux|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Linux|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Linux|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Linux|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.OSX|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.OSX|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.OSX|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.OSX|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.OSX|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.OSX|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.OSX|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.PSM|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.PSM|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.PSM|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.PSM|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.PSM|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.PSM|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.PSM|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Release|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Release|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Release|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Release|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.ReleaseWithStatistics|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.ReleaseWithStatistics|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.ReleaseWithStatistics|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.ReleaseWithStatistics|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.ReleaseWithStatistics|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.ReleaseWithStatistics|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.ReleaseWithStatistics|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows|Zune.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows8|Any CPU.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows8|Any CPU.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows8|Mixed Platforms.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows8|Mixed Platforms.Build.0 = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows8|x86.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows8|Xbox 360.ActiveCfg = Release|Any CPU + {49BA1C69-6104-41AC-A5D8-B54FA9F696E8}.Windows8|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Android|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Android|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Android|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Android|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Android|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Android|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Android|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Debug|x86.ActiveCfg = Debug|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Debug|Xbox 360.ActiveCfg = Debug|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Debug|Zune.ActiveCfg = Debug|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.iOS|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.iOS|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.iOS|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.iOS|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.iOS|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.iOS|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.iOS|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Linux|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Linux|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Linux|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Linux|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Linux|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Linux|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Linux|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.OSX|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.OSX|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.OSX|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.OSX|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.OSX|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.OSX|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.OSX|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.PSM|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.PSM|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.PSM|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.PSM|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.PSM|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.PSM|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.PSM|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Release|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Release|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Release|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Release|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.ReleaseWithStatistics|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.ReleaseWithStatistics|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.ReleaseWithStatistics|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.ReleaseWithStatistics|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.ReleaseWithStatistics|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.ReleaseWithStatistics|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.ReleaseWithStatistics|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows|Zune.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows8|Any CPU.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows8|Any CPU.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows8|Mixed Platforms.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows8|Mixed Platforms.Build.0 = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows8|x86.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows8|Xbox 360.ActiveCfg = Release|Any CPU + {AC5F1CD8-AA8E-4DB5-814F-86C214175841}.Windows8|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Android|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Android|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Android|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Android|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Android|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Android|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Android|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Debug|x86.ActiveCfg = Debug|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Debug|Xbox 360.ActiveCfg = Debug|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Debug|Zune.ActiveCfg = Debug|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.iOS|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.iOS|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.iOS|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.iOS|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.iOS|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.iOS|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.iOS|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Linux|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Linux|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Linux|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Linux|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Linux|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Linux|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Linux|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.OSX|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.OSX|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.OSX|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.OSX|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.OSX|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.OSX|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.OSX|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.PSM|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.PSM|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.PSM|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.PSM|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.PSM|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.PSM|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.PSM|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Release|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Release|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Release|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Release|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.ReleaseWithStatistics|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.ReleaseWithStatistics|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.ReleaseWithStatistics|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.ReleaseWithStatistics|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.ReleaseWithStatistics|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.ReleaseWithStatistics|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.ReleaseWithStatistics|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows|Zune.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows8|Any CPU.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows8|Any CPU.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows8|Mixed Platforms.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows8|Mixed Platforms.Build.0 = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows8|x86.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows8|Xbox 360.ActiveCfg = Release|Any CPU + {9BFE240A-AEE0-4FD6-80B8-5846DCFAE95C}.Windows8|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Android|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Android|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Android|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Android|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Android|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Android|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Android|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Debug|x86.ActiveCfg = Debug|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Debug|Xbox 360.ActiveCfg = Debug|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Debug|Zune.ActiveCfg = Debug|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.iOS|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.iOS|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.iOS|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.iOS|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.iOS|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.iOS|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.iOS|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Linux|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Linux|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Linux|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Linux|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Linux|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Linux|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Linux|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.OSX|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.OSX|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.OSX|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.OSX|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.OSX|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.OSX|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.OSX|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.PSM|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.PSM|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.PSM|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.PSM|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.PSM|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.PSM|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.PSM|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Release|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Release|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Release|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Release|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.ReleaseWithStatistics|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.ReleaseWithStatistics|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.ReleaseWithStatistics|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.ReleaseWithStatistics|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.ReleaseWithStatistics|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.ReleaseWithStatistics|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.ReleaseWithStatistics|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows|Zune.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows8|Any CPU.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows8|Any CPU.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows8|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows8|Mixed Platforms.Build.0 = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows8|x86.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows8|Xbox 360.ActiveCfg = Release|Any CPU + {BA98D4CA-718B-4E50-AD4D-F48E8CA67624}.Windows8|Zune.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E6EF59CA-FDE1-4AE8-8A8F-3C83D8730EE3} = {91E40179-82B4-46A6-ACA4-F1CE7A36C0F6} + EndGlobalSection + GlobalSection(TestCaseManagementSettings) = postSolution + CategoryFile = GravityForce1.vsmdi + EndGlobalSection +EndGlobal diff --git a/SpacePew/Arena.cs b/SpacePew/Arena.cs new file mode 100644 index 0000000..275cfe1 --- /dev/null +++ b/SpacePew/Arena.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; + +namespace SpacePew +{ + public class Arena : DrawableGameComponent + { + public Arena(Game game) : base(game) + { + } + + protected override void LoadContent() + { + base.LoadContent(); + } + + public override void Update(GameTime gameTime) + { + base.Update(gameTime); + } + + public override void Draw(GameTime gameTime) + { + base.Draw(gameTime); + } + } +} diff --git a/SpacePew/Camera/Camera2D.cs b/SpacePew/Camera/Camera2D.cs new file mode 100644 index 0000000..cf37e07 --- /dev/null +++ b/SpacePew/Camera/Camera2D.cs @@ -0,0 +1,93 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace SpacePew.Camera +{ + public class Camera2D : GameComponent, ICamera2D + { + private Vector2 _position; + protected float _viewportHeight; + protected float _viewportWidth; + + public Camera2D(Game game) : base(game) { } + + #region Properties + + public Vector2 Position + { + get { return _position; } + set { _position = value; } + } + + public float Rotation { get; set; } + public Vector2 Origin { get; set; } + public float Scale { get; set; } + public Vector2 ScreenCenter { get; protected set; } + public Matrix Transform { get; set; } + public IFocusable Focus { get; set; } + public float MoveSpeed { get; set; } + + #endregion + + /// + /// Called when the GameComponent needs to be initialized. + /// + public override void Initialize() + { + _viewportWidth = Game.GraphicsDevice.Viewport.Width; + _viewportHeight = Game.GraphicsDevice.Viewport.Height; + + ScreenCenter = new Vector2(_viewportWidth / 2, _viewportHeight / 2); + Scale = 1; + MoveSpeed = 5f; + + base.Initialize(); + } + + public override void Update(GameTime gameTime) + { + // Create the Transform used by any + // spritebatch process + Transform = Matrix.Identity * + Matrix.CreateTranslation(-Position.X, -Position.Y, 0) * + Matrix.CreateRotationZ(Rotation) * + Matrix.CreateTranslation(Origin.X, Origin.Y, 0) * + Matrix.CreateScale(new Vector3(Scale, Scale, Scale)); + + Origin = ScreenCenter / Scale; + + // Move the Camera to the position that it needs to go + var delta = (float)gameTime.ElapsedGameTime.TotalSeconds; + + _position.X += (Focus.Position.X - Position.X) * MoveSpeed * delta; + _position.Y += (Focus.Position.Y - Position.Y) * MoveSpeed * delta; + + base.Update(gameTime); + } + + /// + /// Determines whether the target is in view given the specified position. + /// This can be used to increase performance by not drawing objects + /// directly in the viewport + /// + /// The position. + /// The texture. + /// + /// true if [is in view] [the specified position]; otherwise, false. + /// + public bool IsInView(Vector2 position, Texture2D texture) + { + // If the object is not within the horizontal bounds of the screen + + if ((position.X + texture.Width) < (Position.X - Origin.X) || (position.X) > (Position.X + Origin.X)) + return false; + + // If the object is not within the vertical bounds of the screen + if ((position.Y + texture.Height) < (Position.Y - Origin.Y) || (position.Y) > (Position.Y + Origin.Y)) + return false; + + // In View + return true; + } + } +} diff --git a/SpacePew/Camera/ICamera2D.cs b/SpacePew/Camera/ICamera2D.cs new file mode 100644 index 0000000..30458f0 --- /dev/null +++ b/SpacePew/Camera/ICamera2D.cs @@ -0,0 +1,72 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace SpacePew.Camera +{ + public interface ICamera2D + { + /// + /// Gets or sets the position of the camera + /// + /// The position. + Vector2 Position { get; set; } + + /// + /// Gets or sets the move speed of the camera. + /// The camera will tween to its destination. + /// + /// The move speed. + float MoveSpeed { get; set; } + + /// + /// Gets or sets the rotation of the camera. + /// + /// The rotation. + float Rotation { get; set; } + + /// + /// Gets the origin of the viewport (accounts for Scale) + /// + /// The origin. + Vector2 Origin { get; } + + /// + /// Gets or sets the scale of the Camera + /// + /// The scale. + float Scale { get; set; } + + /// + /// Gets the screen center (does not account for Scale) + /// + /// The screen center. + Vector2 ScreenCenter { get; } + + /// + /// Gets the transform that can be applied to + /// the SpriteBatch Class. + /// + /// + /// The transform. + Matrix Transform { get; } + + /// + /// Gets or sets the focus of the Camera. + /// + /// + /// The focus. + IFocusable Focus { get; set; } + + /// + /// Determines whether the target is in view given the specified position. + /// This can be used to increase performance by not drawing objects + /// directly in the viewport + /// + /// The position. + /// The texture. + /// + /// true if the target is in view at the specified position; otherwise, false. + /// + bool IsInView(Vector2 position, Texture2D texture); + } +} \ No newline at end of file diff --git a/SpacePew/Camera/IFocusable.cs b/SpacePew/Camera/IFocusable.cs new file mode 100644 index 0000000..79aaecf --- /dev/null +++ b/SpacePew/Camera/IFocusable.cs @@ -0,0 +1,9 @@ +using Microsoft.Xna.Framework; + +namespace SpacePew.Camera +{ + public interface IFocusable + { + Vector2 Position { get; } + } +} \ No newline at end of file diff --git a/SpacePew/Content/Skins/Blue.skin b/SpacePew/Content/Skins/Blue.skin new file mode 100644 index 0000000..72b1c5a Binary files /dev/null and b/SpacePew/Content/Skins/Blue.skin differ diff --git a/SpacePew/Content/Skins/Default.skin b/SpacePew/Content/Skins/Default.skin new file mode 100644 index 0000000..edaa82c Binary files /dev/null and b/SpacePew/Content/Skins/Default.skin differ diff --git a/SpacePew/Content/Skins/Green.skin b/SpacePew/Content/Skins/Green.skin new file mode 100644 index 0000000..145c7e1 Binary files /dev/null and b/SpacePew/Content/Skins/Green.skin differ diff --git a/SpacePew/Content/Skins/Magenta.skin b/SpacePew/Content/Skins/Magenta.skin new file mode 100644 index 0000000..3848d3c Binary files /dev/null and b/SpacePew/Content/Skins/Magenta.skin differ diff --git a/SpacePew/Content/Skins/Purple.skin b/SpacePew/Content/Skins/Purple.skin new file mode 100644 index 0000000..3db20ce Binary files /dev/null and b/SpacePew/Content/Skins/Purple.skin differ diff --git a/SpacePew/Downloads/Levels/dummy.txt b/SpacePew/Downloads/Levels/dummy.txt new file mode 100644 index 0000000..e69de29 diff --git a/SpacePew/EntityFactory.cs b/SpacePew/EntityFactory.cs new file mode 100644 index 0000000..788bf9c --- /dev/null +++ b/SpacePew/EntityFactory.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using SpacePew.Models; + +namespace SpacePew +{ + public class EntityFactory + { + public static EntityFactory Instance { get; private set; } + + private MainGame _game; + private readonly List _entities = new List(); + + public IList Entities + { + get { return _entities; } + } + + public void RemoveEntities(IEnumerable entities) + { + Action a = e => + { + e.Collide(null); + _entities.Remove(e); + }; + + entities.ToList().ForEach(e => + { + if (e is IKillable) + (e as IKillable).Kill(); + + _entities.Remove(e); + }); + } + + public void RemoveEntity(IEntity entity) + { + _entities.Remove(entity); + } + + public EntityFactory(MainGame game) + { + Instance = this; + this._game = game; + } + + public T CreateEntity(string owner, Vector2 position, Vector2 velocity, float angle) where T : IEntity, new() + { + var entity = new T(); + + var tex = TextureManager.LoadTexture(entity.TextureName); + entity.Texture = tex; + entity.Position = position; + entity.Velocity = velocity; + entity.Angle = angle; + + entity.Owner = owner; + + this._entities.Add(entity); + + entity.Created(); + + return entity; + } + + public T CreateEntity(Type entityType, string owner, Vector2 position, Vector2 velocity, float angle) where T : IEntity + { + T entity = (T)entityType.Assembly.CreateInstance(entityType.FullName); + + Texture2D tex = TextureManager.LoadTexture(entity.TextureName); + entity.Texture = tex; + entity.Position = position; + entity.Velocity = velocity; + entity.Angle = angle; + + entity.Owner = owner; + + this._entities.Add(entity); + + entity.Created(); + + return entity; + } + } +} diff --git a/SpacePew/Extensions/LidgrenExtensions.cs b/SpacePew/Extensions/LidgrenExtensions.cs new file mode 100644 index 0000000..349702e --- /dev/null +++ b/SpacePew/Extensions/LidgrenExtensions.cs @@ -0,0 +1,294 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics.PackedVector; + +using Lidgren.Network; + +namespace SpacePew.Extensions +{ + public static class LidgrenExtensions + { + /// + /// Write a Point + /// + public static void Write(this NetBuffer message, Point value) + { + message.Write(value.X); + message.Write(value.Y); + } + + /// + /// Read a Point + /// + public static Point ReadPoint(this NetBuffer message) + { + return new Point(message.ReadInt32(), message.ReadInt32()); + } + + /// + /// Write a Single with half precision (16 bits) + /// + public static void WriteHalfPrecision(this NetBuffer message, float value) + { + message.Write(new HalfSingle(value).PackedValue); + } + + /// + /// Reads a half precision Single written using WriteHalfPrecision(float) + /// + public static float ReadHalfPrecisionSingle(this NetBuffer message) + { + HalfSingle h = new HalfSingle(); + h.PackedValue = message.ReadUInt16(); + return h.ToSingle(); + } + + /// + /// Writes a Vector2 + /// + public static void Write(this NetBuffer message, Vector2 vector) + { + message.Write(vector.X); + message.Write(vector.Y); + } + + /// + /// Reads a Vector2 + /// + public static Vector2 ReadVector2(this NetBuffer message) + { + Vector2 retval; + retval.X = message.ReadSingle(); + retval.Y = message.ReadSingle(); + return retval; + } + + /// + /// Writes a Vector3 + /// + public static void Write(this NetBuffer message, Vector3 vector) + { + message.Write(vector.X); + message.Write(vector.Y); + message.Write(vector.Z); + } + + /// + /// Writes a Vector3 at half precision + /// + public static void WriteHalfPrecision(this NetBuffer message, Vector3 vector) + { + message.Write(new HalfSingle(vector.X).PackedValue); + message.Write(new HalfSingle(vector.Y).PackedValue); + message.Write(new HalfSingle(vector.Z).PackedValue); + } + + /// + /// Reads a Vector3 + /// + public static Vector3 ReadVector3(this NetBuffer message) + { + Vector3 retval; + retval.X = message.ReadSingle(); + retval.Y = message.ReadSingle(); + retval.Z = message.ReadSingle(); + return retval; + } + + /// + /// Writes a Vector3 at half precision + /// + public static Vector3 ReadHalfPrecisionVector3(this NetBuffer message) + { + HalfSingle hx = new HalfSingle(); + hx.PackedValue = message.ReadUInt16(); + + HalfSingle hy = new HalfSingle(); + hy.PackedValue = message.ReadUInt16(); + + HalfSingle hz = new HalfSingle(); + hz.PackedValue = message.ReadUInt16(); + + Vector3 retval; + retval.X = hx.ToSingle(); + retval.Y = hy.ToSingle(); + retval.Z = hz.ToSingle(); + return retval; + } + + /// + /// Writes a Vector4 + /// + public static void Write(this NetBuffer message, Vector4 vector) + { + message.Write(vector.X); + message.Write(vector.Y); + message.Write(vector.Z); + message.Write(vector.W); + } + + /// + /// Reads a Vector4 + /// + public static Vector4 ReadVector4(this NetBuffer message) + { + Vector4 retval; + retval.X = message.ReadSingle(); + retval.Y = message.ReadSingle(); + retval.Z = message.ReadSingle(); + retval.W = message.ReadSingle(); + return retval; + } + + + /// + /// Writes a unit vector (ie. a vector of length 1.0, for example a surface normal) + /// using specified number of bits + /// + public static void WriteUnitVector3(this NetBuffer message, Vector3 unitVector, int numberOfBits) + { + float x = unitVector.X; + float y = unitVector.Y; + float z = unitVector.Z; + double invPi = 1.0 / Math.PI; + float phi = (float)(Math.Atan2(x, y) * invPi); + float theta = (float)(Math.Atan2(z, Math.Sqrt(x * x + y * y)) * (invPi * 2)); + + int halfBits = numberOfBits / 2; + message.WriteSignedSingle(phi, halfBits); + message.WriteSignedSingle(theta, numberOfBits - halfBits); + } + + /// + /// Reads a unit vector written using WriteUnitVector3(numberOfBits) + /// + public static Vector3 ReadUnitVector3(this NetBuffer message, int numberOfBits) + { + int halfBits = numberOfBits / 2; + float phi = message.ReadSignedSingle(halfBits) * (float)Math.PI; + float theta = message.ReadSignedSingle(numberOfBits - halfBits) * (float)(Math.PI * 0.5); + + Vector3 retval; + retval.X = (float)(Math.Sin(phi) * Math.Cos(theta)); + retval.Y = (float)(Math.Cos(phi) * Math.Cos(theta)); + retval.Z = (float)Math.Sin(theta); + + return retval; + } + + /// + /// Writes a unit quaternion using the specified number of bits per element + /// for a total of 4 x bitsPerElements bits. Suggested value is 8 to 24 bits. + /// + public static void WriteRotation(this NetBuffer message, Quaternion quaternion, int bitsPerElement) + { + if (quaternion.X > 1.0f) + quaternion.X = 1.0f; + if (quaternion.Y > 1.0f) + quaternion.Y = 1.0f; + if (quaternion.Z > 1.0f) + quaternion.Z = 1.0f; + if (quaternion.W > 1.0f) + quaternion.W = 1.0f; + if (quaternion.X < -1.0f) + quaternion.X = -1.0f; + if (quaternion.Y < -1.0f) + quaternion.Y = -1.0f; + if (quaternion.Z < -1.0f) + quaternion.Z = -1.0f; + if (quaternion.W < -1.0f) + quaternion.W = -1.0f; + + message.WriteSignedSingle(quaternion.X, bitsPerElement); + message.WriteSignedSingle(quaternion.Y, bitsPerElement); + message.WriteSignedSingle(quaternion.Z, bitsPerElement); + message.WriteSignedSingle(quaternion.W, bitsPerElement); + } + + /// + /// Reads a unit quaternion written using WriteRotation(... ,bitsPerElement) + /// + public static Quaternion ReadRotation(this NetBuffer message, int bitsPerElement) + { + Quaternion retval; + retval.X = message.ReadSignedSingle(bitsPerElement); + retval.Y = message.ReadSignedSingle(bitsPerElement); + retval.Z = message.ReadSignedSingle(bitsPerElement); + retval.W = message.ReadSignedSingle(bitsPerElement); + return retval; + } + + /// + /// Writes an orthonormal matrix (rotation, translation but not scaling or projection) + /// + public static void WriteMatrix(this NetBuffer message, ref Matrix matrix) + { + Quaternion rot = Quaternion.CreateFromRotationMatrix(matrix); + WriteRotation(message, rot, 24); + message.Write(matrix.M41); + message.Write(matrix.M42); + message.Write(matrix.M43); + } + + /// + /// Writes an orthonormal matrix (rotation, translation but no scaling or projection) + /// + public static void WriteMatrix(this NetBuffer message, Matrix matrix) + { + Quaternion rot = Quaternion.CreateFromRotationMatrix(matrix); + WriteRotation(message, rot, 24); + message.Write(matrix.M41); + message.Write(matrix.M42); + message.Write(matrix.M43); + } + + /// + /// Reads a matrix written using WriteMatrix() + /// + public static Matrix ReadMatrix(this NetBuffer message) + { + Quaternion rot = ReadRotation(message, 24); + Matrix retval = Matrix.CreateFromQuaternion(rot); + retval.M41 = message.ReadSingle(); + retval.M42 = message.ReadSingle(); + retval.M43 = message.ReadSingle(); + return retval; + } + + /// + /// Reads a matrix written using WriteMatrix() + /// + public static void ReadMatrix(this NetBuffer message, ref Matrix destination) + { + Quaternion rot = ReadRotation(message, 24); + destination = Matrix.CreateFromQuaternion(rot); + destination.M41 = message.ReadSingle(); + destination.M42 = message.ReadSingle(); + destination.M43 = message.ReadSingle(); + } + + /// + /// Writes a bounding sphere + /// + public static void Write(this NetBuffer message, BoundingSphere bounds) + { + message.Write(bounds.Center.X); + message.Write(bounds.Center.Y); + message.Write(bounds.Center.Z); + message.Write(bounds.Radius); + } + + /// + /// Reads a bounding sphere written using Write(message, BoundingSphere) + /// + public static BoundingSphere ReadBoundingSphere(this NetBuffer message) + { + BoundingSphere retval; + retval.Center.X = message.ReadSingle(); + retval.Center.Y = message.ReadSingle(); + retval.Center.Z = message.ReadSingle(); + retval.Radius = message.ReadSingle(); + return retval; + } + } +} diff --git a/SpacePew/Extensions/RenderExtensions.cs b/SpacePew/Extensions/RenderExtensions.cs new file mode 100644 index 0000000..3453e17 --- /dev/null +++ b/SpacePew/Extensions/RenderExtensions.cs @@ -0,0 +1,42 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using SpacePew.Models; + +namespace SpacePew.Extensions +{ + public static class RenderExtensions + { + + public static Vector2 AsVector2D(this Point p) + { + return new Vector2(p.X, p.Y); + } + + public static void Draw(this SpriteBatch batch, TiledTexture texture, Rectangle destination, Color color) + { + Draw(batch, texture, destination, new Rectangle(0, 0, texture.Width, texture.Height), color); + } + + public static void Draw(this SpriteBatch batch, TiledTexture texture, Rectangle dstRect, Rectangle srcRect, Color color) + { + //TODO: kolla om dom ens syns innan man renderar.. hur man nu ska kunna göra det.. + var wratio = dstRect.Width / (double)srcRect.Width; + var hratio = dstRect.Height / (double)srcRect.Height; + var pos = new Point(dstRect.X, dstRect.Y); + dstRect = new Rectangle(0, 0, (int)(wratio * texture.TileWidth), (int)(hratio * texture.TileHeight)); + + for (var j = 0; j < texture.XTiles; j++) + { + for (var i = 0; i < texture.YTiles; i++) + { + //if srcRect.Intersects tile + var tile = texture[i * texture.XTiles + j]; + dstRect.X = pos.X + (int)(tile.Position.X * wratio); + dstRect.Y = pos.Y + (int)(tile.Position.Y * hratio); + + batch.Draw(tile.Texture, dstRect, color); + } + } + } + } +} diff --git a/SpacePew/Game.ico b/SpacePew/Game.ico new file mode 100644 index 0000000..8cff41e Binary files /dev/null and b/SpacePew/Game.ico differ diff --git a/SpacePew/GameThumbnail.png b/SpacePew/GameThumbnail.png new file mode 100644 index 0000000..462311a Binary files /dev/null and b/SpacePew/GameThumbnail.png differ diff --git a/SpacePew/Hud.cs b/SpacePew/Hud.cs new file mode 100644 index 0000000..8394b0d --- /dev/null +++ b/SpacePew/Hud.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using SpacePew.Models; + + +namespace SpacePew +{ + /// + /// This is a game component that implements IUpdateable. + /// + public class Hud : GameComponent, IDrawable + { + SpriteBatch _spriteBatch; + readonly MainGame _game; + + SpriteFont _consoleFont; + + public Hud(MainGame game) + : base(game) + { + _game = game; + } + + /// + /// Allows the game component to perform any initialization it needs to before starting + /// to run. This is where it can query for any required services and load content. + /// + public override void Initialize() + { + _spriteBatch = new SpriteBatch(_game.GraphicsDevice); + _consoleFont = _game.Content.Load("Fonts\\ConsoleFont"); + + base.Initialize(); + } + + public void Draw(GameTime gameTime) + { + if (_game.NetworkClient.LocalPlayer != null) + { + _spriteBatch.Begin(); + _spriteBatch.DrawString(_consoleFont, + string.Format("Health: {0}", + _game.NetworkClient.LocalPlayer.Health), + new Vector2(300, _game.GraphicsDevice.Viewport.Height - 100), + Color.White); + + _spriteBatch.DrawString(_consoleFont, + string.Format("Fuel: {0}", + _game.NetworkClient.LocalPlayer.Fuel), + new Vector2(300, _game.GraphicsDevice.Viewport.Height - 75), + Color.White); + + _spriteBatch.DrawString(_consoleFont, + string.Format("Heat #1: {0}", + _game.NetworkClient.LocalPlayer.Weapon.Heat), + new Vector2(500, _game.GraphicsDevice.Viewport.Height - 100), + Color.White); + + _spriteBatch.DrawString(_consoleFont, + string.Format("Heat #2: {0}", + _game.NetworkClient.LocalPlayer.SecondaryWeapon.Heat), + new Vector2(500, _game.GraphicsDevice.Viewport.Height - 75), + Color.White); + + _spriteBatch.DrawString(_consoleFont, + string.Format("Entities: {0}", + ((List)EntityFactory.Instance.Entities).Count), + new Vector2(500, _game.GraphicsDevice.Viewport.Height - 50), + Color.White); + + _spriteBatch.DrawString(_consoleFont, + string.Format("Pos X: {0}", + _game.NetworkClient.LocalPlayer.Position.X), + new Vector2(700, _game.GraphicsDevice.Viewport.Height - 100), + Color.White); + + _spriteBatch.DrawString(_consoleFont, + string.Format("Pos Y: {0}", + _game.NetworkClient.LocalPlayer.Position.Y), + new Vector2(700, _game.GraphicsDevice.Viewport.Height - 75), + Color.White); + + _spriteBatch.End(); + } + } + + public int DrawOrder + { + get { return 2; } + } + + public bool Visible + { + get { return true; } + } + + event EventHandler IDrawable.DrawOrderChanged + { + add { } + remove { } + } + + event EventHandler IDrawable.VisibleChanged + { + add { } + remove { } + } + } +} \ No newline at end of file diff --git a/SpacePew/Icon.ico b/SpacePew/Icon.ico new file mode 100644 index 0000000..13be62a Binary files /dev/null and b/SpacePew/Icon.ico differ diff --git a/SpacePew/KeyboardHelper.cs b/SpacePew/KeyboardHelper.cs new file mode 100644 index 0000000..e0e9ab1 --- /dev/null +++ b/SpacePew/KeyboardHelper.cs @@ -0,0 +1,663 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework.Input; + +namespace SpacePew +{ + [Flags] + public enum KbModifiers + { + None = 0, + Ctrl = 1, + Shift = 2, + Alt = 4, + } + + public static class KeyboardHelper + { + public static bool TreadNumpadAsNumeric = true; + private static readonly string[] _unShiftedKeysString = new string[256] + { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + " ", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "", + "", + "", + "", + "", + "", + "", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "", + "", + "", + "", + "", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "*", + "+", + "", + "-", + ".", + "/", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ";", + "=", + ",", + "-", + ".", + "/", + "`", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "[", + "\\", + "]", + "'", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + }; + private static string[] shiftedkeysstring = new string[256] + { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + " ", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ")", + "!", + "@", + "#", + "$", + "%", + "^", + "&", + "*", + "(", + "", + "", + "", + "", + "", + "", + "", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "*", + "+", + "", + "-", + ".", + "/", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ":", + "+", + "<", + "_", + ">", + "?", + "~", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "{", + "|", + "}", + "\"", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + }; + + static KeyboardHelper() + { + } + + public static bool IsKeyAlpha(Keys k) + { + return k >= Keys.A && k <= Keys.Z; + } + + public static bool IsKeyNumber(Keys k) + { + return k >= Keys.D0 && k <= Keys.D9; + } + + public static bool IsKeyNumberpad(Keys k) + { + return k >= Keys.NumPad0 && k <= Keys.NumPad9; + } + + public static bool IsKeyNumeric(Keys k) + { + return KeyboardHelper.IsKeyNumber(k) || KeyboardHelper.TreadNumpadAsNumeric && KeyboardHelper.IsKeyNumberpad(k); + } + + public static bool IsKeyAlphanumeric(Keys k) + { + return KeyboardHelper.IsKeyAlpha(k) || KeyboardHelper.IsKeyNumeric(k); + } + + public static bool IsFkey(Keys k) + { + return k >= Keys.F1 && k <= Keys.F12; + } + + public static bool IsKeySpace(Keys k) + { + return k == Keys.Space; + } + + public static bool IsShift(Keys k) + { + return k == Keys.LeftShift || k == Keys.RightShift; + } + + public static bool IsCtrl(Keys k) + { + return k == Keys.LeftControl || k == Keys.RightControl; + } + + public static bool IsAlt(Keys k) + { + return k == Keys.LeftAlt || k == Keys.RightAlt; + } + + public static bool IsShiftDown(KbModifiers m) + { + return (KbModifiers.Shift & m) == KbModifiers.Shift; + } + + public static bool IsCtrlDown(KbModifiers m) + { + return (KbModifiers.Ctrl & m) == KbModifiers.Ctrl; + } + + public static bool IsAltDown(KbModifiers m) + { + return (KbModifiers.Alt & m) == KbModifiers.Alt; + } + + public static bool IsMod(Keys k) + { + return KeyboardHelper.IsShift(k) || KeyboardHelper.IsAlt(k) || KeyboardHelper.IsCtrl(k); + } + + public static KbModifiers IsShiftM(Keys k) + { + return k == Keys.LeftShift || k == Keys.RightShift ? KbModifiers.Shift : KbModifiers.None; + } + + public static KbModifiers IsCtrlM(Keys k) + { + return k == Keys.LeftControl || k == Keys.RightControl ? KbModifiers.Ctrl : KbModifiers.None; + } + + public static KbModifiers IsAltM(Keys k) + { + return k == Keys.LeftAlt || k == Keys.RightAlt ? KbModifiers.Alt : KbModifiers.None; + } + + public static string ToPrintableString(Keys k, KbModifiers m) + { + return KeyboardHelper.ToPrintableString(k, m, true, true, true, false); + } + + public static string ToPrintableString(Keys k, KbModifiers m, bool selectspecials) + { + return KeyboardHelper.ToPrintableString(k, m, selectspecials, true, true, false); + } + + public static string ToPrintableString(Keys k, KbModifiers m, bool selectspecials, bool selectalphas, bool selectnumerics) + { + return KeyboardHelper.ToPrintableString(k, m, selectspecials, selectalphas, selectnumerics, false); + } + + public static string ToPrintableString(Keys k, KbModifiers m, bool selectspecials, bool selectalphas, bool selectnumerics, bool suppressspace) + { + if (KeyboardHelper.IsKeySpace(k) && !suppressspace) + return " "; + if (KeyboardHelper.IsKeyAlpha(k) && selectalphas || KeyboardHelper.IsKeyNumber(k) && selectnumerics || KeyboardHelper.TreadNumpadAsNumeric && KeyboardHelper.IsKeyNumberpad(k) && selectnumerics || selectspecials && (!KeyboardHelper.IsKeyAlpha(k) && !KeyboardHelper.IsKeyNumeric(k) || KeyboardHelper.IsKeyNumber(k) && KeyboardHelper.IsShiftDown(m))) + { + if (!KeyboardHelper.IsShiftDown(m)) + return KeyboardHelper._unShiftedKeysString[k.GetHashCode()]; + if (selectspecials || !KeyboardHelper.IsKeyNumber(k)) + return KeyboardHelper.shiftedkeysstring[k.GetHashCode()]; + } + return ""; + } + + public static KbModifiers GetModifiers(KeyboardState ks) + { + return ks.GetPressedKeys().Aggregate(KbModifiers.None, (current, k) => current | KeyboardHelper.IsShiftM(k) | KeyboardHelper.IsAltM(k) | KeyboardHelper.IsCtrlM(k)); + } + } +} diff --git a/SpacePew/LevelLoader.cs b/SpacePew/LevelLoader.cs new file mode 100644 index 0000000..8948a7f --- /dev/null +++ b/SpacePew/LevelLoader.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Media; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Graphics; +using ICSharpCode.SharpZipLib.Zip; +using System.IO; +using System.Xml; +using Microsoft.Xna.Framework.Media; +using Microsoft.Xna.Framework.Content; +using SpacePew.Models; +using Rectangle = System.Drawing.Rectangle; + +namespace SpacePew +{ + static class LevelLoader + { + public static Level LoadLevel(string filePath, ContentManager cm, GraphicsDevice device) + { + var levelResources = GetResourceList(filePath); + if (!levelResources.ContainsKey("leveldata.xml")) + throw new InvalidOperationException("Level: " + filePath + " doesnt contain any leveldata.xml"); + + var levelData = GetXmlFile(levelResources["leveldata.xml"]); + string baseTexture = levelData.SelectSingleNode("//baseTexture").InnerText.ToLower(); + string indestructibleTexture = levelData.SelectSingleNode("//indestructibleTexture").InnerText.ToLower(); + + var spriteBatch = new SpriteBatch(device); + var level = new Level(); + level.FilePath = filePath; + level.Texture = LoadTextureTiles(levelResources[baseTexture], device, spriteBatch); + level.IndestructibleTexture = LoadTextureTiles(levelResources[indestructibleTexture], device, spriteBatch); + level.DeformedTexture = new TiledTexture + { + Width = level.Texture.Width, + Height = level.Texture.Height, + TileHeight = level.Texture.TileHeight, + TileWidth = level.Texture.TileWidth, + XTiles = level.Texture.XTiles, + YTiles = level.Texture.YTiles + }; + + level.Texture.ForEach(t => level.DeformedTexture.Add(new Tile( + new RenderTarget2D(device, level.Texture.TileWidth, level.Texture.TileHeight, false, + SurfaceFormat.Color, DepthFormat.Depth24, + device.PresentationParameters.MultiSampleCount, + RenderTargetUsage.PreserveContents + ), + t.Position))); + + if (levelData.SelectSingleNode("//song") != null) + { + string songName = levelData.SelectSingleNode("//song").InnerText.ToLower(); + level.OggVorbisSong = levelResources[songName]; + } + + level.Initialize(); + return level; + } + + private static XmlDocument GetXmlFile(byte[] rawData) + { + var d = new XmlDocument(); + d.LoadXml(System.Text.Encoding.UTF8.GetString(rawData)); + return d; + } + + public static TiledTexture LoadTextureTiles(Byte[] bitmapData, GraphicsDevice device, SpriteBatch spriteBatch, int tileWidth = 0, int tileHeight = 0) + { + using (var ms = new MemoryStream(bitmapData)) + { + ms.Seek(0, SeekOrigin.Begin); + using (var image = Image.FromStream(ms)) + { + return LoadTextureTiles(image, device, spriteBatch, tileWidth, tileHeight); + } + } + } + + public static TiledTexture LoadTextureTiles(string fileName, GraphicsDevice device, SpriteBatch spriteBatch, int tileWidth = 0, int tileHeight = 0) + { + using (var image = Image.FromFile(AppDomain.CurrentDomain.BaseDirectory + fileName.Replace("/", "\\"))) + { + return LoadTextureTiles(image, device, spriteBatch, tileWidth, tileHeight); + } + } + + public static TiledTexture LoadTextureTiles(Image image, GraphicsDevice device, SpriteBatch spriteBatch, int tileWidth = 0, int tileHeight = 0) + { + //OBS!!! Stt kartans bredd el hjd till ett primtal o det r krt iom att d kan man inte hitta en jmn multiple och en ojmn kommer att anvndas + //och det kommer att pricka i framtiden. + if (tileWidth == 0) + tileWidth = FindNearbyMultiple(image.Width, 1000); + if (tileHeight == 0) + tileHeight = FindNearbyMultiple(image.Height, 1000); + + + int tilesX = (image.Width % tileWidth == 0) + ? (int)Math.Floor(image.Width / (double)tileWidth) + : (int)Math.Ceiling(image.Width / (double)tileWidth); + + int tilesY = (image.Height % tileHeight == 0) + ? (int)Math.Floor(image.Height / (double)tileHeight) + : (int)Math.Ceiling(image.Height / (double)tileHeight); + + var ret = new TiledTexture + { + TileWidth = tileWidth, + TileHeight = tileHeight, + XTiles = tilesX, + YTiles = tilesY, + Width = image.Width, + Height = image.Height + }; + for (var j = 0; j < tilesX; j++) + { + for (var i = 0; i < tilesY; i++) + { + var part = new Bitmap(tileWidth, tileHeight, image.PixelFormat); + var graphics = Graphics.FromImage(part); + var srcRect = new Rectangle(j * tileWidth, i * tileHeight, tileWidth, tileHeight); + graphics.DrawImage(image, 0, 0, srcRect, GraphicsUnit.Pixel); + using (var ms = new MemoryStream()) + { + //TODO: ngot vettigare n s hr fr att f fram bytesen... tar ju r och dagar att ladda nu. + part.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Seek(0, SeekOrigin.Begin); + + ret.Add(new Tile(Texture2D.FromStream(device, ms), new Microsoft.Xna.Framework.Point(j * tileWidth, i * tileHeight))); + } + } + } + return ret; + } + + private static int FindNearbyMultiple(int totLen, int chunkLen) + { + chunkLen = totLen / (int)Math.Ceiling(totLen / (double)chunkLen); + + var step = 0; + while (step < 100) + { + var t = chunkLen - step; + if ((totLen % t) == 0) + return t; + t = chunkLen + step; + if ((totLen % t) == 0) + return t; + step++; + } + return FindNearbyMultiple(totLen - 1, chunkLen); + } + + private static Dictionary GetResourceList(string file) + { + var resources = new Dictionary(); + using (var stream = new ZipInputStream(File.OpenRead(file))) + { + ZipEntry entry = null; + while ((entry = stream.GetNextEntry()) != null) + { + if (entry.Name.Length > 0) + { + var buf = new byte[(int)entry.Size]; + stream.Read(buf, 0, buf.Length); + resources.Add(entry.Name.ToLower().Trim(), buf); + } + } + } + + return resources; + } + } +} \ No newline at end of file diff --git a/SpacePew/Levels/hippie.zip b/SpacePew/Levels/hippie.zip new file mode 100644 index 0000000..970f05d Binary files /dev/null and b/SpacePew/Levels/hippie.zip differ diff --git a/SpacePew/MainGame.cs b/SpacePew/MainGame.cs new file mode 100644 index 0000000..3543ba4 --- /dev/null +++ b/SpacePew/MainGame.cs @@ -0,0 +1,483 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Media; +using SpacePew.Camera; +using SpacePew.Models; +using SpacePew.Models.Weapons; +using SpacePew.Networking; +using SpacePew.ParticleSystem; +using Color = Microsoft.Xna.Framework.Color; +using Rectangle = Microsoft.Xna.Framework.Rectangle; +using SpacePew.Extensions; +using System.Linq; +using System.Windows.Forms; + +namespace SpacePew +{ + /// + /// This is the main type for your game + /// + public class MainGame : Game + { + public static int CenterX; + public static int CenterY; + + public UdpServer NetworkServer { get; private set; } + public UdpClient NetworkClient { get; private set; } + + private Level _level; + public Level Level + { + get { return _level; } + set { _level = value; } + } + + private SpriteBatch _spriteBatch; + public SpriteBatch SpriteBatch + { + get + { + return _spriteBatch; + } + } + + readonly GraphicsDeviceManager _graphics; + readonly EntityFactory _entityFactory; + + Hud _hud; + Minimap _minimap; + + private readonly UdpNetworkGui _udpClientGui; + + private KeyboardState _currentKeyboardState; + private GamePadState _currentGamePadState; + + private Texture2D _backgroundTexture; + + private ExplosionParticleSystem _explosion; + private ExplosionSmokeParticleSystem _smoke; + private SmokePlumeParticleSystem _smokePlume; + + private NetworkMessenger _networkMessenger; + + private Song _gameSong; + public Song GameSong + { + get { return _gameSong; } + } + + private ICamera2D _camera; + + private readonly Random _randomizer; + + public static bool IsKeyboardInUse; + + public void AddGameComponents() + { + _hud = new Hud(this); + Components.Add(_hud); + + _minimap = new Minimap(this); + Components.Add(_minimap); + + _camera = new Camera2D(this) + { + Focus = NetworkClient.LocalPlayer + }; + + Components.Add((IGameComponent)_camera); + + _explosion = new ExplosionParticleSystem(this, 1, _camera); + Components.Add(_explosion); + + _smoke = new ExplosionSmokeParticleSystem(this, 2, _camera); + Components.Add(_smoke); + + _smokePlume = new SmokePlumeParticleSystem(this, 9, _camera); + Components.Add(_smokePlume); + + _networkMessenger = new NetworkMessenger(this, NetworkClient); + Components.Add(_networkMessenger); + + Components.Add(new ScoreBoard(this, NetworkClient)); + } + + public void AddExplosion(Vector2 position, float size) + { + _explosion.AddParticles(position, size); + _smoke.AddParticles(position, size); + } + + public MainGame() + { + Trace.Listeners.Add(new TextWriterTraceListener("debug.log")); + Trace.AutoFlush = true; + Trace.Indent(); + + Trace.WriteLine(string.Empty); + Trace.WriteLine(DateTime.Now); + Trace.WriteLine("------------------------------------------------------------------------------------------------------------------"); + + Window.Title = "Space, pew pew!"; + Content.RootDirectory = "Content"; + + NetworkServer = new UdpServer(); + NetworkClient = new UdpClient(this); + + _graphics = new GraphicsDeviceManager(this); + _entityFactory = new EntityFactory(this); + + SoundManager.Initialize(this); + TextureManager.Initialize(this); + + IsMouseVisible = true; + + _graphics.CreateDevice(); + + Cursor.Hide(); + + _graphics.PreferredBackBufferWidth = 1366; + _graphics.PreferredBackBufferHeight = 768; + + //var screen = Screen.AllScreens.First(e => e.Primary); + + //Window.IsBorderless = true; + //Window.Position = new Point(screen.Bounds.X, screen.Bounds.Y); + //_graphics.PreferredBackBufferWidth = screen.Bounds.Width; + //_graphics.PreferredBackBufferHeight = screen.Bounds.Height; + + _graphics.ApplyChanges(); + + CenterX = _graphics.PreferredBackBufferWidth / 2; + CenterY = _graphics.PreferredBackBufferHeight / 2; + + _randomizer = new Random(); + + _udpClientGui = new UdpNetworkGui(this, _graphics, (UdpClient)NetworkClient, (UdpServer)NetworkServer); + Components.Add(_udpClientGui); + } + + /// + /// LoadContent will be called once per game and is the place to load + /// all of your content. + /// + protected override void LoadContent() + { + _udpClientGui.Initialize(); + + _spriteBatch = new SpriteBatch(GraphicsDevice); + + _backgroundTexture = TextureManager.LoadTexture("stars"); + } + + /// + /// UnloadContent will be called once per game and is the place to unload + /// all content. + /// + protected override void UnloadContent() + { + } + + protected override void OnExiting(object sender, EventArgs args) + { + if (_level != null) + { + _level.StopLevelSong(); + } + + if (NetworkClient.IsSessionAlive) + { + NetworkClient.ExitSession("Quitting..."); + } + + NetworkServer.Shutdown(); + + Trace.WriteLine("Exiting"); + Trace.Unindent(); + Trace.Flush(); + + base.OnExiting(sender, args); + } + + /// + /// Handles input. + /// + private void HandleInput() + { + _currentKeyboardState = Keyboard.GetState(); + _currentGamePadState = GamePad.GetState(PlayerIndex.One); + + // Check for exit. + if (IsActive && IsPressed(Microsoft.Xna.Framework.Input.Keys.Escape, Buttons.Back)) + { + Exit(); + } + } + + /// + /// Checks if the specified button is pressed on either keyboard or gamepad. + /// + bool IsPressed(Microsoft.Xna.Framework.Input.Keys key, Buttons button) + { + return (_currentKeyboardState.IsKeyDown(key) || _currentGamePadState.IsButtonDown(button)); + } + + /// + /// Allows the game to run logic such as updating the world, + /// checking for collisions, gathering input, and playing audio. + /// + /// Provides a snapshot of timing values. + protected override void Update(GameTime gameTime) + { + HandleInput(); + + if (!NetworkClient.IsSessionAlive || NetworkClient.LocalPlayer == null) + { + // If we are not in a network session, update the + // menu screen that will let us create or join one. + } + else + { + while (WeaponBase.FiredShots.Count > 0) + { + NetworkClient.EntitiesToSend.Add(WeaponBase.FiredShots.Dequeue()); + } + + NetworkClient.Update(); + + NetworkClient.LocalPlayer.HandleKeyboard(Keyboard.GetState()); + + var entitiesToKill = new HashSet(); + + //fixa en metod fr entity som skter det hr.. + foreach (var entity in _entityFactory.Entities) + { + entity.Update(gameTime); + + foreach (var player in NetworkClient.Players) + { + if (player.IsCollisionWith(entity)) + { + player.CollideWith(entity); + + if (!(entity is Player)) + { + AddExplosion(entity.Position, 0.05f); + + entitiesToKill.Add(entity); + } + } + + if (player == NetworkClient.LocalPlayer && player.Health <= 0) + { + AddExplosion(player.Position, 1f); + player.Kill(); + + NetworkClient.SendDeath(entity); + + var message = new NetworkMessage + { + Sent = DateTime.Now + }; + + if (player.Owner == entity.Owner) + { + message.Color = player.Color; + message.Message = string.Format("- {0} commited suicide.", player.Owner); + } + else + { + var killer = ((List)NetworkClient.Players).Find(p => p.Owner == entity.Owner); + message.Message = string.Format("{0} killed {1}.", entity.Owner, player.Owner); + message.Color = killer.Color; + } + + NetworkMessenger.SendMessage(message); + } + } + + entity.Health -= _level.Collide(entity, CenterX, CenterY); + + if (NetworkClient.LocalPlayer.Health <= 0) + { + AddExplosion(NetworkClient.LocalPlayer.Position, 1f); + NetworkClient.LocalPlayer.Kill(); + + NetworkMessenger.AddDeath(NetworkClient.LocalPlayer); + NetworkMessenger.SendMessage(new NetworkMessage + { + Color = NetworkClient.LocalPlayer.Color, + Message = GetRandomCrashMessage(), + Sent = DateTime.Now + }); + } + + if (!(entity is Player) && entity.Health <= 0) + { + entitiesToKill.Add(entity); + + AddExplosion(entity.Position, 0.05f); + } + } + + _entityFactory.RemoveEntities(entitiesToKill); + } + + base.Update(gameTime); + } + + private string GetRandomCrashMessage() + { + int random = _randomizer.Next(0, 3); + var crashMessages = new List + { + "{0} is driving under influence.", + "{0} crashed.", + "{0} sent himself straight towards the wall." + }; + + return string.Format(crashMessages[random], NetworkClient.LocalPlayer.Owner); + } + + /// + /// This is called when the game should draw itself. + /// + /// Provides a snapshot of timing values. + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.Black); + + if (NetworkClient.IsSessionAlive) + { + if (!BeginDraw() || NetworkClient.LocalPlayer == null) + return; + + _graphics.GraphicsDevice.Clear(Color.Black); + + RenderDeformation(); + RenderTiledBackground(); + RenderLevel(); + RenderEntities(); + } + + base.Draw(gameTime); + } + + private static readonly BlendState + opaqueExceptAlpha = new BlendState + { + ColorSourceBlend = Blend.One, + AlphaSourceBlend = Blend.One, + ColorDestinationBlend = Blend.InverseDestinationAlpha, + AlphaDestinationBlend = Blend.InverseDestinationAlpha, + ColorWriteChannels = ColorWriteChannels.Alpha + }; + + void RenderDeformation() + { + var hits = new List(); + while (_level.Hits.Count > 0) + { + hits.Add(_level.Hits.Dequeue()); + } + + foreach (var tile in _level.DeformedTexture) + { + _graphics.GraphicsDevice.SetRenderTarget((RenderTarget2D)tile.Texture); + _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); // TODO: fixa blendstaten dr uppe s att den tar hnsyn till alpha i source + foreach (var hit in hits) + { + _spriteBatch.Draw(hit.Texture, + hit.Position + _camera.ScreenCenter - tile.Position.AsVector2D(), + null, + Color.Black, + hit.Angle, + hit.Origin, + 1, + SpriteEffects.None, + 1); + } + + _spriteBatch.End(); + } + + _graphics.GraphicsDevice.SetRenderTarget(null); + } + + private void RenderTiledBackground() + { + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque); + + var pos = Vector2.Zero; + var scrollPos = NetworkClient.LocalPlayer.Position / 3.0f; + + int startX = ((int)scrollPos.X) % _backgroundTexture.Width; + int startY = ((int)scrollPos.Y) % _backgroundTexture.Height; + + for (int y = -startY; y < GraphicsDevice.Viewport.Height; y += _backgroundTexture.Height) + { + for (int x = -startX; x < GraphicsDevice.Viewport.Width; x += _backgroundTexture.Width) + { + pos.X = x; pos.Y = y; + _spriteBatch.Draw(_backgroundTexture, pos, Color.White); + } + } + + _spriteBatch.End(); + } + + void RenderLevel() + { + var bounds = new Rectangle(0, 0, _level.Texture.TileWidth, _level.Texture.TileHeight); + var viewPort = new Rectangle( + (int)_camera.Position.X - _graphics.GraphicsDevice.Viewport.Width / 2, + (int)_camera.Position.Y - _graphics.GraphicsDevice.Viewport.Height / 2, + _graphics.GraphicsDevice.Viewport.Width + _graphics.GraphicsDevice.Viewport.Width / 2, + _graphics.GraphicsDevice.Viewport.Height + _graphics.GraphicsDevice.Viewport.Height / 2); + + for (var i = 0; i < _level.Texture.Count; i++) + { + var tile = _level.Texture[i]; + var deformedTile = _level.DeformedTexture[i]; + var indestructibleTile = _level.IndestructibleTexture[i]; + + bounds.X = tile.Position.X; + bounds.Y = tile.Position.Y; + if (viewPort.Intersects(bounds)) + { + _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); + + _spriteBatch.Draw(indestructibleTile.Texture, new Vector2(indestructibleTile.Position.X - _camera.Position.X, indestructibleTile.Position.Y - _camera.Position.Y)); + _spriteBatch.Draw(deformedTile.Texture, new Vector2(deformedTile.Position.X - _camera.Position.X, deformedTile.Position.Y - _camera.Position.Y)); + + _spriteBatch.End(); + } + } + } + + private void RenderEntities() + { + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, _camera.Transform); + + foreach (var entity in _entityFactory.Entities) + { + _spriteBatch.Draw(entity.Texture, + entity.Position, + null, + entity.Color, + entity.Angle, + entity.Origin, + 1, + SpriteEffects.None, + 1); + } + + _spriteBatch.End(); + } + } +} diff --git a/SpacePew/Minimap.cs b/SpacePew/Minimap.cs new file mode 100644 index 0000000..3c030ca --- /dev/null +++ b/SpacePew/Minimap.cs @@ -0,0 +1,111 @@ +using System; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using SpacePew.Extensions; +using SpacePew.Models; + +namespace SpacePew +{ + /// + /// This is a game component that implements IUpdateable. + /// + public class Minimap : GameComponent, IDrawable + { + private const int MinimapWidth = 160; + + private int _minimapModifier; + + private readonly MainGame _game; + private SpriteBatch _spriteBatch; + private int _screenWidth; + private int _screenHeight; + private Texture2D _miniPlayer; + + public Minimap(MainGame game) + : base(game) + { + _game = game; + } + + /// + /// Allows the game component to perform any initialization it needs to before starting + /// to run. This is where it can query for any required services and load content. + /// + public override void Initialize() + { + _spriteBatch = new SpriteBatch(_game.GraphicsDevice); + + _screenWidth = _game.GraphicsDevice.PresentationParameters.BackBufferWidth; + _screenHeight = _game.GraphicsDevice.PresentationParameters.BackBufferHeight; + + _miniPlayer = TextureManager.LoadTexture("bullet"); + + base.Initialize(); + } + + public void Draw(GameTime gameTime) + { + if (_game.NetworkClient.LocalPlayer != null) + { + _minimapModifier = _game.Level.Texture.Width / MinimapWidth; + + _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); + + int minimapHeight = _game.Level.Texture.Height / _minimapModifier > _screenHeight ? + _screenHeight - 20 : + _game.Level.Texture.Height / _screenHeight; + + if (minimapHeight > _screenHeight) + minimapHeight = _screenHeight - 20; + + var rect = new Rectangle(10, _screenHeight - minimapHeight - 10, + _game.Level.Texture.Width / _minimapModifier, + minimapHeight); + + + //Skriv ut spelarpluppen ver hela ytan i svart fr att rensa + _spriteBatch.Draw(_miniPlayer, rect, Color.Black); + _spriteBatch.Draw(_game.Level.DeformedTexture, rect, Color.White); + _spriteBatch.Draw(_game.Level.IndestructibleTexture, rect, Color.White); + + float xScale = _game.Level.Texture.Width / (float)rect.Width; + float yScale = _game.Level.Texture.Height / (float)rect.Height; + float centerX = _screenWidth / 2; + float centerY = _screenHeight / 2; + + foreach (Player player in _game.NetworkClient.Players) + { + var miniMapPosition = new Vector2(rect.X - 1 + (player.Position.X + centerX) / xScale, rect.Y - 1 + (player.Position.Y + centerY) / yScale); + + _spriteBatch.Draw(_miniPlayer, miniMapPosition, player.Color); + } + + _spriteBatch.End(); + } + } + + public int DrawOrder + { + get { return 2; } + } + + public bool Visible + { + get { return true; } + } + + + event EventHandler IDrawable.DrawOrderChanged + { + add { } + remove { } + } + + event EventHandler IDrawable.VisibleChanged + { + add { } + remove { } + } + } +} \ No newline at end of file diff --git a/SpacePew/Models/EntityBase.cs b/SpacePew/Models/EntityBase.cs new file mode 100644 index 0000000..1d90988 --- /dev/null +++ b/SpacePew/Models/EntityBase.cs @@ -0,0 +1,197 @@ +using System; + +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; + +namespace SpacePew.Models +{ + public abstract class EntityBase : IEntity + { + public const float OneRound = (float)Math.PI * 2; + public const float VelocityModifier = .1f; + public const float GravityModifier = 100f; + + public Rectangle BoundingRectangle + { + get + { + return new Rectangle((int)this.Position.X - (int)this.Origin.X, (int)this.Position.Y - (int)this.Origin.Y, this.Texture.Width, this.Texture.Height); + } + } + + private Color[] _textureData; + public Color[] GetTextureData() + { + if (_textureData == null) + { + this._textureData = new Color[this.Texture.Width * this.Texture.Height]; + this.Texture.GetData(this._textureData); + } + + return _textureData; + } + + // These should be settings in the start menu GUI later on + + public bool IsCollisionWith(IEntity entity) + { + // never collide with oneself + if (ReferenceEquals(entity, this)) + return false; + + return entity.Collide(this); + } + + public virtual bool Collide(IEntity entity) + { + Matrix transform = Matrix.CreateTranslation(new Vector3(this.Position - this.Origin, 0.0f)); + + Matrix entityTransform = + Matrix.CreateTranslation(new Vector3(-entity.Origin, 0.0f)) * + Matrix.CreateRotationZ(entity.Angle) * + Matrix.CreateTranslation(new Vector3(entity.Position, 0.0f)); + + Rectangle entityRectangle = CalculateBoundingRectangle( + new Rectangle(0, 0, entity.Texture.Width, entity.Texture.Height), + entityTransform); + + if (entityRectangle.Intersects(this.BoundingRectangle)) + { + if (IntersectPixels(transform, this.Texture.Width, + this.Texture.Height, this.GetTextureData(), + entityTransform, entity.Texture.Width, + entity.Texture.Height, entity.GetTextureData())) + { + return true; + } + } + + return false; + } + + private static bool IntersectPixels(Matrix transformA, int widthA, int heightA, Color[] dataA, + Matrix transformB, int widthB, int heightB, Color[] dataB) + { + Matrix transformAtoB = transformA * Matrix.Invert(transformB); + + Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAtoB); + Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAtoB); + + Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAtoB); + + for (int yA = 0; yA < heightA; yA++) + { + Vector2 posInB = yPosInB; + + for (int xA = 0; xA < widthA; xA++) + { + var xB = (int)Math.Round(posInB.X); + var yB = (int)Math.Round(posInB.Y); + + if (0 <= xB && xB < widthB && + 0 <= yB && yB < heightB) + { + Color colorA = dataA[xA + yA * widthA]; + Color colorB = dataB[xB + yB * widthB]; + + if (colorA.A != 0 && colorB.A != 0) + { + return true; + } + } + + posInB += stepX; + } + + yPosInB += stepY; + } + + return false; + } + + private static Rectangle CalculateBoundingRectangle(Rectangle rectangle, Matrix transform) + { + var leftTop = new Vector2(rectangle.Left, rectangle.Top); + var rightTop = new Vector2(rectangle.Right, rectangle.Top); + var leftBottom = new Vector2(rectangle.Left, rectangle.Bottom); + var rightBottom = new Vector2(rectangle.Right, rectangle.Bottom); + + Vector2.Transform(ref leftTop, ref transform, out leftTop); + Vector2.Transform(ref rightTop, ref transform, out rightTop); + Vector2.Transform(ref leftBottom, ref transform, out leftBottom); + Vector2.Transform(ref rightBottom, ref transform, out rightBottom); + + Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom)); + Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom)); + + return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y)); + } + + #region IEntity Members + + public virtual string Owner + { + get; + set; + } + + public abstract int Health { get; set; } + public abstract string TextureName { get; } + + public virtual void CollideWithLevel(Level level) { } + + public Texture2D Texture { get; set; } + public virtual Color Color { get; set; } + public Vector2 Position { get; set; } + public Vector2 Velocity { get; set; } + + public virtual Vector2 Origin + { + get + { + return new Vector2(this.Texture.Width / 2, this.Texture.Height / 2); + } + } + + private float _angle; + public float Angle + { + get + { + return _angle; + } + set + { + _angle = value; + + if (_angle > OneRound) + { + _angle -= OneRound; + } + else if (_angle < 0) + { + _angle += OneRound; + } + } + } + + public virtual void Created() { } + + public virtual void ApplyGravity(GameTime time) + { + float timeSeconds = time.ElapsedGameTime.Milliseconds * .001f; + Velocity = new Vector2( + Velocity.X, + Velocity.Y + (timeSeconds * GravityModifier)); + } + + public virtual void Update(GameTime time) + { + ApplyGravity(time); + + float timeSeconds = time.ElapsedGameTime.Milliseconds * .001f; + Position += Velocity * timeSeconds; + } + #endregion + } +} diff --git a/SpacePew/Models/Explosion.cs b/SpacePew/Models/Explosion.cs new file mode 100644 index 0000000..b749afc --- /dev/null +++ b/SpacePew/Models/Explosion.cs @@ -0,0 +1,28 @@ +namespace SpacePew.Models +{ + public class Explosion : EntityBase + { + public Explosion() + { + _health = 10; + } + + private int _health; + public override int Health + { + get + { + return _health; + } + set + { + _health = value; + } + } + + public override string TextureName + { + get { return "explosion_small"; } + } + } +} \ No newline at end of file diff --git a/SpacePew/Models/IEntity.cs b/SpacePew/Models/IEntity.cs new file mode 100644 index 0000000..21443c4 --- /dev/null +++ b/SpacePew/Models/IEntity.cs @@ -0,0 +1,28 @@ +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; + +namespace SpacePew.Models +{ + public interface IEntity + { + string TextureName { get; } + Texture2D Texture { get; set; } + Color[] GetTextureData(); + Color Color { get; set; } + + string Owner { get; set; } + + int Health { get; set; } + + Vector2 Origin { get; } + Vector2 Position { get; set; } + Vector2 Velocity { get; set; } + float Angle { get; set; } + + bool Collide(IEntity entity); + void CollideWithLevel(Level level); + void ApplyGravity(GameTime time); + void Update(GameTime time); + void Created(); + } +} diff --git a/SpacePew/Models/IKillable.cs b/SpacePew/Models/IKillable.cs new file mode 100644 index 0000000..bc842d5 --- /dev/null +++ b/SpacePew/Models/IKillable.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SpacePew.Models +{ + public interface IKillable + { + void Kill(); + } +} diff --git a/SpacePew/Models/Level.cs b/SpacePew/Models/Level.cs new file mode 100644 index 0000000..397763b --- /dev/null +++ b/SpacePew/Models/Level.cs @@ -0,0 +1,301 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; +using System.Collections; +using Microsoft.Xna.Framework.Media; + +namespace SpacePew.Models +{ + public class Tile + { + public Point Position { get; set; } + public Texture2D Texture { get; set; } + public Tile(Texture2D texure, Point position) + { + Texture = texure; + Position = position; + } + } + + public class TiledTexture : List + { + public int TileWidth { get; set; } + public int TileHeight { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XTiles { get; set; } + public int YTiles { get; set; } + public Vector2 Position { get; set; } + + public void GetData(Color[] textureData) + { + var buffer = new Color[TileWidth * TileHeight]; + for (var j = 0; j < XTiles; j++) + { + for (var i = 0; i < YTiles; i++) + { + var tile = this[i * XTiles + j]; + tile.Texture.GetData(buffer); + + for (var x = 0; x < TileWidth; x++) + { + for (var y = 0; y < TileHeight; y++) + { + var xpos = tile.Position.X + x; + var ypos = tile.Position.Y + y; + textureData[ypos * Width + xpos] = buffer[y * TileWidth + x]; + } + } + } + } + } + + public void SetData(Color[] textureData) + { + var buffer = new Color[TileWidth * TileHeight]; + for (var j = 0; j < XTiles; j++) + { + for (var i = 0; i < YTiles; i++) + { + var tile = this[i * XTiles + j]; + for (var x = 0; x < TileWidth; x++) + { + for (var y = 0; y < TileHeight; y++) + { + var xpos = tile.Position.X + x; + var ypos = tile.Position.Y + y; + buffer[y * TileWidth + x] = textureData[ypos * Width + xpos]; + } + } + + tile.Texture.SetData(buffer); + } + } + } + + public void SaveAsPng(Stream s, int width, int height) + { + throw new NotImplementedException("Har inte hunnit med.. borde gå att kopiera nån av Get/Set ovan och skapa upp en bild."); + } + } + + public class Level + { + public string Name { get; set; } + public string Description { get; set; } + public string FilePath { get; set; } + public byte[] OggVorbisSong { get; set; } + public TiledTexture Texture { get; set; } + public TiledTexture IndestructibleTexture { get; set; } + public TiledTexture DeformedTexture { get; set; } + private Color[] _deformedTextureData; + + public Queue Hits { get; set; } + + public bool[] CollisionData + { + get; + set; + } + + public bool[] IndestructibleCollisionData + { + get; + set; + } + + private int _width; + private int _height; + + public Level(GraphicsDevice device) + { + } + + public Level() + { } + + public void Initialize() + { + _width = Texture.Width; + _height = Texture.Height; + + Hits = new Queue(); + + var textureData = new Color[Texture.Width * Texture.Height]; + Texture.GetData(textureData); + + var indestructibleTextureData = new Color[IndestructibleTexture.Width * IndestructibleTexture.Height]; + IndestructibleTexture.GetData(indestructibleTextureData); + + CollisionData = new bool[Texture.Width * Texture.Height]; + + _deformedTextureData = new Color[DeformedTexture.Width * DeformedTexture.Height]; + for (int i = _deformedTextureData.Length - 1; i >= 0; i--) + { + _deformedTextureData[i] = textureData[i].A > 0 ? textureData[i] : Color.Transparent; + } + + DeformedTexture.SetData(_deformedTextureData); + + IndestructibleCollisionData = new bool[IndestructibleTexture.Width * IndestructibleTexture.Height]; + for (int i = 0; i < indestructibleTextureData.Length; i++) + { + if (indestructibleTextureData[i].A == 0) + { + IndestructibleCollisionData[i] = true; + } + } + } + + public void BuildLevelFromCollisionData() + { + for (int i = 0; i < CollisionData.Length; i++) + { + if (CollisionData[i]) + { + _deformedTextureData[i] = Color.Transparent; + } + } + + DeformedTexture.SetData(_deformedTextureData); + } + + public void BuildCollisionDataFromLevel() + { + for (int i = 0; i < _deformedTextureData.Length; i++) + { + if (_deformedTextureData[i] == Color.Transparent) + { + CollisionData[i] = true; + } + } + } + + /// + /// Returns the number of pixels of the entity that collides with the background + /// + /// + /// + /// + /// + public int Collide(IEntity entity, int centerX, int centerY) + { + int entityWidth = entity.Texture.Width; + int entityHeight = entity.Texture.Height; + + int posX = (int)entity.Position.X - (int)entity.Origin.X + centerX; + int posY = (int)entity.Position.Y - (int)entity.Origin.Y + centerY; + + if (posX < 1 || posY < 1 || posX > Texture.Width - entityWidth - 1 || posY > Texture.Height - entityHeight - 1) + return 1000000000; + + int hit = 0; + + Color[] data = entity.GetTextureData(); + + var player = entity as Player; + bool isLandingAngle = player != null && (MathHelper.ToDegrees(player.Angle) >= 345 || MathHelper.ToDegrees(player.Angle) <= 15) && player.Velocity.Y > 0; + + for (int y = 0; y < entityHeight; y++) + { + for (int x = 0; x < entityWidth; x++) + { + Color colorInPixel = data[(x - entity.Texture.Bounds.X) + (y - entity.Texture.Bounds.Y) * entity.Texture.Bounds.Width]; + if(colorInPixel.A != 0) + { + if (CollisionData[(posX + x) + (posY + y) * _width] == false) + { + if (isLandingAngle) + { + if (CollisionData[(posX + x) + (posY + y + 1) * _width] == false && + CollisionData[(posX + x + player.Texture.Width) + (posY + y + 1) * _width] == false) + { + if (player.Velocity.Y > 200f) + { + var yVelocity = (int)player.Velocity.Y; + SoundManager.Play("Audio/Waves/thump", player.Position); + player.Velocity = new Vector2(player.Velocity.X / 3, -player.Velocity.Y / 3); + return (yVelocity - 200) / 50; + } + + player.Land(); + return 0; + } + } + + entity.CollideWithLevel(this); + + CollisionData[(posX + x) + (posY + y) * _width] = true; + + Hits.Enqueue(new MapHit(entity)); + + hit++; + } + else if (IndestructibleCollisionData[(posX + x) + (posY + y) * _width] == false) + { + if (isLandingAngle) + { + if (IndestructibleCollisionData[(posX + x) + (posY + y + 1) * _width] == false && + IndestructibleCollisionData[(posX + x + player.Texture.Width) + (posY + y + 1) * _width] == false) + { + if (player.Velocity.Y > 200f) + { + var yVelocity = (int)player.Velocity.Y; + SoundManager.Play("Audio/Waves/thump", player.Position); + player.Velocity = new Vector2(player.Velocity.X / 3, -player.Velocity.Y / 3); + return (yVelocity - 200) / 50; + } + + player.Land(); + return 0; + } + } + + entity.CollideWithLevel(this); + + hit++; + } + } + } + } + + if (player != null) + { + return hit / 20; + } + + return hit; + } + + public void StopLevelSong() + { + _stopMusic = true; + } + + private bool _stopMusic; + public void PlayLevelSong() + { + using (var ms = new MemoryStream(this.OggVorbisSong)) + { + using (var vorbis = new NVorbis.NAudioSupport.VorbisWaveReader(ms)) + { + using (var waveOut = new NAudio.Wave.WaveOut()) + { + waveOut.Init(vorbis); + waveOut.Play(); + + while (!_stopMusic) + { + Thread.Sleep(100); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/SpacePew/Models/MapHit.cs b/SpacePew/Models/MapHit.cs new file mode 100644 index 0000000..97b360c --- /dev/null +++ b/SpacePew/Models/MapHit.cs @@ -0,0 +1,21 @@ +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; + +namespace SpacePew.Models +{ + public class MapHit + { + public Texture2D Texture { get; private set; } + public Vector2 Position { get; private set; } + public Vector2 Origin { get; private set; } + public float Angle { get; private set; } + + public MapHit(IEntity entity) + { + Texture = entity.Texture; + Position = entity.Position; + Origin = entity.Origin; + Angle = entity.Angle; + } + } +} diff --git a/SpacePew/Models/Player.cs b/SpacePew/Models/Player.cs new file mode 100644 index 0000000..6938018 --- /dev/null +++ b/SpacePew/Models/Player.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Audio; +using SpacePew.Networking; +using SpacePew.Camera; +using SpacePew.Models.Projectiles; +using SpacePew.Models.Weapons; + +namespace SpacePew.Models +{ + public class Player : EntityBase, IFocusable + { + #region Constructors + + private SoundEffectInstance _thrustSound; + public Player() + { + _weapon = new Cannon(); + _projectile = new Bullet(); + + _secondaryWeapon = new ClusterLauncher(); + _secondaryProjectile = new ClusterBomb(); + + Health = 100; + + Fuel = 50000; + + _lastCollide = DateTime.Now; + + _thrustSound = SoundManager.GetSoundEffectInstance("Audio/Waves/engine"); + } + + #endregion + + private readonly Vector2 _up = new Vector2(0, -1); + private Matrix _rotationMatrix; + + private Vector2 _direction; + + private readonly IProjectile _projectile; + private readonly IProjectile _secondaryProjectile; + + private IWeapon _secondaryWeapon; + public IWeapon SecondaryWeapon + { + get { return _secondaryWeapon; } + set { _secondaryWeapon = value; } + } + + private IWeapon _weapon; + public IWeapon Weapon + { + get { return _weapon; } + set { _weapon = value; } + } + + public bool IsRemotePlayer { get; set; } + public bool Landed { get; set; } + public double Fuel { get; private set; } + + #region Input handling + + private bool _isThrusting; + public void HandleKeyboard(KeyboardState state) + { + if (!MainGame.IsKeyboardInUse) + { + if (state.IsKeyDown(Keys.Right)) + { + if (Landed) + { + Landed = false; + } + + this.MoveRight(); + } + + if (state.IsKeyDown(Keys.Left)) + { + if (Landed) + { + Landed = false; + } + + this.MoveLeft(); + } + + if (state.IsKeyDown(Keys.Up)) + { + if (Landed) + { + Landed = false; + } + + _isThrusting = true; + + this.Thrust(); + } + else + { + _isThrusting = false; + } + + if (state.IsKeyDown(Keys.Space)) + { + this.Fire(); + } + else + { + if (this.Weapon.Heat > 0) + { + this.Weapon.Heat -= 1; + } + } + + if (state.IsKeyDown(Keys.LeftControl)) + { + this._secondaryWeapon.Fire(this._secondaryProjectile, this); + } + else + { + if (this._secondaryWeapon.Heat > 0) + { + this._secondaryWeapon.Heat -= 1; + } + } + + if (state.IsKeyDown(Keys.R)) + { + this.Health = 0; + } + } + } + + private void Fire() + { + this._weapon.Fire(_projectile, this); + } + + private void MoveRight() + { + Angle += 0.1f; + } + + private void MoveLeft() + { + Angle -= 0.1f; + } + + private void Thrust() + { + Fuel -= 1; + + _rotationMatrix = Matrix.CreateRotationZ(Angle); + _direction = Vector2.Transform(_up, _rotationMatrix); + + Velocity += _direction / VelocityModifier; + } + + #endregion + + #region IEntity Members + + public override string Owner { get; set; } + public override sealed int Health { get; set; } + + public override void ApplyGravity(GameTime time) + { + if (!Landed) + { + base.ApplyGravity(time); + } + } + + public override Vector2 Origin + { + get { return new Vector2(Texture.Width / 2, Texture.Height / 2); } + } + + public override string TextureName + { + get { return "player"; } + } + + #endregion + + private DateTime _lastCollide; + public void CollideWith(IEntity entity) + { + var projectile = entity as IProjectile; + if (projectile != null) + { + this.Health -= projectile.Damage; + return; + } + var player = entity as Player; + if (player != null) + { + if (_lastCollide <= DateTime.Now.AddMilliseconds(-80)) + { + _lastCollide = DateTime.Now; + + if (Math.Abs(this.Velocity.Length()) > Math.Abs(entity.Velocity.Length())) + { + entity.Velocity += this.Velocity / 2; + this.Velocity /= 4; + } + else + { + this.Velocity += entity.Velocity / 2; + entity.Velocity /= 4; + } + } + + this.Landed = false; + player.Landed = false; + } + } + + public override void CollideWithLevel(Level level) + { + Velocity -= (Velocity / 300); + } + + public static Player CreatePlayer(Vector2 startPosition) + { + return CreatePlayer(startPosition, string.Empty); + } + + public static Player CreatePlayer(Vector2 startPosition, string name) + { + return EntityFactory.Instance.CreateEntity( + name, + startPosition, + new Vector2(), + 0 + ); + } + + public void Land() + { + if (!Landed) + { + this.Velocity = new Vector2(0, 0); + this.Position = new Vector2(this.Position.X, this.Position.Y - 1); // Need to adjust because per pixel collision + // sometimes can't catch up with frame rate + this.Angle = 0; + + Landed = true; + } + } + + public void Kill() + { + SoundManager.Play("Audio/Waves/explosion", this.Position); + this.Position = new Vector2(25, 25); + this.Velocity = new Vector2(0, 0); + this.Angle = 0; + this.Health = 100; + this.Landed = false; + } + + public override void Update(GameTime time) + { + if (_isThrusting) + { + if (_thrustSound.State != SoundState.Playing) + { + _thrustSound.Play(); + } + + this.Texture = TextureManager.LoadTexture("player_thrusting"); + } + else + { + _thrustSound.Pause(); + this.Texture = TextureManager.LoadTexture("player"); + } + + base.Update(time); + } + + #region IFocusable Members + + Vector2 IFocusable.Position + { + get { return this.Position; } + } + + #endregion + } +} diff --git a/SpacePew/Models/Projectiles/BouncingBullet.cs b/SpacePew/Models/Projectiles/BouncingBullet.cs new file mode 100644 index 0000000..1e66b20 --- /dev/null +++ b/SpacePew/Models/Projectiles/BouncingBullet.cs @@ -0,0 +1,18 @@ +namespace SpacePew.Models.Projectiles +{ + public sealed class BouncingBullet : Bullet + { + public BouncingBullet() + { + Health = 30; + } + + public override CollisionType CollisionType + { + get + { + return CollisionType.Bounce; + } + } + } +} diff --git a/SpacePew/Models/Projectiles/Bullet.cs b/SpacePew/Models/Projectiles/Bullet.cs new file mode 100644 index 0000000..2cd6353 --- /dev/null +++ b/SpacePew/Models/Projectiles/Bullet.cs @@ -0,0 +1,70 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace SpacePew.Models.Projectiles +{ + public class Bullet : ProjectileBase + { + #region Constructors + + public Bullet() + { + this.Color = Color.White; + this.Velocity = new Vector2(); + this.Angle = 0; + this.Health = 15; + } + + public Bullet(Vector2 position) + : this() + { + this.Position = position; + } + + #endregion + + #region IEntity Members + + public override sealed int Health + { + get; + set; + } + + public override string TextureName + { + get { return "bullet"; } + } + + #endregion + + #region IProjectile Members + + public override string FireSoundAssetName + { + get { return "Audio/Waves/bullet_sound"; } + } + + public override string HitSoundAssetName + { + get { return "Audio/Waves/bullet_hit"; } + } + + public override int Damage + { + get { return 5; } + } + + public override float Speed + { + get { return 400f; } + } + + public override CollisionType CollisionType + { + get { return CollisionType.Explode; } + } + + #endregion + } +} \ No newline at end of file diff --git a/SpacePew/Models/Projectiles/ClusterBomb.cs b/SpacePew/Models/Projectiles/ClusterBomb.cs new file mode 100644 index 0000000..be8cdb3 --- /dev/null +++ b/SpacePew/Models/Projectiles/ClusterBomb.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; +using SpacePew.Models.Weapons; + +namespace SpacePew.Models.Projectiles +{ + class ClusterBomb : Bullet, IKillable + { + private const int FuseTime = 7000; //time in milliseconds before it explodes + private double _elapsed = 0; + readonly DateTime _startTick; + + public override int Damage + { + get { return 10; } + } + + public override float Speed + { + get { return 100; } + } + + public ClusterBomb() + { + _startTick = DateTime.Now; + } + + public override Color Color + { + get + { + var r = (byte)((_elapsed / FuseTime) * 255f); + var g = (byte)(128 + Math.Sin(_elapsed / 150f) * 127f); + var b = (byte)(255 - g); + return new Color(r, g, b); + } + } + + public override void Update(Microsoft.Xna.Framework.GameTime time) + { + _elapsed = (DateTime.Now - _startTick).TotalMilliseconds; + + if (_elapsed >= FuseTime) + { + this.Health = 0; + } + + base.Update(time); + } + + private void CreateCluster(float angle, string owner) + { + var entity = EntityFactory.Instance.CreateEntity( + typeof(Bullet), + owner, + Position, + Vector2.Zero, + angle); + + Matrix m = Matrix.CreateRotationZ(angle + (float)(WeaponBase.Randomizer.NextDouble() * .2f - .1f)); + Vector2 velocity = Vector2.Transform(-Vector2.UnitY, m); + + entity.Position += velocity * 10; + entity.Velocity = velocity * entity.Speed * (float)(WeaponBase.Randomizer.NextDouble() + 0.5f); + + //TODO.. fix + //FiredShots.Enqueue(entity); + } + + public override void ApplyGravity(GameTime time) + { + float timeSeconds = time.ElapsedGameTime.Milliseconds * .001f; + Velocity = new Vector2( + Velocity.X, + Velocity.Y + (timeSeconds * GravityModifier * 0.5f)); + } + + #region IKillable Members + + public void Kill() + { + //TODO: make sure the clusters are only created on the client that created the cluster, and then let the server send them to all klients + for (float i = 0; i < 45; i += .5f) + CreateCluster(i, this.Owner); + } + + #endregion + } +} diff --git a/SpacePew/Models/Projectiles/CollisionType.cs b/SpacePew/Models/Projectiles/CollisionType.cs new file mode 100644 index 0000000..8830058 --- /dev/null +++ b/SpacePew/Models/Projectiles/CollisionType.cs @@ -0,0 +1,8 @@ +namespace SpacePew.Models.Projectiles +{ + public enum CollisionType + { + Explode, + Bounce + } +} \ No newline at end of file diff --git a/SpacePew/Models/Projectiles/HomingBullet.cs b/SpacePew/Models/Projectiles/HomingBullet.cs new file mode 100644 index 0000000..6ffede2 --- /dev/null +++ b/SpacePew/Models/Projectiles/HomingBullet.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Linq; + +using Microsoft.Xna.Framework; + +namespace SpacePew.Models.Projectiles +{ + public class HomingBullet : Bullet + { + public HomingBullet() + { + this.Color = Color.PowderBlue; + } + + public override float Speed + { + get + { + return 200f; + } + } + + public override int Damage + { + get + { + return 3; + } + } + + private Player _target; + + const float TimeIncrementSpeed = 5; + + public override void Update(GameTime time) + { + base.Update(time); + + if (_target == null) + { + GetTarget(); + } + + if (_target != null) + { + var delta = new Vector2(_target.Position.X - this.Position.X, _target.Position.Y - this.Position.Y); + + if (delta.Length() > TimeIncrementSpeed) + { + delta.Normalize(); + delta.X *= TimeIncrementSpeed; + delta.Y *= TimeIncrementSpeed; + } + + this.Position = new Vector2(Position.X + (int)delta.X, Position.Y + (int)delta.Y); + } + } + + private void GetTarget() + { + var players = EntityFactory.Instance.Entities.OfType().Where(player => player.Owner != this.Owner).ToList(); + + if (players.Count > 0) + { + _target = FindClosestPlayer(players, this.Position); + } + } + + private static Player FindClosestPlayer(IEnumerable list, Vector2 pointToCompare) + { + Player closestPlayer = list.ToList()[0]; + + float distance = float.PositiveInfinity; + foreach (var player in list) + { + float dx = pointToCompare.X - player.Position.X; + float dy = pointToCompare.Y - player.Position.Y; + + float d = dx * dx - dy * dy; + + if (d < distance) + { + distance = d; + closestPlayer = player; + } + } + + return closestPlayer; + } + } +} \ No newline at end of file diff --git a/SpacePew/Models/Projectiles/IProjectile.cs b/SpacePew/Models/Projectiles/IProjectile.cs new file mode 100644 index 0000000..1635c8c --- /dev/null +++ b/SpacePew/Models/Projectiles/IProjectile.cs @@ -0,0 +1,12 @@ +namespace SpacePew.Models.Projectiles +{ + public interface IProjectile : IEntity + { + string HitSoundAssetName { get; } + string FireSoundAssetName { get; } + + int Damage { get; } + float Speed { get; } + CollisionType CollisionType { get; } + } +} \ No newline at end of file diff --git a/SpacePew/Models/Projectiles/LongShot.cs b/SpacePew/Models/Projectiles/LongShot.cs new file mode 100644 index 0000000..0ac49c2 --- /dev/null +++ b/SpacePew/Models/Projectiles/LongShot.cs @@ -0,0 +1,29 @@ +using System; + +using Microsoft.Xna.Framework; + +namespace SpacePew.Models.Projectiles +{ + public class LongShot : Bullet + { + public override Vector2 Origin + { + get { return new Vector2(5, 12); } + } + + public override string TextureName + { + get { return "longshot"; } + } + + public override void ApplyGravity(GameTime time) + { + base.ApplyGravity(time); + + //Sätt rotationen till lika som riktningen som pilen åker + Vector2 vNormal = Velocity; + vNormal.Normalize(); + Angle = (vNormal.X > 0 ? 1f : -1f) * (float)(Math.Acos(Vector2.Dot(-Vector2.UnitY, vNormal))); + } + } +} diff --git a/SpacePew/Models/Projectiles/Missile.cs b/SpacePew/Models/Projectiles/Missile.cs new file mode 100644 index 0000000..903d716 --- /dev/null +++ b/SpacePew/Models/Projectiles/Missile.cs @@ -0,0 +1,75 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace SpacePew.Models.Projectiles +{ + public sealed class Missile : ProjectileBase + { + #region Constructors + + public Missile() + { + this.Color = Color.Red; + this.Velocity = new Vector2(); + this.Angle = 0; + this.Health = 10; + } + + public Missile(Vector2 position) + : this() + { + this.Position = position; + } + + #endregion + + #region IEntity Members + + public override void CollideWithLevel(Level level) + { + // blow stuff + } + + public override int Health + { + get; + set; + } + + public override string TextureName + { + get { return "bullet"; } + } + + #endregion + + #region IProjectile Members + + public override string FireSoundAssetName + { + get { return "Audio/Waves/bullet_sound"; } + } + + public override string HitSoundAssetName + { + get { return "Audio/Waves/bullet_hit"; } + } + + public override int Damage + { + get { return 50; } + } + + public override float Speed + { + get { return 600f; } + } + + public override CollisionType CollisionType + { + get { return CollisionType.Explode; } + } + + #endregion + } +} diff --git a/SpacePew/Models/Projectiles/ProjectileBase.cs b/SpacePew/Models/Projectiles/ProjectileBase.cs new file mode 100644 index 0000000..8288fb8 --- /dev/null +++ b/SpacePew/Models/Projectiles/ProjectileBase.cs @@ -0,0 +1,48 @@ +namespace SpacePew.Models.Projectiles +{ + public abstract class ProjectileBase : EntityBase, IProjectile + { + #region IEntity Members + + public override bool Collide(IEntity entity) + { + bool collided = base.Collide(entity); + + if (collided) + { + SoundManager.Play(HitSoundAssetName, this.Position); + } + + return collided; + } + + public override void CollideWithLevel(Level level) + { + if (this.CollisionType == CollisionType.Explode) + { + // blow up somehow (particle system is there, but needs to trash the level a little more too) + } + else if (this.CollisionType == CollisionType.Bounce) + { + this.Velocity -= (this.Velocity * 1.9f); + } + } + + public override void Created() + { + SoundManager.Play(FireSoundAssetName, this.Position); + } + + #endregion + + #region IProjectile Members + + public abstract string HitSoundAssetName { get; } + public abstract string FireSoundAssetName { get; } + public abstract int Damage { get; } + public abstract float Speed { get; } + public abstract CollisionType CollisionType { get; } + + #endregion + } +} \ No newline at end of file diff --git a/SpacePew/Models/Weapons/Cannon.cs b/SpacePew/Models/Weapons/Cannon.cs new file mode 100644 index 0000000..5b983db --- /dev/null +++ b/SpacePew/Models/Weapons/Cannon.cs @@ -0,0 +1,20 @@ +namespace SpacePew.Models.Weapons +{ + public class Cannon : WeaponBase + { + public override float HeatGeneration + { + get { return 0.4f; } + } + + public override int Delay + { + get { return 35; } + } + + public override int Spread + { + get { return 0; } + } + } +} diff --git a/SpacePew/Models/Weapons/ClusterLauncher.cs b/SpacePew/Models/Weapons/ClusterLauncher.cs new file mode 100644 index 0000000..e24f162 --- /dev/null +++ b/SpacePew/Models/Weapons/ClusterLauncher.cs @@ -0,0 +1,20 @@ +namespace SpacePew.Models.Weapons +{ + public class ClusterLauncher : WeaponBase + { + public override float HeatGeneration + { + get { return 100f; } + } + + public override int Delay + { + get { return 0; } + } + + public override int Spread + { + get { return 0; } + } + } +} diff --git a/SpacePew/Models/Weapons/IWeapon.cs b/SpacePew/Models/Weapons/IWeapon.cs new file mode 100644 index 0000000..bac7854 --- /dev/null +++ b/SpacePew/Models/Weapons/IWeapon.cs @@ -0,0 +1,13 @@ +using SpacePew.Models.Projectiles; + +namespace SpacePew.Models.Weapons +{ + public interface IWeapon + { + int Delay { get; } + int Spread { get; } + float Heat { get; set; } + float HeatGeneration { get; } + void Fire(IProjectile projectile, Player player); + } +} \ No newline at end of file diff --git a/SpacePew/Models/Weapons/Launcher.cs b/SpacePew/Models/Weapons/Launcher.cs new file mode 100644 index 0000000..f3c4c53 --- /dev/null +++ b/SpacePew/Models/Weapons/Launcher.cs @@ -0,0 +1,20 @@ +namespace SpacePew.Models.Weapons +{ + public class Launcher : WeaponBase + { + public override float HeatGeneration + { + get { return 100f; } + } + + public override int Delay + { + get { return 200; } + } + + public override int Spread + { + get { return 0; } + } + } +} \ No newline at end of file diff --git a/SpacePew/Models/Weapons/SecondaryCannon.cs b/SpacePew/Models/Weapons/SecondaryCannon.cs new file mode 100644 index 0000000..db1668e --- /dev/null +++ b/SpacePew/Models/Weapons/SecondaryCannon.cs @@ -0,0 +1,24 @@ +namespace SpacePew.Models.Weapons +{ + /// + /// A slower version of the cannon, should be used for powerful projectiles like the homing bullets to even things out + /// + public class SecondaryCannon : Cannon + { + public override float HeatGeneration + { + get + { + return 1f; + } + } + + public override int Delay + { + get + { + return 80; + } + } + } +} \ No newline at end of file diff --git a/SpacePew/Models/Weapons/TriCannon.cs b/SpacePew/Models/Weapons/TriCannon.cs new file mode 100644 index 0000000..ee667a7 --- /dev/null +++ b/SpacePew/Models/Weapons/TriCannon.cs @@ -0,0 +1,64 @@ +using System; + +using Microsoft.Xna.Framework; +using SpacePew.Models.Projectiles; + +namespace SpacePew.Models.Weapons +{ + public class TriCannon : WeaponBase + { + public override float HeatGeneration + { + get { return 0.8f; } + } + + public override int Delay + { + get { return 40; } + } + + public override int Spread + { + get { return 3; } + } + + public override void Fire(IProjectile projectile, Player player) + { + if (Heat + this.HeatGeneration > MaxHeat) + return; + + Heat += 1f * this.HeatGeneration; + + if (_lastShot <= DateTime.Now.AddMilliseconds(-this.Delay - (Heat >= HeatStartAffectAt ? Heat : 0))) + { + float angle = player.Angle + (float)(Randomizer.Next(-Spread * 100, Spread * 100) / 9000.0); + + InternalFire(projectile, player, angle - 0.15f); + InternalFire(projectile, player, angle); + InternalFire(projectile, player, angle + 0.15f); + + _lastShot = DateTime.Now; + } + } + + private static void InternalFire(IProjectile projectile, IEntity player, float angle) + { + var entity = EntityFactory.Instance.CreateEntity( + projectile.GetType(), + player.Owner, + player.Position, + new Vector2(), + angle); + + Matrix m = Matrix.CreateRotationZ(angle); + Vector2 velocity = Vector2.Transform(-Vector2.UnitY, m); + + entity.Position += velocity * (player.Origin.Y + entity.Origin.Y); + entity.Velocity = velocity * projectile.Speed; + + entity.Velocity = player.Velocity + entity.Velocity; + + FiredShots.Enqueue(entity); + } + } +} \ No newline at end of file diff --git a/SpacePew/Models/Weapons/WeaponBase.cs b/SpacePew/Models/Weapons/WeaponBase.cs new file mode 100644 index 0000000..e0dfed5 --- /dev/null +++ b/SpacePew/Models/Weapons/WeaponBase.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Xna.Framework; +using SpacePew.Models.Projectiles; + +namespace SpacePew.Models.Weapons +{ + public abstract class WeaponBase : IWeapon + { + protected const int MaxHeat = 100; + protected const int HeatStartAffectAt = MaxHeat / 2; + + static WeaponBase() + { + FiredShots = new Queue(); + } + + public static Queue FiredShots; + + protected DateTime _lastShot; + + private static Random _randomizer; + + public static Random Randomizer + { + get { return _randomizer ?? (_randomizer = new Random()); } + } + + private float _heat; + + protected WeaponBase() + { + _lastShot = DateTime.Now; + } + + public virtual float Heat + { + get { return _heat; } + set { _heat = value; } + } + + #region IWeapon Members + + public abstract float HeatGeneration { get; } + public abstract int Delay { get; } + public abstract int Spread { get; } + + public virtual void Fire(IProjectile projectile, Player player) + { + if (_heat + this.HeatGeneration > MaxHeat) + return; + + _heat += 1f * this.HeatGeneration; + + if (_lastShot <= DateTime.Now.AddMilliseconds(-this.Delay - (_heat >= HeatStartAffectAt ? _heat : 0))) + { + float angle = player.Angle + (float)(Randomizer.Next(-Spread * 100, Spread * 100) / 9000.0); + + var entity = EntityFactory.Instance.CreateEntity( + projectile.GetType(), + player.Owner, + player.Position, + new Vector2(), + angle); + + Matrix m = Matrix.CreateRotationZ(angle); + Vector2 velocity = Vector2.Transform(-Vector2.UnitY, m); + + entity.Position += velocity * (player.Origin.Y + entity.Origin.Y); + entity.Velocity = velocity * projectile.Speed; + + entity.Velocity = player.Velocity + entity.Velocity; + FiredShots.Enqueue(entity); + + _lastShot = DateTime.Now; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/SpacePew/Networking/NetworkMessage.cs b/SpacePew/Networking/NetworkMessage.cs new file mode 100644 index 0000000..86e7c1e --- /dev/null +++ b/SpacePew/Networking/NetworkMessage.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using SpacePew.Models; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace SpacePew.Networking +{ + public class NetworkMessage + { + // TODO: Add SoundAssetName to make NetworkMessenger play custom sounds upon different messages + public string Message { get; set; } + public DateTime Sent { get; set; } + public Color Color { get; set; } + public bool IsChatMessage { get; set; } + } +} diff --git a/SpacePew/Networking/NetworkMessenger.cs b/SpacePew/Networking/NetworkMessenger.cs new file mode 100644 index 0000000..c743e4b --- /dev/null +++ b/SpacePew/Networking/NetworkMessenger.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.GamerServices; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Media; +using Microsoft.Xna.Framework.Net; +using Microsoft.Xna.Framework.Storage; +using SpacePew.Models; + + +namespace SpacePew.Networking +{ + /// + /// TODO: This is not really a network messenger yet, so we need to change the functionality a little (send all message over network in the loop down there) + /// + public class NetworkMessenger : Microsoft.Xna.Framework.GameComponent, IDrawable + { + private readonly MainGame _game; + private SpriteBatch _spriteBatch; + private SpriteFont _netFont; + private Rectangle _chatRectangle; + private Rectangle _textBounds; + private static UdpClient _client; + + private static List _messages; + + private const int DefaultMessageLifeTime = 10; + + public NetworkMessenger(MainGame game, UdpClient client) + : base(game) + { + _game = game; + _client = client; + _messages = new List(); + } + + /// + /// Allows the game component to perform any initialization it needs to before starting + /// to run. This is where it can query for any required services and load content. + /// + public override void Initialize() + { + _spriteBatch = new SpriteBatch(_game.GraphicsDevice); + _netFont = _game.Content.Load("Fonts\\Default"); + _chatRectangle = new Rectangle(300, 20, 300, _game.GraphicsDevice.Viewport.Height - 40); + _textBounds = new Rectangle(); + + base.Initialize(); + } + + public static void AddDeath(IEntity entity) + { + _client.SendDeath(entity); + } + + public static void DisplayMessage(NetworkMessage message) + { + _messages.Add(message); + } + + public static void SendMessage(NetworkMessage message) + { + _messages.Add(message); + _client.SendMessage(message); + + if (message.IsChatMessage) + { + SoundManager.Play("Audio/Waves/message", _client.LocalPlayer.Position); + } + } + + private string _chatMessage = string.Empty; + + private KeyboardState _oldKeyboardState; + private KeyboardState _currentKeyboardState; + + bool chatting = false; + + /// + /// Allows the game component to update itself. + /// + /// Provides a snapshot of timing values. + public override void Update(GameTime gameTime) + { + MainGame.IsKeyboardInUse = chatting; + + _oldKeyboardState = _currentKeyboardState; + + _currentKeyboardState = Keyboard.GetState(); + Keys[] pressedKeys = _currentKeyboardState.GetPressedKeys(); + + bool isShiftDown = pressedKeys.Any(KeyboardHelper.IsShift); + + foreach (Keys key in pressedKeys) + { + if (!_oldKeyboardState.IsKeyUp(key)) continue; + + if (key == Keys.T && !chatting) + { + chatting = true; + continue; + } + + if (!chatting) continue; + + switch (key) + { + case Keys.Back: + _chatMessage = _chatMessage.Remove(_chatMessage.Length - 1, 1); + break; + case Keys.Space: + _chatMessage = _chatMessage + " "; + break; + default: + if (key == Keys.Enter && chatting) + { + chatting = false; + SendMessage(new NetworkMessage() + { + Color = _client.LocalPlayer.Color, + Message = + string.Format("{0}: {1}", _client.LocalPlayer.Owner, + _chatMessage), + Sent = DateTime.Now, + IsChatMessage = true + }); + + _chatMessage = string.Empty; + } + else + { + if (isShiftDown) + { + _chatMessage += KeyboardHelper.ToPrintableString(key, KbModifiers.None).ToUpper(); + } + else + { + _chatMessage += KeyboardHelper.ToPrintableString(key, KbModifiers.None); + } + } + break; + } + } + + for (int i = _messages.Count - 1; i >= 0; i--) + { + if (_messages[i].Sent.AddSeconds(DefaultMessageLifeTime) <= DateTime.Now) + { + _messages.RemoveAt(i); + } + } + + base.Update(gameTime); + } + + public void Draw(GameTime gameTime) + { + _spriteBatch.Begin(); + + int yOffset = 0; + + if (chatting) + { + _spriteBatch.DrawString(_netFont, "Chat: " + _chatMessage, + new Vector2(300, _game.GraphicsDevice.Viewport.Height - 140), Color.White); + } + + foreach (var message in _messages) + { + DrawString(_spriteBatch, + _netFont, + message.Message, + _chatRectangle, + message.Color, + TextAlignment.TopLeft, + true, + new Vector2(0, (_messages.IndexOf(message) * 15) + yOffset), + out _textBounds); + + yOffset += _textBounds.Height; + } + + _spriteBatch.End(); + } + + public int DrawOrder + { + get { return 5; } + } + + public bool Visible + { + get { return true; } + } + + private enum TextAlignment + { + Top, + Left, + Middle, + Right, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight + } + + private static void DrawString(SpriteBatch sb, SpriteFont fnt, string text, Rectangle r, + Color col, TextAlignment align, bool performWordWrap, Vector2 offsett, out Rectangle textBounds) + { + textBounds = r; + if (text == null) return; + if (text == string.Empty) return; + + var lines = new StringCollection(); + lines.AddRange(text.Split(new string[] { "\\n" }, StringSplitOptions.RemoveEmptyEntries)); + + Rectangle tmprect = ProcessLines(fnt, r, performWordWrap, lines); + + var pos = new Vector2(r.X, r.Y); + int aStyle = 0; + + switch (align) + { + case TextAlignment.Bottom: + pos.Y = r.Bottom - tmprect.Height; + aStyle = 1; + break; + case TextAlignment.BottomLeft: + pos.Y = r.Bottom - tmprect.Height; + aStyle = 0; + break; + case TextAlignment.BottomRight: + pos.Y = r.Bottom - tmprect.Height; + aStyle = 2; + break; + case TextAlignment.Left: + pos.Y = r.Y + ((r.Height / 2) - (tmprect.Height / 2)); + aStyle = 0; + break; + case TextAlignment.Middle: + pos.Y = r.Y + ((r.Height / 2) - (tmprect.Height / 2)); + aStyle = 1; + break; + case TextAlignment.Right: + pos.Y = r.Y + ((r.Height / 2) - (tmprect.Height / 2)); + aStyle = 2; + break; + case TextAlignment.Top: + aStyle = 1; + break; + case TextAlignment.TopLeft: + aStyle = 0; + break; + case TextAlignment.TopRight: + aStyle = 2; + break; + } + + foreach (string txt in lines) + { + Vector2 size = fnt.MeasureString(txt); + switch (aStyle) + { + case 0: + pos.X = r.X; + break; + case 1: + pos.X = r.X + ((r.Width / 2) - (size.X / 2)); + break; + case 2: + pos.X = r.Right - size.X; + break; + } + + sb.DrawString(fnt, txt, pos + offsett, col); + pos.Y += fnt.LineSpacing; + } + + textBounds = tmprect; + } + + private static Rectangle ProcessLines(SpriteFont fnt, Rectangle r, bool performWordWrap, StringCollection lines) + { + Rectangle bounds = r; + bounds.Width = 0; + bounds.Height = 0; + int index = 0; + float Width; + bool lineInserted = false; + while (index < lines.Count) + { + string linetext = lines[index]; + + Vector2 size = fnt.MeasureString(linetext); + + if (performWordWrap && size.X > r.Width) + { + string endspace = string.Empty; + if (linetext.EndsWith(" ")) + { + endspace = " "; + linetext = linetext.TrimEnd(); + } + + int i = linetext.LastIndexOf(" ", StringComparison.InvariantCulture); + if (i != -1) + { + string lastword = linetext.Substring(i + 1); + + if (index == lines.Count - 1) + { + lines.Add(lastword); + lineInserted = true; + } + else + { + if (lineInserted) + { + lines[index + 1] = lastword + endspace + lines[index + 1]; + } + else + { + lines.Insert(index + 1, lastword); + lineInserted = true; + } + } + + lines[index] = linetext.Substring(0, i + 1); + + } + else + { + lineInserted = false; + size = fnt.MeasureString(lines[index]); + if (size.X > bounds.Width) Width = size.X; + bounds.Height += fnt.LineSpacing; + index++; + } + } + else + { + lineInserted = false; + size = fnt.MeasureString(lines[index]); + if (size.X > bounds.Width) bounds.Width = (int)size.X; + bounds.Height += fnt.LineSpacing; + index++; + } + } + + return bounds; + } + + + event EventHandler IDrawable.DrawOrderChanged + { + add { } + remove { } + } + + event EventHandler IDrawable.VisibleChanged + { + add { } + remove { } + } + } +} \ No newline at end of file diff --git a/SpacePew/Networking/ScoreBoard.cs b/SpacePew/Networking/ScoreBoard.cs new file mode 100644 index 0000000..db63320 --- /dev/null +++ b/SpacePew/Networking/ScoreBoard.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.GamerServices; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Media; +using Microsoft.Xna.Framework.Net; +using Microsoft.Xna.Framework.Storage; +using SpacePew.Models; + + +namespace SpacePew.Networking +{ + /// + /// This is a game component that implements IUpdateable. + /// + public class ScoreBoard : Microsoft.Xna.Framework.GameComponent, IDrawable + { + public static List CurrentScoreBoard { get; set; } + + private readonly MainGame _game; + private readonly UdpClient _client; + private Texture2D _scoreBoardTexture; + + private SpriteBatch _spriteBatch; + + private SpriteFont _scoreFont; + + public ScoreBoard(MainGame game, UdpClient client) + : base(game) + { + _game = game; + _client = client; + + CurrentScoreBoard = new List(); + } + + /// + /// Allows the game component to perform any initialization it needs to before starting + /// to run. This is where it can query for any required services and load content. + /// + public override void Initialize() + { + _spriteBatch = new SpriteBatch(_game.GraphicsDevice); + + _scoreFont = _game.Content.Load("Fonts\\Default"); + _scoreBoardTexture = _game.Content.Load("scoreboard"); + + base.Initialize(); + } + + bool requestedScoreBoard; + + /// + /// Allows the game component to update itself. + /// + /// Provides a snapshot of timing values. + public override void Update(GameTime gameTime) + { + if (Keyboard.GetState().IsKeyDown(Keys.Tab)) + { + if (!requestedScoreBoard) + { + _client.RequestScoreBoard(); + } + + requestedScoreBoard = true; + } + else + { + requestedScoreBoard = false; + } + + base.Update(gameTime); + } + + public void Draw(GameTime gameTime) + { + if (Keyboard.GetState().IsKeyDown(Keys.Tab)) + { + _spriteBatch.Begin(); + + _spriteBatch.Draw(_scoreBoardTexture, new Vector2(20, 20), Color.White); + + foreach (ScoreBoardItem item in CurrentScoreBoard) + { + Player player = ((List)_client.Players).Find(p => p.Owner == item.Name); + + if (player != null) + { + Color color = player.Color; + + int yOffset = (CurrentScoreBoard.IndexOf(item) * 15) + 70; + + _spriteBatch.DrawString(_scoreFont, item.Name, new Vector2(35, yOffset), color); + _spriteBatch.DrawString(_scoreFont, item.Kills.ToString(CultureInfo.InvariantCulture), new Vector2(185, yOffset), color); + _spriteBatch.DrawString(_scoreFont, item.Deaths.ToString(CultureInfo.InvariantCulture), new Vector2(243, yOffset), color); + _spriteBatch.DrawString(_scoreFont, (DateTime.Now - item.Joined).Minutes.ToString(CultureInfo.InvariantCulture), + new Vector2(314, yOffset), color); + _spriteBatch.DrawString(_scoreFont, item.Ping.ToString(CultureInfo.InvariantCulture), new Vector2(373, yOffset), color); + } + } + + _spriteBatch.End(); + } + } + + public int DrawOrder + { + get { return 5; } + } + + public bool Visible + { + get { return true; } + } + + event EventHandler IDrawable.DrawOrderChanged + { + add { } + remove { } + } + + event EventHandler IDrawable.VisibleChanged + { + add { } + remove { } + } + } +} \ No newline at end of file diff --git a/SpacePew/Networking/ScoreBoardItem.cs b/SpacePew/Networking/ScoreBoardItem.cs new file mode 100644 index 0000000..3a137c8 --- /dev/null +++ b/SpacePew/Networking/ScoreBoardItem.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SpacePew.Networking +{ + public class ScoreBoardItem + { + public string Name { get; set; } + public int Kills { get; set; } + public int Deaths { get; set; } + public DateTime Joined { get; set; } + public long Ping { get; set; } + } +} diff --git a/SpacePew/Networking/StreamingClient.cs b/SpacePew/Networking/StreamingClient.cs new file mode 100644 index 0000000..e6939c9 --- /dev/null +++ b/SpacePew/Networking/StreamingClient.cs @@ -0,0 +1,64 @@ +using System.Diagnostics; +using Lidgren.Network; +using System.IO; +using SpacePew.Common; + +namespace SpacePew.Networking +{ + public class StreamingClient + { + private FileStream _inputStream; + private int _sentOffset; + private int _chunkLen; + private byte[] _tmpBuffer; + private NetConnection _connection; + + public StreamingClient(NetConnection conn, string fileName) + { + _connection = conn; + _inputStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); + _chunkLen = _connection.Peer.Configuration.MaximumTransmissionUnit - 20; + _tmpBuffer = new byte[_chunkLen]; + _sentOffset = 0; + } + + public void Heartbeat() + { + if (_inputStream == null) + return; + + if (_connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) + { + long remaining = _inputStream.Length - _sentOffset; + int sendBytes = (remaining > _chunkLen ? _chunkLen : (int)remaining); + + _inputStream.Read(_tmpBuffer, 0, sendBytes); + + NetOutgoingMessage message; + if (_sentOffset == 0) + { + message = _connection.Peer.CreateMessage(sendBytes + 8); + message.Write((int)UdpNetworkPacketType.LevelResponse); + message.Write((ulong)_inputStream.Length); + message.Write(Path.GetFileName(_inputStream.Name)); + + _connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, 1); + } + + message = _connection.Peer.CreateMessage(sendBytes + 8); + message.Write((int)UdpNetworkPacketType.LevelResponse); + message.Write(_tmpBuffer, 0, sendBytes); + + _connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, 1); + _sentOffset += sendBytes; + + if (remaining - sendBytes <= 0) + { + _inputStream.Close(); + _inputStream.Dispose(); + _inputStream = null; + } + } + } + } +} diff --git a/SpacePew/Networking/UdpBase.cs b/SpacePew/Networking/UdpBase.cs new file mode 100644 index 0000000..93e8c26 --- /dev/null +++ b/SpacePew/Networking/UdpBase.cs @@ -0,0 +1,62 @@ +using System.IO; +using System.IO.Compression; +using System.Runtime.Serialization.Formatters.Binary; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Lidgren.Network; +using SpacePew.Common; +using System.Net; + +namespace SpacePew.Networking +{ + public abstract class UdpBase + { + protected Color[] _playerColors = + { + Color.Red, + Color.Blue, + Color.Green, + Color.Yellow, + Color.Purple, + Color.White, + Color.Red, + Color.LightBlue, + Color.Orange, + Color.Gray + }; + + protected byte[] ObjectToByteArray(object obj) + { + if (obj == null) + return null; + + var memoryStream = new MemoryStream(); + var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress); + var formatter = new BinaryFormatter(); + formatter.Serialize(gZipStream, obj); + + gZipStream.Close(); + memoryStream.Close(); + + return memoryStream.ToArray(); + } + + protected object ByteArrayToObject(byte[] bytes) + { + var memoryStream = new MemoryStream(); + var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress); + + var formatter = new BinaryFormatter(); + + memoryStream.Write(bytes, 0, bytes.Length); + memoryStream.Seek(0, SeekOrigin.Begin); + + var obj = formatter.Deserialize(gzipStream); + + gzipStream.Close(); + memoryStream.Close(); + + return obj; + } + } +} diff --git a/SpacePew/Networking/UdpClient.cs b/SpacePew/Networking/UdpClient.cs new file mode 100644 index 0000000..020685c --- /dev/null +++ b/SpacePew/Networking/UdpClient.cs @@ -0,0 +1,586 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +using SpacePew.Extensions; +using Lidgren.Network; +using System.Collections; +using SpacePew.Models; +using SpacePew.Common; +using System.Net; + +namespace SpacePew.Networking +{ + public class UdpClient : UdpBase + { + private string _playerName; + private readonly NetClient _client; + private Level _level; + + private readonly MainGame _game; + + private int _colorIndex; + + private IPEndPoint _masterServerEndpoint; + + private UdpClient() + { + + } + + public UdpClient(MainGame game) + { + _game = game; + _masterServerEndpoint = NetUtility.Resolve("spacepew.wodanaz.se", Constants.MasterServerPort); // TODO: Fixa upp masterserver någonstans + EntitiesToSend = new List(); + + var configuration = new NetPeerConfiguration("SpacePew"); + + configuration.EnableMessageType(NetIncomingMessageType.DiscoveryRequest); + configuration.EnableMessageType(NetIncomingMessageType.DiscoveryResponse); + configuration.EnableMessageType(NetIncomingMessageType.UnconnectedData); + configuration.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess); + + _client = new NetClient(configuration); + _client.Start(); + } + + public List EntitiesToSend { get; set; } + + public NetClient CurrentClient + { + get { return _client; } + } + + private Player _localPlayer; + public Player LocalPlayer + { + get + { + return _localPlayer; + } + set + { + _localPlayer = value; + } + } + + private readonly List _players = new List(); + public IList Players + { + get + { + return _players; + } + } + + public bool IsSessionAlive + { + get + { + return _client != null && _client.ConnectionStatus == NetConnectionStatus.Connected; + } + } + + public void JoinSession(string host, string playerName) + { + JoinSession(new IPEndPoint(System.Net.Dns.GetHostAddresses(host)[0], SpacePew.Common.Constants.GameServerPort), playerName); + } + + public void JoinSession(IPEndPoint endpoint, string playerName) + { + _playerName = playerName; + + var hailMessage = _client.CreateMessage(playerName); + _client.Connect(endpoint, hailMessage); + + while (_client.ServerConnection == null) + { + // wait + } + + System.Threading.Thread.Sleep(500); // TODO: nya lidgren är dumt i huvet, grotta i vad skiten sätter i någon tråd se'n + + System.Diagnostics.Trace.WriteLine("Got server connection..."); + WriteLevelRequest(); + WaitForLevel(); + WriteJoiningMessage(); + WaitForJoin(); + + if (_level.OggVorbisSong != null) + { + new Thread(_level.PlayLevelSong).Start(); + } + } + + public void ExitSession(string reason) + { + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerDisconnecting); + message.Write(_playerName); + + _client.SendMessage(message, NetDeliveryMethod.ReliableOrdered); + + _client.Disconnect(reason); + } + + public void SendMessage(NetworkMessage message) + { + var netMessage = _client.CreateMessage(); + + netMessage.Write((int)UdpNetworkPacketType.MessageSent); + netMessage.Write(message.Color.ToVector3()); + netMessage.Write(message.Sent.ToString(CultureInfo.InvariantCulture)); + netMessage.Write(message.Message); + netMessage.Write(message.IsChatMessage); + + _client.SendMessage(netMessage, NetDeliveryMethod.UnreliableSequenced); + } + + public void RequestScoreBoard() + { + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.RequestingScoreboard); + + _client.SendMessage(message, NetDeliveryMethod.UnreliableSequenced); + } + + private void ReadMessage(NetIncomingMessage netMessage) + { + var color = new Color(netMessage.ReadVector3()); + var sent = DateTime.Parse(netMessage.ReadString(), CultureInfo.InvariantCulture); + string message = netMessage.ReadString(); + bool isChatMessage = netMessage.ReadBoolean(); + + NetworkMessenger.DisplayMessage(new NetworkMessage() { Color = color, Sent = sent, Message = message, IsChatMessage = isChatMessage}); + } + + public void SendDeath(IEntity killedBy) + { + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerDying); + message.Write(_playerName); + message.Write(killedBy.Owner); // TODO: server side score board + + _client.SendMessage(message, NetDeliveryMethod.ReliableUnordered); + } + + private void WriteLevelRequest() + { + Trace.WriteLine("WriteLevelRequest"); + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.LevelRequest); + + _client.SendMessage(message, NetDeliveryMethod.ReliableUnordered); + System.Diagnostics.Trace.WriteLine("Sent level request..."); + } + + private void WriteJoiningMessage() + { + Trace.WriteLine("WriteJoiningMessage"); + + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerJoining); + message.Write(_playerName); + + _client.SendMessage(message, NetDeliveryMethod.ReliableUnordered); + } + + private void WaitForJoin() + { + System.Diagnostics.Trace.WriteLine("WaitForJoin"); + bool hasAnswer = false; + while (!hasAnswer) // wait for answer + { + NetIncomingMessage message; + while ((message = _client.ReadMessage()) != null) + { + if (message.MessageType == NetIncomingMessageType.Data) + { + var packetType = (UdpNetworkPacketType)message.ReadInt32(); + if (packetType == UdpNetworkPacketType.PlayerJoined) + { + string owner = message.ReadString(); + + int colorIndex = message.ReadInt32(); + Color color = _playerColors[colorIndex]; + + _colorIndex = Array.IndexOf(_playerColors, color); + + Vector2 pos = message.ReadVector2(); + Vector2 velocity = message.ReadVector2(); + float angle = message.ReadFloat(); + int collisionSize = message.ReadInt32(); + var collisionData = message.ReadBytes(collisionSize); + + _level.CollisionData = (bool[])ByteArrayToObject(collisionData); + _level.BuildLevelFromCollisionData(); + + Player p = Player.CreatePlayer(pos, owner); + p.Color = color; + p.Position = pos; + p.Velocity = velocity; + p.Angle = angle; + + _localPlayer = p; + _players.Add(p); + + hasAnswer = true; + } + } + } + } + } + + public void Update() + { + NetIncomingMessage message; + + while ((message = _client.ReadMessage()) != null) + { + ReadBuffer(message); + } + + if (EntitiesToSend.Count > 0) + { + WriteServerShots(); + } + + WritePlayerUpdatePacket(); + } + + private void WriteServerShots() + { + SendEntities(EntitiesToSend); + } + + private void WritePlayerUpdatePacket() + { + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerUpdate); + message.Write(_localPlayer.Owner); + message.Write(_colorIndex); + message.Write(_localPlayer.Position); + message.Write(_localPlayer.Velocity); + message.Write(_localPlayer.Angle); + message.Write(_localPlayer.Health); + + _client.SendMessage(message, NetDeliveryMethod.UnreliableSequenced); + } + + private void ReadBuffer(NetIncomingMessage message) + { + switch (message.MessageType) + { + case NetIncomingMessageType.Data: + { + var packetType = (UdpNetworkPacketType)message.ReadInt32(); + + switch (packetType) + { + case UdpNetworkPacketType.PlayerUpdate: + ReadPlayers(message); + break; + case UdpNetworkPacketType.EntitiesCreated: + ReadEntities(message); + break; + case UdpNetworkPacketType.MessageReceived: + ReadMessage(message); + break; + case UdpNetworkPacketType.PlayerDied: + ReadPlayerDied(message); + break; + case UdpNetworkPacketType.SendingScoreBoard: + ReadScoreBoard(message); + break; + case UdpNetworkPacketType.EntityCreated: + ReadEntity(message); + break; + case UdpNetworkPacketType.PlayerDisconnected: + RemovePlayer(message); + break; + case UdpNetworkPacketType.LevelResponse: + ReadLevel(message); + break; + } + } + break; + } + + _client.Recycle(message); + } + + private void WaitForLevel() + { + System.Diagnostics.Trace.WriteLine("WaitForLevel"); + while (_level == null) // wait for level + { + NetIncomingMessage message; + while ((message = _client.ReadMessage()) != null) + { + if (message.MessageType == NetIncomingMessageType.Data) + { + var packetType = (UdpNetworkPacketType)message.ReadInt32(); + if (packetType == UdpNetworkPacketType.LevelResponse) + { + ReadLevel(message); + } + } + } + } + } + + private static ulong _levelLength; + private static ulong _levelReceived; + private static FileStream _levelWriteStream; + private static int _levelTimeStarted; + string levelPath = string.Empty; + + private void ReadLevel(NetIncomingMessage message) + { + int chunkLen = message.LengthBytes; + if (_levelLength == 0) + { + _levelLength = message.ReadUInt64(); + string filename = message.ReadString(); + + var downloadLevelFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Downloads\\Levels\\"); + levelPath = Path.Combine(downloadLevelFolder, filename); + + _levelWriteStream = new FileStream(levelPath, FileMode.Create, FileAccess.Write, FileShare.None); + _levelTimeStarted = Environment.TickCount; + + return; + } + + byte[] all = message.ReadBytes(message.LengthBytes - 4); // offset for UdpNetworkPacketType + _levelReceived += (ulong)all.Length; + _levelWriteStream.Write(all, 0, all.Length); + + //int v = (int)(((float)_levelReceived / (float)_levelLength) * 100.0f); + //int passed = Environment.TickCount - _levelTimeStarted; + //double psec = (double)passed / 1000.0; + //double bps = (double)_levelReceived / psec; + + //var passedText = NetUtility.ToHumanReadable((long)bps) + " per second"; + + if (_levelReceived >= _levelLength) + { + Trace.WriteLine("Got level"); + _levelWriteStream.Flush(); + _levelWriteStream.Close(); + _levelWriteStream.Dispose(); + + _level = LevelLoader.LoadLevel(levelPath, _game.Content, _game.GraphicsDevice); + _game.Level = _level; + } + } + + private void RemovePlayer(NetIncomingMessage message) + { + string name = message.ReadString(); + + Player player = _players.Find(p => p.Owner == name); + if (player != null) + { + EntityFactory.Instance.RemoveEntity(player); + + NetworkMessenger.DisplayMessage(new NetworkMessage() + { + Color = player.Color, + Message = string.Format("{0} has left.", player.Owner), + Sent = DateTime.Now + }); + + _players.Remove(player); + } + } + + private void ReadScoreBoard(NetIncomingMessage message) + { + int count = message.ReadInt32(); + + var scoreBoard = new List(); + + for (int i = 0; i < count; i++) + { + var item = new ScoreBoardItem + { + Name = message.ReadString(), + Kills = message.ReadInt32(), + Deaths = message.ReadInt32(), + Joined = DateTime.Parse(message.ReadString(), CultureInfo.InvariantCulture), + Ping = message.ReadInt64() + }; + + scoreBoard.Add(item); + } + + ScoreBoard.CurrentScoreBoard = scoreBoard; + } + + private void ReadPlayerDied(NetIncomingMessage message) + { + string playerName = message.ReadString(); + + Player player = _players.Find(p => p.Owner == playerName); + + if (player != null) + { + _game.AddExplosion(player.Position, 1f); + player.Kill(); + } + } + + private void ReadEntities(NetIncomingMessage message) + { + int count = message.ReadInt32(); + for (int i = 0; i < count; i++) + { + string entityType = message.ReadString(); + string owner = message.ReadString(); + Vector2 pos = message.ReadVector2(); + Vector2 velocity = message.ReadVector2(); + float angle = message.ReadFloat(); + + EntityFactory.Instance.CreateEntity(Type.GetType(entityType), owner, pos, velocity, angle); + } + } + + private void ReadEntity(NetIncomingMessage message) + { + string entityType = message.ReadString(); + string owner = message.ReadString(); + Vector2 pos = message.ReadVector2(); + Vector2 velocity = message.ReadVector2(); + float angle = message.ReadFloat(); + + EntityFactory.Instance.CreateEntity(Type.GetType(entityType), owner, pos, velocity, angle); + } + + private void ReadPlayers(NetIncomingMessage message) + { + string owner = message.ReadString(); + + int colorIndex = message.ReadInt32(); + Color color = _playerColors[colorIndex]; + + Vector2 pos = message.ReadVector2(); + Vector2 velocity = message.ReadVector2(); + float angle = message.ReadFloat(); + + if (owner == LocalPlayer.Owner) + return; + + Player player = _players.Find(p => p.Owner == owner); + if (player == null) + { + Player p = Player.CreatePlayer(new Vector2(300, 280), owner); + + p.Position = pos; + p.Color = color; + p.Velocity = velocity; + p.Angle = angle; + p.Owner = owner; + + _players.Add(p); + + NetworkMessenger.DisplayMessage(new NetworkMessage() + { + Color = p.Color, + Message = string.Format("{0} has joined.", p.Owner), + Sent = DateTime.Now + }); + } + else + { + int index = _players.IndexOf(player); + player.Owner = owner; + player.Color = color; + player.Position = pos; + player.Velocity = velocity; + player.Angle = angle; + + _players[index] = player; + } + } + + public void SendEntities(List entities) + { + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.EntitiesCreated); + message.Write(entities.Count); + + lock (EntitiesToSend) + { + for (int i = entities.Count - 1; i >= 0; i--) + { + message.Write(entities[i].GetType().ToString()); + message.Write(_localPlayer.Owner); + message.Write(entities[i].Position); + message.Write(entities[i].Velocity); + message.Write(entities[i].Angle); + } + } + + EntitiesToSend.Clear(); + + _client.SendMessage(message, NetDeliveryMethod.UnreliableSequenced); + } + + public void SendEntity(IEntity entity) + { + var message = _client.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.EntityCreated); + message.Write(entity.GetType().ToString()); + message.Write(_localPlayer.Owner); + message.Write(entity.Position); + message.Write(entity.Velocity); + message.Write(entity.Angle); + + _client.SendMessage(message, NetDeliveryMethod.UnreliableSequenced); + } + + public void GetServerList() + { + var listRequest = _client.CreateMessage(); + listRequest.Write((byte)UdpNetworkPacketType.RequestHostList); + _client.SendUnconnectedMessage(listRequest, _masterServerEndpoint); + } + + public void RequestNATIntroduction(long hostid) + { + if (hostid == 0) + { + return; + } + + if (_masterServerEndpoint == null) + throw new Exception("Must connect to master server first!"); + + IPAddress mask; + var message = _client.CreateMessage(); + message.Write((byte)UdpNetworkPacketType.RequestIntroduction); + + message.Write(new IPEndPoint(NetUtility.GetMyAddress(out mask), _client.Port)); + message.Write(hostid); + message.Write(_playerName); + + _client.SendUnconnectedMessage(message, _masterServerEndpoint); + } + } +} diff --git a/SpacePew/Networking/UdpNetworkGui.cs b/SpacePew/Networking/UdpNetworkGui.cs new file mode 100644 index 0000000..8bf4b3e --- /dev/null +++ b/SpacePew/Networking/UdpNetworkGui.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.GamerServices; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Media; +using Microsoft.Xna.Framework.Net; +using Microsoft.Xna.Framework.Storage; +using System.Threading; +using SpacePew.Models; +#if WINDOWS +using TomShane.Neoforce.Controls; +using Lidgren.Network; +using System.Net; +using System.Diagnostics; +#endif + +namespace SpacePew.Networking +{ + /// + /// This is a game component that implements IUpdateable. + /// +#if WINDOWS + public class UdpNetworkGui : Microsoft.Xna.Framework.DrawableGameComponent + { + private MainGame _game; + + private UdpClient _client; + private UdpServer _server; + + private GraphicsDeviceManager _graphics; + + private Manager _manager; + private Window _window; + private TabControl _tabControl; + private TextBox _nameTextBox; + private TextBox _nameTextBox2; + private TextBox _ipTextBox; + private Label _nameLabel; + private Label _nameLabel2; + private ListBox _localGamesListBox; + + private Label _joinErrorLabel; + + private Label _ipLabel; + private Button _createButton; + private Button _joinButton; + private Button _refreshButton; + + public UdpNetworkGui(MainGame game, GraphicsDeviceManager graphics, UdpClient client, UdpServer server) + : base(game) + { + _game = game; + _graphics = graphics; + _client = client; + _server = server; + + _client.CurrentClient.Start(); + + _manager = new Manager(game, _graphics, "Default"); + _manager.Skin = new Skin(_manager, "Default"); + _manager.AutoCreateRenderTarget = true; + _manager.TargetFrames = 60; + _manager.LogUnhandledExceptions = false; + _manager.ShowSoftwareCursor = true; + } + + /// + /// Allows the game component to perform any initialization it needs to before starting + /// to run. This is where it can query for any required services and load content. + /// + public override void Initialize() + { + base.Initialize(); + + _window = new Window(_manager); + _window.Init(); + _window.Text = "Space, pew pew!"; + _window.Width = 480; + _window.Height = 200; + _window.Center(); + _window.CloseButtonVisible = false; + _window.Resizable = false; + _window.Visible = true; + + _tabControl = new TabControl(_manager); + _tabControl.Width = _window.Width; + _tabControl.Height = _window.Height; + _tabControl.Parent = _window; + + _nameLabel = new Label(_manager); + _nameLabel.Init(); + _nameLabel.Width = 100; + _nameLabel.Height = 24; + _nameLabel.Text = "Name"; + _nameLabel.Left = 10; + _nameLabel.Top = 10; + + _nameTextBox = new TextBox(_manager); + _nameTextBox.Init(); + _nameTextBox.Width = 140; + _nameTextBox.Height = 24; + _nameTextBox.Left = 50; + _nameTextBox.Top = 10; + + _nameLabel2 = new Label(_manager); + _nameLabel2.Init(); + _nameLabel2.Width = 100; + _nameLabel2.Height = 24; + _nameLabel2.Text = "Name"; + _nameLabel2.Left = 10; + _nameLabel2.Top = 10; + + _nameTextBox2 = new TextBox(_manager); + _nameTextBox2.Init(); + _nameTextBox2.Width = 140; + _nameTextBox2.Height = 24; + _nameTextBox2.Left = 50; + _nameTextBox2.Top = 10; + + _createButton = new Button(_manager); + _createButton.Init(); + _createButton.Text = "Create game"; + _createButton.Width = 140; + _createButton.Height = 24; + _createButton.Left = 50; + _createButton.Top = 40; + _createButton.Click += _createButton_Click; + + _ipLabel = new Label(_manager); + _ipLabel.Init(); + _ipLabel.Width = 100; + _ipLabel.Height = 24; + _ipLabel.Text = "Host"; + _ipLabel.Left = 10; + _ipLabel.Top = 40; + + _ipTextBox = new TextBox(_manager); + _ipTextBox.Init(); + _ipTextBox.Width = 140; + _ipTextBox.Height = 24; + _ipTextBox.Left = 50; + _ipTextBox.Top = 40; + + _joinErrorLabel = new Label(_manager); + _joinErrorLabel.Init(); + _joinErrorLabel.Width = 460; + _joinErrorLabel.Height = 24; + _joinErrorLabel.Left = 10; + _joinErrorLabel.Top = 110; + _joinErrorLabel.Text = string.Empty; + _joinErrorLabel.TextColor = Color.DarkRed; + + _joinButton = new Button(_manager); + _joinButton.Init(); + _joinButton.Text = "Join game"; + _joinButton.Width = 140; + _joinButton.Height = 24; + _joinButton.Left = 50; + _joinButton.Top = 70; + _joinButton.Anchor = Anchors.Bottom; + _joinButton.Click += _joinButton_Click; + + _localGamesListBox = new ListBox(_manager); + _localGamesListBox.Init(); + _localGamesListBox.Left = 200; + _localGamesListBox.Top = 10; + _localGamesListBox.Height = 84; + _localGamesListBox.Width = 254; + _localGamesListBox.ItemIndexChanged += new TomShane.Neoforce.Controls.EventHandler(_localGamesListBox_ItemIndexChanged); + + _refreshButton = new Button(_manager); + _refreshButton.Init(); + _refreshButton.Text = "Refresh"; + _refreshButton.Width = 140; + _refreshButton.Height = 24; + _refreshButton.Left = 314; + _refreshButton.Top = 104; + _refreshButton.Click += _refreshButton_Click; + + _nameTextBox.Click += ChangeTextBoxColor; + _nameTextBox2.Click += ChangeTextBoxColor; + _ipTextBox.Click += ChangeTextBoxColor; + + _tabControl.AddPage(); + _tabControl.AddPage(); + _tabControl.TabPages[0].Text = "Create"; + _tabControl.TabPages[0].Add(_nameLabel); + _tabControl.TabPages[0].Add(_nameTextBox); + _tabControl.TabPages[0].Add(_createButton); + + _tabControl.TabPages[1].Text = "Join"; + _tabControl.TabPages[1].Add(_nameLabel2); + _tabControl.TabPages[1].Add(_nameTextBox2); + _tabControl.TabPages[1].Add(_ipLabel); + _tabControl.TabPages[1].Add(_ipTextBox); + _tabControl.TabPages[1].Add(_joinButton); + _tabControl.TabPages[1].Add(_joinErrorLabel); + _tabControl.TabPages[1].Add(_localGamesListBox); + _tabControl.TabPages[1].Add(_refreshButton); + + _manager.Add(_window); + _manager.Initialize(); + + _client.CurrentClient.DiscoverLocalPeers(SpacePew.Common.Constants.GameServerPort); + } + + private void _refreshButton_Click(object sender, TomShane.Neoforce.Controls.EventArgs e) + { + _localGamesListBox.Items.Clear(); + _client.CurrentClient.DiscoverLocalPeers(SpacePew.Common.Constants.GameServerPort); + } + + private void ChangeTextBoxColor(object sender, TomShane.Neoforce.Controls.EventArgs e) + { + ((TomShane.Neoforce.Controls.Control)sender).Color = Color.TransparentBlack; + } + + private void _localGamesListBox_ItemIndexChanged(object sender, TomShane.Neoforce.Controls.EventArgs e) + { + _ipTextBox.Text = _localGamesListBox.Items[_localGamesListBox.ItemIndex].ToString(); + } + + private void _createButton_Click(object sender, TomShane.Neoforce.Controls.EventArgs e) + { + _nameTextBox.Color = string.IsNullOrEmpty(_nameTextBox.Text) ? Color.Pink : Color.TransparentBlack; + + if (_nameTextBox.Color == Color.Pink) + { + return; + } + + string levelPath = AppDomain.CurrentDomain.BaseDirectory + "\\Levels\\hippie.zip"; // TODO: Vlja + var level = LevelLoader.LoadLevel(levelPath, _game.Content, GraphicsDevice); + + _server.SetLevel(level); + + _window.Close(); + Trace.WriteLine("CreateSession()"); + _server.CreateSession(); + + new Thread(_server.Listen).Start(); + + _client.JoinSession("127.0.0.1", _nameTextBox.Text); + + _game.AddGameComponents(); + _game.Components.Remove(this); + } + + private void _joinButton_Click(object sender, TomShane.Neoforce.Controls.EventArgs e) + { + _ipTextBox.Color = string.IsNullOrEmpty(_ipTextBox.Text) ? Color.Pink : Color.TransparentBlack; + _nameTextBox2.Color = string.IsNullOrEmpty(_nameTextBox2.Text) ? Color.Pink : Color.TransparentBlack; + + if (_ipTextBox.Color == Color.Pink || _nameTextBox2.Color == Color.Pink) + { + return; + } + + var splits = _ipTextBox.Text.Split(' '); + if (splits.Count() > 1) + { + var host = Int64.Parse(splits[0]); + _client.RequestNATIntroduction(host); + } + else + { + try + { + _client.JoinSession(_ipTextBox.Text, _nameTextBox2.Text); + + _game.AddGameComponents(); + _game.Components.Remove(this); + } + catch (NetException ex) + { + _joinErrorLabel.Text = ex.Message; + return; + } + } + } + + private DateTime _lastUpdate = DateTime.Now.AddSeconds(-5); + private static Dictionary _hostList = new Dictionary(); + + /// + /// Allows the game component to update itself. + /// + /// Provides a snapshot of timing values. + public override void Update(GameTime gameTime) + { + _manager.Update(gameTime); + + if (_lastUpdate <= DateTime.Now.AddSeconds(-5)) + { + _lastUpdate = DateTime.Now; + _client.CurrentClient.DiscoverLocalPeers(SpacePew.Common.Constants.GameServerPort); + _client.GetServerList(); + } + NetIncomingMessage message; + while ((message = _client.CurrentClient.ReadMessage()) != null) + { + if (message.MessageType == NetIncomingMessageType.UnconnectedData) + { + var id = message.ReadInt64(); + var hostInternal = message.ReadIPEndPoint(); + var hostExternal = message.ReadIPEndPoint(); + + _hostList[id] = new IPEndPoint[] { hostInternal, hostExternal }; + + _localGamesListBox.Items.Clear(); + foreach (var kvp in _hostList) + { + _localGamesListBox.Items.Add(kvp.Key.ToString() + " (" + kvp.Value[1] + ")"); + } + } + else if (message.MessageType == NetIncomingMessageType.DiscoveryResponse) + { + IPEndPoint ep = message.ReadIPEndPoint(); + if (!_localGamesListBox.Items.Contains(ep.Address.ToString())) + { + _localGamesListBox.Items.Add(ep.Address.ToString()); + } + } + else if (message.MessageType == NetIncomingMessageType.NatIntroductionSuccess) + { + try + { + _client.JoinSession(message.SenderEndPoint, _nameTextBox2.Text); + + _game.AddGameComponents(); + _game.Components.Remove(this); + } + catch (NetException ex) + { + _joinErrorLabel.Text = ex.Message; + return; + } + } + } + + base.Update(gameTime); + } + + public override void Draw(GameTime gameTime) + { + _manager.BeginDraw(gameTime); + + GraphicsDevice.Clear(Color.Black); + + _manager.EndDraw(); + + base.Draw(gameTime); + } + } +#endif +} \ No newline at end of file diff --git a/SpacePew/Networking/UdpServer.cs b/SpacePew/Networking/UdpServer.cs new file mode 100644 index 0000000..7bc5beb --- /dev/null +++ b/SpacePew/Networking/UdpServer.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using Microsoft.Xna.Framework; + +using SpacePew.Extensions; + +using Lidgren.Network; +using System.Net; +using Microsoft.Xna.Framework.Graphics; +using System.Net.NetworkInformation; +using SpacePew.Models; +using SpacePew.Common; + +namespace SpacePew.Networking +{ + public class UdpServer : UdpBase + { + private NetPeerConfiguration _configuration; + private NetServer _server; + private Level _level; + + private readonly List _scoreBoard; + private readonly List _players; + + private readonly Vector2 _defaultPosition = new Vector2(300, 280); + + private IPEndPoint _masterServerEndpoint; + + public UdpServer() + : base() + { + _masterServerEndpoint = NetUtility.Resolve("spacepew.wodanaz.se", Constants.MasterServerPort); // TODO: Fixa upp masterserver någonstans + _scoreBoard = new List(); + _players = new List(); + } + + public void CreateSession() + { + if (_server == null) + { + _configuration = new NetPeerConfiguration("SpacePew") + { + MaximumConnections = 16, + Port = SpacePew.Common.Constants.GameServerPort + }; + + _configuration.EnableMessageType(NetIncomingMessageType.DiscoveryRequest); + _configuration.EnableMessageType(NetIncomingMessageType.DiscoveryResponse); + _configuration.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess); + + _server = new NetServer(_configuration); + _server.Start(); + } + } + + public void SetLevel(Level level) + { + _level = level; + } + + public void Shutdown() + { + if (_server != null && _server.Status == NetPeerStatus.Running) + { + _server.Shutdown("Quitting"); + } + } + + public void Listen() + { + + System.Diagnostics.Trace.WriteLine("Server.Listen()"); + while (_server.Status == NetPeerStatus.Running) + { + NetIncomingMessage message; + while ((message = _server.ReadMessage()) != null) + { + ReadBuffer(message, message.SenderConnection); + } + + if (NetTime.Now > lastRegistered + 60) + { + RegisterWithMasterServer(); + } + + StreamLevelToClients(); + } + } + + private void StreamLevelToClients() + { + foreach (var conn in _server.Connections) + { + var client = conn.Tag as StreamingClient; + if (client != null) + client.Heartbeat(); + } + } + + float lastRegistered = -60.0f; + private void RegisterWithMasterServer() + { + IPAddress mask; + IPAddress localAddress = NetUtility.GetMyAddress(out mask); + var message = _server.CreateMessage(); + message.Write((byte)UdpNetworkPacketType.RegisterHost); + message.Write(_server.UniqueIdentifier); + message.Write(new IPEndPoint(localAddress, Constants.GameServerPort)); + + _server.SendUnconnectedMessage(message, _masterServerEndpoint); + + lastRegistered = (float)NetTime.Now; + } + + private void ReadBuffer(NetIncomingMessage message, NetConnection sender) + { + switch (message.MessageType) + { + case NetIncomingMessageType.Data: + { + var packetType = (UdpNetworkPacketType)message.ReadInt32(); + switch (packetType) + { + case UdpNetworkPacketType.PlayerUpdate: + WritePlayerUpdate(message, sender); + break; + case UdpNetworkPacketType.EntitiesCreated: + WriteEntities(message, sender); + break; + case UdpNetworkPacketType.MessageSent: + WriteMessage(message, sender); + break; + case UdpNetworkPacketType.EntityCreated: + WriteEntity(message, sender); + break; + case UdpNetworkPacketType.PlayerDying: + WritePlayerDied(message, sender); + break; + case UdpNetworkPacketType.RequestingScoreboard: + WriteScoreBoard(sender); + break; + case UdpNetworkPacketType.PlayerJoining: + { + string name = message.ReadString(); + _players.Add(name); + sender.Tag = name; + _scoreBoard.Add(new ScoreBoardItem() { Name = name, Joined = DateTime.Now }); + + WritePlayerJoinedPacket(sender, name); + break; + } + case UdpNetworkPacketType.PlayerDisconnecting: + { + string name = message.ReadString(); + + WritePlayerDisconnectedPacket(sender, name); + break; + } + case UdpNetworkPacketType.LevelRequest: + { + WriteLevelResponse(message); + break; + } + } + } + break; + case NetIncomingMessageType.ConnectionApproval: + AuthorizeConnection(sender); + break; + case NetIncomingMessageType.DiscoveryRequest: + WriteDiscoveryResponse(sender); + break; + case NetIncomingMessageType.StatusChanged: + { + var status = (NetConnectionStatus)message.ReadByte(); + var msg = message.ReadString(); + break; + } + } + + _server.Recycle(message); + } + + private void WriteLevelResponse(NetIncomingMessage message) + { + message.SenderConnection.Tag = new StreamingClient(message.SenderConnection, _level.FilePath); + } + + private void WriteDiscoveryResponse(NetConnection sender) + { + if (sender == null) + return; + + var message = _server.CreateMessage(); + + // TODO: Skicka med lite stats + _server.SendDiscoveryResponse(message, sender.RemoteEndPoint); + } + + private void WritePlayerDisconnectedPacket(NetConnection sender, string name) + { + var message = _server.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerDisconnected); + message.Write(name); + + _server.SendToAll(message, sender, NetDeliveryMethod.ReliableUnordered, 0); + + _players.Remove(name); + } + + private void WriteScoreBoard(NetConnection sender) + { + var message = _server.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.SendingScoreBoard); + + int count = _scoreBoard.Count; + + message.Write(count); + + var ping = new Ping(); + + foreach (var item in _scoreBoard) + { + message.Write(item.Name); + message.Write(item.Kills); + message.Write(item.Deaths); + message.Write(item.Joined.ToString(CultureInfo.InvariantCulture)); + + //TODO: Fixa riktig ping + //NetConnection connection = _server.Connections.Find(s => s.Tag.ToString() == item.Name); + + //long pingTime = ping.Send(connection.RemoteEndpoint.Address).RoundtripTime; + const long pinglong = 14; + message.Write(pinglong); + } + + _server.SendMessage(message, sender, NetDeliveryMethod.UnreliableSequenced); + } + + private void WriteMessage(NetIncomingMessage incomingMessage, NetConnection sender) + { + Vector3 colorVector = incomingMessage.ReadVector3(); + string dateString = incomingMessage.ReadString(); + string message = incomingMessage.ReadString(); + bool isChatMessage = incomingMessage.ReadBoolean(); + + var outgoingMessage = _server.CreateMessage(); + + outgoingMessage.Write((int)UdpNetworkPacketType.MessageReceived); + outgoingMessage.Write(colorVector); + outgoingMessage.Write(dateString); + outgoingMessage.Write(message); + outgoingMessage.Write(isChatMessage); + + _server.SendToAll(outgoingMessage, sender, NetDeliveryMethod.UnreliableSequenced, 1); + } + + private void WritePlayerDied(NetIncomingMessage incomingMessage, NetConnection sender) + { + string playerName = incomingMessage.ReadString(); + string killedBy = incomingMessage.ReadString(); + + if (playerName != killedBy) + { + AddKillScore(killedBy); + } + + AddDeathScore(playerName); + + var message = _server.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerDied); + message.Write(playerName); + + _server.SendToAll(message, sender, NetDeliveryMethod.ReliableUnordered, 0); + } + + private void AddKillScore(string name) + { + ScoreBoardItem item = _scoreBoard.Find(i => i.Name == name); + item.Kills++; + _scoreBoard[_scoreBoard.IndexOf(item)] = item; + } + + private void AddDeathScore(string name) + { + ScoreBoardItem item = _scoreBoard.Find(i => i.Name == name); + item.Deaths++; + _scoreBoard[_scoreBoard.IndexOf(item)] = item; + } + + private void WriteEntities(NetIncomingMessage incomingMessage, NetConnection sender) + { + int count = incomingMessage.ReadInt32(); + + var message = _server.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.EntitiesCreated); + message.Write(count); + + for (int i = 0; i < count; i++) + { + message.Write(incomingMessage.ReadString()); + message.Write(incomingMessage.ReadString()); + message.Write(incomingMessage.ReadVector2()); + message.Write(incomingMessage.ReadVector2()); + message.Write(incomingMessage.ReadFloat()); + } + + _server.SendToAll(message, sender, NetDeliveryMethod.UnreliableSequenced, 1); + } + + private void AuthorizeConnection(NetConnection sender) + { + string name = Encoding.ASCII.GetString(sender.RemoteHailMessage.Data); + if (!string.IsNullOrEmpty(name) && !_players.Contains(name)) + { + sender.Approve(); + } + else + { + sender.Deny("A player with that name is already playing."); + } + } + + private void WritePlayerJoinedPacket(NetConnection sender, string name) + { + System.Diagnostics.Trace.WriteLine("In WritePlayerJoined()..."); + + var message = _server.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerJoined); + + message.Write(name); + message.Write(_server.Connections.IndexOf(sender)); + message.Write(_defaultPosition); // TODO: positions should be stored on the level somehow + message.Write(Vector2.Zero); + message.Write(0f); + + _level.BuildCollisionDataFromLevel(); + + byte[] collisionData = ObjectToByteArray(_level.CollisionData); + message.Write(collisionData.Length); + message.Write(collisionData); + + _server.SendMessage(message, sender, NetDeliveryMethod.ReliableUnordered); + System.Diagnostics.Trace.WriteLine("Sent joined..."); + } + + private void WriteEntity(NetIncomingMessage message, NetConnection sender) + { + string entityType = message.ReadString(); + string owner = message.ReadString(); + Vector2 pos = message.ReadVector2(); + Vector2 velocity = message.ReadVector2(); + float angle = message.ReadFloat(); + + WriteEntityPacket(sender, entityType, owner, ref pos, ref velocity, angle); + } + + private void WriteEntityPacket(NetConnection connectionToExclude, string entityType, string owner, ref Vector2 pos, ref Vector2 velocity, float angle) + { + var message = _server.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.EntityCreated); + message.Write(entityType); + message.Write(owner); + message.Write(pos); + message.Write(velocity); + message.Write(angle); + + _server.SendToAll(message, connectionToExclude, NetDeliveryMethod.UnreliableSequenced, 1); + } + + private void WritePlayerUpdate(NetIncomingMessage message, NetConnection sender) + { + string owner = message.ReadString(); + int colorIndex = message.ReadInt32(); + Vector2 pos = message.ReadVector2(); + Vector2 velocity = message.ReadVector2(); + float angle = message.ReadFloat(); + + WritePlayerUpdatePacket(sender, owner, colorIndex, ref pos, ref velocity, angle); + } + + private void WritePlayerUpdatePacket(NetConnection connectionToExclude, string owner, int colorIndex, ref Vector2 pos, ref Vector2 velocity, float angle) + { + var message = _server.CreateMessage(); + + message.Write((int)UdpNetworkPacketType.PlayerUpdate); + message.Write(owner); + message.Write(colorIndex); + message.Write(pos); + message.Write(velocity); + message.Write(angle); + + _server.SendToAll(message, connectionToExclude, NetDeliveryMethod.UnreliableSequenced, 1); + } + } +} \ No newline at end of file diff --git a/SpacePew/ParticleSystem/ExplosionParticleSystem.cs b/SpacePew/ParticleSystem/ExplosionParticleSystem.cs new file mode 100644 index 0000000..fd7b2ed --- /dev/null +++ b/SpacePew/ParticleSystem/ExplosionParticleSystem.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using SpacePew.Camera; + +namespace SpacePew.ParticleSystem +{ + public class ExplosionParticleSystem : ParticleSystem + { + public ExplosionParticleSystem(MainGame game, int howManyEffects, ICamera2D camera) : base(game, howManyEffects, camera) { } + + protected override void InitializeConstants() + { + _textureFilename = "ParticleTextures\\explosion"; + + _minInitialSpeed = 40; + _maxInitialSpeed = 500; + + _minAcceleration = 0; + _maxAcceleration = 0; + + _minLifetime = .5f; + _maxLifetime = 1.0f; + + _minScale = .3f; + _maxScale = 1.0f; + + _minNumParticles = 20; + _maxNumParticles = 25; + + _minRotationSpeed = -MathHelper.PiOver4; + _maxRotationSpeed = MathHelper.PiOver4; + + _blendState = BlendState.Additive; + + DrawOrder = AdditiveDrawOrder; + } + + protected override void InitializeParticle(Particle p, Vector2 where) + { + base.InitializeParticle(p, where); + + p.Acceleration = -p.Velocity / p.Lifetime; + } + } +} \ No newline at end of file diff --git a/SpacePew/ParticleSystem/ExplosionSmokeParticleSystem.cs b/SpacePew/ParticleSystem/ExplosionSmokeParticleSystem.cs new file mode 100644 index 0000000..2a4d99e --- /dev/null +++ b/SpacePew/ParticleSystem/ExplosionSmokeParticleSystem.cs @@ -0,0 +1,41 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using SpacePew.Camera; + +namespace SpacePew.ParticleSystem +{ + public class ExplosionSmokeParticleSystem : ParticleSystem + { + public ExplosionSmokeParticleSystem(MainGame game, int howManyEffects, ICamera2D camera) + : base(game, howManyEffects, camera) + { + } + + protected override void InitializeConstants() + { + _textureFilename = "ParticleTextures\\smoke"; + + _minInitialSpeed = 20; + _maxInitialSpeed = 200; + + _minAcceleration = -10; + _maxAcceleration = -50; + + _minLifetime = 1.0f; + _maxLifetime = 2.5f; + + _minScale = 1.0f; + _maxScale = 2.0f; + + _minNumParticles = 10; + _maxNumParticles = 20; + + _minRotationSpeed = -MathHelper.PiOver4; + _maxRotationSpeed = MathHelper.PiOver4; + + _blendState = BlendState.AlphaBlend; + + DrawOrder = AlphaBlendDrawOrder; + } + } +} diff --git a/SpacePew/ParticleSystem/Particle.cs b/SpacePew/ParticleSystem/Particle.cs new file mode 100644 index 0000000..e4f0fe4 --- /dev/null +++ b/SpacePew/ParticleSystem/Particle.cs @@ -0,0 +1,55 @@ +using System; + +using Microsoft.Xna.Framework; + +namespace SpacePew.ParticleSystem +{ + public class Particle + { + private static readonly Random _random = new Random(); + private float RandomBetween(float min, float max) + { + return min + (float)_random.NextDouble() * (max - min); + } + + public Vector2 Position; + public Vector2 Velocity; + public Vector2 Acceleration; + + public float Scale { get; set; } + + public float Lifetime { get; private set; } + public float TimeSinceStart { get; private set; } + public float Rotation { get; private set; } + public float RotationSpeed { get; private set; } + + public bool Active + { + get { return TimeSinceStart < Lifetime; } + } + + public void Initialize(Vector2 position, Vector2 velocity, Vector2 acceleration, float lifetime, float scale, float rotationSpeed) + { + this.Position = position; + this.Velocity = velocity;// *this.Scale; + this.Acceleration = acceleration; + this.Lifetime = lifetime * this.Scale; + this.Scale = scale * this.Scale; + this.RotationSpeed = rotationSpeed; + + this.TimeSinceStart = 0.0f; + + this.Rotation = RandomBetween(0, MathHelper.TwoPi); + } + + public void Update(float dt) + { + Velocity += Acceleration * dt; + Position += Velocity * dt; + + Rotation += RotationSpeed * dt; + + TimeSinceStart += dt; + } + } +} \ No newline at end of file diff --git a/SpacePew/ParticleSystem/ParticleSystem.cs b/SpacePew/ParticleSystem/ParticleSystem.cs new file mode 100644 index 0000000..15839bc --- /dev/null +++ b/SpacePew/ParticleSystem/ParticleSystem.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; +using SpacePew.Camera; + +namespace SpacePew.ParticleSystem +{ + public abstract class ParticleSystem : DrawableGameComponent + { + private static readonly Random _random = new Random(); + protected Random Random + { + get { return _random; } + } + + protected float RandomBetween(float min, float max) + { + return min + (float)_random.NextDouble() * (max - min); + } + + public const int AlphaBlendDrawOrder = 100; + public const int AdditiveDrawOrder = 200; + + private readonly MainGame _game; + private readonly int _howManyEffects; + + private Texture2D _texture; + private Vector2 _origin; + + private Particle[] _particles; + + private Queue _freeParticles; + + public int FreeParticleCount + { + get { return _freeParticles.Count; } + } + + protected int _minNumParticles; + protected int _maxNumParticles; + + protected string _textureFilename; + + protected float _minInitialSpeed; + protected float _maxInitialSpeed; + + protected float _minAcceleration; + protected float _maxAcceleration; + + protected float _minRotationSpeed; + protected float _maxRotationSpeed; + + protected float _minLifetime; + protected float _maxLifetime; + + protected float _minScale; + protected float _maxScale; + + protected BlendState _blendState; + + protected ICamera2D _camera; + + protected ParticleSystem(MainGame game, int howManyEffects, ICamera2D camera) + : base(game) + { + this._game = game; + this._howManyEffects = howManyEffects; + this._camera = camera; + } + + public override void Initialize() + { + InitializeConstants(); + + _particles = new Particle[_howManyEffects * _maxNumParticles]; + _freeParticles = new Queue(_howManyEffects * _maxNumParticles); + + for (int i = 0; i < _particles.Length; i++) + { + _particles[i] = new Particle(); + _freeParticles.Enqueue(_particles[i]); + } + + base.Initialize(); + } + + protected abstract void InitializeConstants(); + + protected override void LoadContent() + { + if (string.IsNullOrEmpty(_textureFilename)) + { + throw new ArgumentNullException("textureFilename"); + } + + _texture = _game.Content.Load(_textureFilename); + + _origin.X = _texture.Width / 2; + _origin.Y = _texture.Height / 2; + + base.LoadContent(); + } + + public void AddParticles(Vector2 where, float scale) + { + int numParticles = Random.Next(_minNumParticles, _maxNumParticles); + + for (int i = 0; i < numParticles && _freeParticles.Count > 0; i++) + { + Particle p = _freeParticles.Dequeue(); + p.Scale = scale; + InitializeParticle(p, where); + } + } + + protected virtual void InitializeParticle(Particle p, Vector2 where) + { + Vector2 direction = PickRandomDirection(); + + float velocity = + RandomBetween(_minInitialSpeed, _maxInitialSpeed); + float acceleration = + RandomBetween(_minAcceleration, _maxAcceleration); + float lifetime = + RandomBetween(_minLifetime, _maxLifetime); + float scale = + RandomBetween(_minScale, _maxScale); + float rotationSpeed = + RandomBetween(_minRotationSpeed, _maxRotationSpeed); + + p.Initialize( + where, velocity * direction, acceleration * direction, + lifetime, scale, rotationSpeed); + } + + protected virtual Vector2 PickRandomDirection() + { + float angle = RandomBetween(0, MathHelper.TwoPi); + return new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); + } + + public override void Update(GameTime gameTime) + { + var dt = (float)gameTime.ElapsedGameTime.TotalSeconds; + + foreach (var p in _particles.Where(p => p.Active)) + { + p.Update(dt); + + if (!p.Active) + { + _freeParticles.Enqueue(p); + } + } + + base.Update(gameTime); + } + + public override void Draw(GameTime gameTime) + { + _game.SpriteBatch.Begin(SpriteSortMode.Deferred, _blendState, null, null, null, null, _camera.Transform); + + foreach (Particle p in _particles) + { + if (!p.Active) + continue; + + float normalizedLifetime = p.TimeSinceStart / p.Lifetime; + + float alpha = 4 * normalizedLifetime * (1 - normalizedLifetime); + var color = new Color(new Vector4(1, 1, 1, alpha)); + + float scale = p.Scale * (.75f + .25f * normalizedLifetime); + + _game.SpriteBatch.Draw(_texture, + p.Position, + null, + color, + p.Rotation, + _origin, + scale, + SpriteEffects.None, + 0.0f); + } + + _game.SpriteBatch.End(); + + base.Draw(gameTime); + } + } +} diff --git a/SpacePew/ParticleSystem/SmokePlumeParticleSystem.cs b/SpacePew/ParticleSystem/SmokePlumeParticleSystem.cs new file mode 100644 index 0000000..98d4a35 --- /dev/null +++ b/SpacePew/ParticleSystem/SmokePlumeParticleSystem.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using SpacePew.Camera; + +namespace SpacePew.ParticleSystem +{ + public class SmokePlumeParticleSystem : ParticleSystem + { + public SmokePlumeParticleSystem(MainGame game, int howManyEffects, ICamera2D camera) + : base(game, howManyEffects, camera) + { + } + + protected override void InitializeConstants() + { + _textureFilename = "ParticleTextures\\smoke"; + + _minInitialSpeed = 20; + _maxInitialSpeed = 100; + + _minAcceleration = 0; + _maxAcceleration = 0; + + _minLifetime = 5.0f; + _maxLifetime = 7.0f; + + _minScale = .5f; + _maxScale = 1.0f; + + _minNumParticles = 7; + _maxNumParticles = 15; + + _minRotationSpeed = -MathHelper.PiOver4 / 2.0f; + _maxRotationSpeed = MathHelper.PiOver4 / 2.0f; + + _blendState = BlendState.AlphaBlend; + + DrawOrder = AlphaBlendDrawOrder; + } + + protected override Vector2 PickRandomDirection() + { + float radians = RandomBetween( + MathHelper.ToRadians(80), MathHelper.ToRadians(100)); + + Vector2 direction = Vector2.Zero; + + direction.X = (float)Math.Cos(radians); + direction.Y = -(float)Math.Sin(radians); + return direction; + } + + protected override void InitializeParticle(Particle p, Vector2 where) + { + base.InitializeParticle(p, where); + + p.Acceleration.X += RandomBetween(10, 50); + } + } +} diff --git a/SpacePew/Program.cs b/SpacePew/Program.cs new file mode 100644 index 0000000..5027f4f --- /dev/null +++ b/SpacePew/Program.cs @@ -0,0 +1,26 @@ +#region Using Statements +using System; +using System.Collections.Generic; +using System.Linq; +#endregion + +namespace SpacePew +{ +#if WINDOWS || LINUX + /// + /// The main class. + /// + public static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + using (var game = new MainGame()) + game.Run(); + } + } +#endif +} diff --git a/SpacePew/Properties/AssemblyInfo.cs b/SpacePew/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..83abbf7 --- /dev/null +++ b/SpacePew/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SpacePew")] +[assembly: AssemblyProduct("SpacePew")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCompany("")] + +[assembly: AssemblyCopyright("Copyright © 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1588cda1-b2dd-4e65-bd68-1dcdcc35901d")] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] diff --git a/SpacePew/SoundManager.cs b/SpacePew/SoundManager.cs new file mode 100644 index 0000000..982b3bd --- /dev/null +++ b/SpacePew/SoundManager.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework; + +namespace SpacePew +{ + public static class SoundManager + { + private static MainGame _game; + + public static void Initialize(MainGame game) + { + _game = game; + } + + public static SoundEffectInstance GetSoundEffectInstance(string assetName) + { + var soundEffect = _game.Content.Load(assetName); + var soundEffectInstance = soundEffect.CreateInstance(); + + return soundEffectInstance; + } + + private static int _playCalled = 0; + public static void Play(string assetName, Vector2 position) + { + Debug.Print("Play called: " + ++_playCalled); + + var soundEffect = _game.Content.Load(assetName); + var soundEffectInstance = soundEffect.CreateInstance(); + var emitter = new AudioEmitter(); + var listener = new AudioListener(); + emitter.Position = new Vector3(position, 0); + listener.Position = new Vector3(_game.NetworkClient.LocalPlayer.Position, 0); + + soundEffectInstance.Apply3D(listener, emitter); + soundEffectInstance.Play(); + } + } +} diff --git a/SpacePew/SpacePew.csproj b/SpacePew/SpacePew.csproj new file mode 100644 index 0000000..6b869d2 --- /dev/null +++ b/SpacePew/SpacePew.csproj @@ -0,0 +1,236 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {1C82F1B3-6F3C-47EC-901A-E656D037F862} + WinExe + Properties + SpacePew + SpacePew + 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + v4.5.1 + + + + + x86 + true + full + false + bin\Windows\Debug\ + DEBUG;TRACE;WINDOWS + prompt + 4 + false + + + x86 + pdbonly + true + bin\Windows\Release\ + TRACE;WINDOWS + prompt + 4 + false + + + Icon.ico + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + ..\Library\ICSharpCode.SharpZipLib.dll + + + False + C:\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\MonoGame.Framework.dll + + + ..\Library\NAudio.dll + + + ..\Library\NVorbis.dll + + + ..\Library\NVorbis.NAudioSupport.dll + + + False + C:\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\SharpDX.dll + + + False + C:\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\SharpDX.Direct3D11.dll + + + False + C:\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\SharpDX.MediaFoundation.dll + + + False + C:\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\SharpDX.XAudio2.dll + + + + + + + + + SDL.dll + Always + + + PreserveNewest + + + + + + + + {49ba1c69-6104-41ac-a5d8-b54fa9f696e8} + Lidgren.Network + + + {ac5f1cd8-aa8e-4db5-814f-86c214175841} + TomShane.Neoforce.Controls + + + {ba98d4ca-718b-4e50-ad4d-f48e8ca67624} + SpacePew.Common + + + {944baed2-53a4-47e9-ae89-7f6c5843de94} + SpacePew.Content + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 4.5 + true + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + \ No newline at end of file diff --git a/SpacePew/TextureManager.cs b/SpacePew/TextureManager.cs new file mode 100644 index 0000000..3239954 --- /dev/null +++ b/SpacePew/TextureManager.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +using Microsoft.Xna.Framework.Graphics; + +namespace SpacePew +{ + public static class TextureManager + { + private static MainGame _game; + + static readonly Dictionary Textures = new Dictionary(); + + public static void Initialize(MainGame game) + { + _game = game; + } + + public static Texture2D LoadTexture(string assetName) + { + if (!Textures.ContainsKey(assetName)) + { + var texture = _game.Content.Load(assetName); + Textures.Add(assetName, texture); + } + + return Textures[assetName]; + } + } +} diff --git a/SpacePew/app.config b/SpacePew/app.config new file mode 100644 index 0000000..884f984 --- /dev/null +++ b/SpacePew/app.config @@ -0,0 +1,3 @@ + + +