Changed 'MSG_TYPE_INTERNAL' back to normal.

Strict

Public

' Imports (Public):
Import client
Import packet

' Imports (Private):
Private

Import socket

Public

' Aliases:
Alias MessageType = Int ' Short
Alias SockType = Int

' Interfaces:
Interface NetworkListener
	' Methods:
	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)

	' The 'Message' object will be automatically released.
	' The 'MessageSize' argument specifies how many bytes are in the data-segment of 'Message'.
	Method OnReceiveMessage:Void(Network:NetworkEngine, C:Client, Type:MessageType, Message:Packet, MessageSize:Int)

	' This is called when a client attempts to connect.
	' The return-value of this command dictates if the client at 'Address' should be accepted.
	Method OnClientConnect:Bool(Network:NetworkEngine, Address:SocketAddress)

	' This is called once, at any time after 'OnClientConnect'.
	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)

	' The 'P' object represents the "real" 'Packet' that was sent. (Unlike 'OnReceiveMessage')
	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:SocketAddress, BytesSent:Int)
End

' Classes:
Class NetworkEngine Implements IOnBindComplete, IOnAcceptComplete, IOnConnectComplete, IOnSendComplete, IOnSendToComplete, IOnReceiveFromComplete, IOnReceiveComplete ' Final
	' Constant variable(s):
	Const PORT_AUTOMATIC:= 0

	' Socket types:
	Const SOCKET_TYPE_UDP:= 0
	Const SOCKET_TYPE_TCP:= 1

	' Message types:
	Const MSG_TYPE_ERROR:= -1
	Const MSG_TYPE_INTERNAL:= 1

	' You may use this as a starting-point for message types.
	Const MSG_TYPE_CUSTOM:= 1

	' Internal message types:
	Const INTERNAL_MSG_CONNECT:= 0
	Const INTERNAL_MSG_WARNING:= 1
	Const INTERNAL_MSG_DISCONNECT:= 2

	' Defaults:
	Const Default_PacketSize:= 4096
	Const Default_PacketPoolSize:= 4

	' Booleans / Flags:
	Const Default_FixByteOrder:Bool = True
	Const Default_MultiConnection:Bool = True

	' Functions:
	Function AddressesEqual:Bool(X:SocketAddress, Y:SocketAddress)
		If (X = Y) Then
			Return True
		Endif

		Return (X.Port = Y.Port And X.Host = Y.Host)
	End

	' Constructor(s) (Public):
	Method New(PacketSize:Int=Default_PacketSize, PacketPoolSize:Int=Default_PacketPoolSize, FixByteOrder:Bool=Default_FixByteOrder)
		PacketPool = New PacketPool(PacketSize, PacketPoolSize, FixByteOrder)
		WaitingPackets = New Stack<Packet>()
	End

	' Constructor(s) (Protected):
	Protected

	Method GenerateHostSocket:Void(ProtocolString:String="stream")
		If (Open) Then
			Close()
		Endif

		Connection = New Socket(ProtocolString)

		Return
	End

	Method Init:Void(Protocol:SockType, IsClient:Bool)
		Self.SocketType = Protocol
		Self.IsClient = IsClient

		Select Protocol
			Case SOCKET_TYPE_UDP
				GenerateHostSocket("datagram")
			Case SOCKET_TYPE_TCP
				If (IsClient) Then
					GenerateHostSocket("stream")
				Else
					GenerateHostSocket("server")
				Endif
		End Select

		If (Clients = Null) Then
			Clients = New List<Client>()
		Endif

		Return
	End

	Public

	' Destructor(s):
	Method Close:Void()
		If (Connection <> Null) Then
			Connection.Close()

			Connection = Null
		Endif

		If (Clients <> Null) Then
			For Local C:= Eachin Clients
				C.Close()
			Next

			Clients.Clear()
		Endif

		Remote = Null

		MultiConnection = Default_MultiConnection

		Return
	End

	' Methods:
	Method SetCallback:Void(Callback:NetworkListener)
		Self.Callback = Callback

		Return
	End

	Method Host:Bool(Port:Int, Async:Bool=False, Protocol:SockType=SOCKET_TYPE_UDP, MultiConnection:Bool=Default_MultiConnection, Hostname:String="")
		Init(Protocol, False)

		Self.MultiConnection = MultiConnection

		If (Not Bind(Port, Async, Hostname)) Then
			Close()

			Return False
		Endif

		' Return the default response.
		Return True
	End

	Method Connect:Bool(Address:SocketAddress, Async:Bool=False, Protocol:SockType=SOCKET_TYPE_UDP)
		Init(Protocol, True)

		Remote = New Client(Address, Connection)

		Clients.AddFirst(Remote)

		#Rem
			If (Not Bind(LocalPort, Async, LocalHostName)) Then
				Close()

				Return False
			Endif
		#End

		If (Not RawConnect(Address.Host, Address.Port, Async)) Then
			Close()

			Return False
		Endif

		' Return the default response.
		Return True
	End

	Method Connect:Bool(Host:String, Port:Int, Async:Bool=False, Protocol:SockType=SOCKET_TYPE_UDP)
		Return Connect(New SocketAddress(Host, Port), Async, Protocol)
	End

	Method Update:Void(AsyncEvents:Bool=False)
		If (AsyncEvents) Then
			UpdateAsyncEvents()
		Endif

		If (Not Open) Then
			Return
		Endif

		Return
	End

	' I/O related:

	#Rem
		When no address is specified, 'Send' will output to
		the host for clients, and everyone else for hosts.

		With an address, clients may formally send to hosts,
		and hosts may send to exact addresses. Clients sending
		to other end-points is currently undefined.
	#End

	Method Send:Void(P:Packet, Type:MessageType)
		Local RawPacket:= BuildOutputMessage(P, Type)

		If (Not IsClient And TCPSocket) Then
			RawSendToAll(RawPacket)

			#Rem
				For Local C:= Eachin Clients
					RawSend(C.Connection, BuildOutputMessage(P, Type))
				Next
			#End
		Else
			'Local RawPacket:= BuildOutputMessage(P, Type)

			RawSend(Connection, RawPacket)
		Endif

		Return
	End

	Method Send:Void(P:Packet, C:Client, Type:MessageType)
		Local MSG:= BuildOutputMessage(P, Type)

		Select SocketType
			Case SOCKET_TYPE_UDP
				RawSend(Connection, MSG, C.Address)
			Case SOCKET_TYPE_TCP
				RawSend(C.Connection, MSG)
		End Select

		Return
	End

	#Rem
		These commands may be used to send raw data.

		This can be useful, as you can generate an output packet yourself,
		then send it as you see fit. Use these commands with caution.
	#End

	Method RawSend:Void(Connection:Socket, RawPacket:Packet)
		If (IsClient Or TCPSocket) Then
			' Obtain a transit-reference.
			RawPacket.Obtain()

			Connection.SendAsync(RawPacket.Data, RawPacket.Offset, RawPacket.Length, Self)
		Else
			RawSendToAll(RawPacket)
		Endif

		Return
	End

	' This is only useful for hosts; clients will send normally.
	Method RawSendToAll:Void(RawPacket:Packet)
		If (UDPSocket) Then
			For Local C:= Eachin Clients
				RawSend(Connection, RawPacket, C.Address)
			Next
		Else
			For Local C:= Eachin Clients
				RawSend(C.Connection, RawPacket)
			Next
		Endif

		Return
	End

	' This may only be called by UDP sockets.
	Method RawSend:Void(Connection:Socket, RawPacket:Packet, Address:SocketAddress)
		If (IsClient And (Address = Null Or AddressesEqual(Address, Remote.Address))) Then
			RawSend(Connection, RawPacket)
		Else ' If (Not IsClient) Then
			' Obtain a transit-reference.
			RawPacket.Obtain()

			Connection.SendToAsync(RawPacket.Data, RawPacket.Offset, RawPacket.Length, Address, Self)
		Endif

		Return
	End

	' Used internally; use at your own risk.
	' This command produces a packet in the appropriate format.
	' This will generate an "intermediate" packet, which is handled by an internal system.
	' For details on the 'DefaultSize' argument, please see 'WriteMessage'.
	Method BuildOutputMessage:Packet(P:Packet, Type:MessageType, DefaultSize:Int=0)
		Local Output:= AllocateIntermediatePacket()

		WriteMessage(Output, Type, P, DefaultSize)

		Return Output
	End

	Method IsCallback:Bool(L:NetworkListener)
		Return (Callback = L)
	End

	Method AllocatePacket:Packet()
		Return PacketPool.Allocate()
	End

	Method ReleasePacket:Bool(P:Packet)
		Return PacketPool.Release(P)
	End

	Method GetClient:Client(Address:SocketAddress)
		For Local C:= Eachin Clients
			If (AddressesEqual(Address, C.Address)) Then
				Return C
			Endif
		Next

		Return Null
	End

	Method Connected:Bool(Address:SocketAddress)
		Return (GetClient(Address) <> Null)
	End

	' Methods (Protected):
	Protected

	' This will bind the socket specified, using this network.
	' If 'Async' is disabled, this will return whether the bind operation was successful.
	' If enabled, this will only return 'False' when an internal error occurs.
	Method Bind:Bool(Connection:Socket, Port:Int, Async:Bool=False, Hostname:String="")
		' Check for errors:
		If (Connection.IsBound) Then
			Return False
		Endif

		If (Not Async) Then
			Local Response:= Connection.Bind(Hostname, Port)

			OnBindComplete(Response, Connection)

			Return Response
		Else
			Connection.BindAsync(Hostname, Port, Self)
		Endif

		' Return the default response.
		Return True
	End

	' This will use the internal socket to perform a 'Bind' operation.
	Method Bind:Bool(Port:Int, Async:Bool=False, Hostname:String="")
		Return Bind(Self.Connection, Port, Async, Hostname)
	End

	Method RawConnect:Bool(Host:String, Port:Int, Async:Bool=False)
		If (Async) Then
			Connection.ConnectAsync(Host, Port, Self)
		Else
			Connection.Connect(Host, Port)
		Endif

		' Return the default response.
		Return True
	End

	' Call-backs:
	Method OnBindComplete:Void(Bound:Bool, Source:Socket)
		If (HasCallback) Then
			Callback.OnNetworkBind(Self, Bound)
		Endif

		If (Bound) Then
			If (Not IsClient) Then
				If (TCPSocket) Then
					Connection.AcceptAsync(Self)
				Else
					AutoLaunchReceive(Source)
				Endif
			Else
				AutoLaunchReceive(Source)
			Endif
		Endif

		Return
	End

	Method OnConnectComplete:Void(Connected:Bool, Source:Socket)
		OnBindComplete(Connected, Source)

		If (Connected) Then
			SendConnectMessage()
		Endif

		Return
	End

	Method OnAcceptComplete:Void(Socket:Socket, Source:Socket)
		AutoLaunchReceive(Socket)

		' Check if we can accept more connections:
		If (MultiConnection) Then
			Source.AcceptAsync(Self) ' Connection
		Endif

		Return
	End

	Method OnReceiveFromComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Address:SocketAddress, Source:Socket)
		#Rem
			If (Source <> Connection) Then
				Return
			Endif
		#End

		Local P:= RetrieveWaitingPacketHandle(Data)

		If (P <> Null) Then
			If (HasCallback) Then
				' Manually disable 'Socket' usage when using UDP:
				If (UDPSocket) Then
					ReadMessage(P, Address)
				Else
					ReadMessage(P, Address, Source)
				Endif
			Endif

			AutoLaunchReceive(Source, P)
		Endif

		Return
	End

	Method OnReceiveComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Source:Socket)
		If (UDPSocket) Then
			OnReceiveFromComplete(Data, Offset, Count, Remote.Address, Source)
		Else
			OnReceiveFromComplete(Data, Offset, Count, Source.RemoteAddress, Source)
		Endif

		Return
	End

	Method OnSendToComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Address:SocketAddress, Source:Socket)
		Local P:= RetrieveWaitingPacketHandle(Data)

		If (P <> Null) Then
			If (HasCallback) Then
				Callback.OnSendComplete(Self, P, Address, Count)
			Endif

			' Remove our transit-reference to this packet.
			P.Release()

			' Now that we've removed our transit-reference,
			' attempt to formally deallocate the packet in question.
			DeallocateIntermediatePacket(P)
		Endif

		Return
	End

	Method OnSendComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Source:Socket)
		If (UDPSocket) Then
			OnSendToComplete(Data, Offset, Count, Remote.Address, Source)
		Else
			OnSendToComplete(Data, Offset, Count, Source.RemoteAddress, Source)
		Endif

		Return
	End

	Method AutoLaunchReceive:Void(S:Socket, P:Packet)
		If (IsClient Or TCPSocket) Then
			LaunchAsyncReceive(S, P)
		Else
			LaunchAsyncReceiveFrom(S, P)
		Endif

		Return
	End

	Method AutoLaunchReceive:Void(S:Socket)
		AutoLaunchReceive(S, AllocateIntermediatePacket())

		Return
	End

	' The 'P' object must be added internally by an external source:
	Method LaunchAsyncReceive:Void(S:Socket, P:Packet)
		P.Reset()

		S.ReceiveAsync(P.Data, P.Offset, P.DataLength, Self)

		Return
	End

	Method LaunchAsyncReceiveFrom:Void(S:Socket, P:Packet)
		P.Reset()

		LaunchAsyncReceiveFrom(S, P, New SocketAddress())

		Return
	End

	Method LaunchAsyncReceiveFrom:Void(S:Socket, P:Packet, Address:SocketAddress)
		P.Reset()

		S.ReceiveFromAsync(P.Data, P.Offset, P.DataLength, Address, Self)

		Return
	End

	Method AllocateIntermediatePacket:Packet()
		Local P:= AllocatePacket()

		WaitingPackets.Push(P)

		Return P
	End

	' The return-value of this command specifies
	' if 'P' is no longer in use, and has been removed.
	Method DeallocateIntermediatePacket:Bool(P:Packet)
		If (ReleasePacket(P)) Then
			WaitingPackets.RemoveEach(P)

			Return True
		Endif

		' Return the default response.
		Return False
	End

	Method RetrieveWaitingPacketHandle:Packet(Data:DataBuffer)
		For Local P:= Eachin WaitingPackets
			If (P.Data = Data) Then
				Return P
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method RemoveWaitingPacket:Void(Data:DataBuffer)
		Local P:= RetrieveWaitingPacketHandle(Data)

		If (P <> Null) Then
			DeallocateIntermediatePacket(P)
		Endif

		Return
	End

	' I/O related:
	Method ReadInternalMessageType:MessageType(S:Stream)
		Return MessageType(S.ReadByte())
	End

	Method WriteInternalMessageType:Void(S:Stream, InternalType:MessageType)
		S.WriteByte(InternalType)

		Return
	End

	' If we are using TCP as our underlying protocol, then 'Source' must be specified.
	Method ReadMessage:MessageType(P:Packet, Address:SocketAddress, Source:Socket=Null)
		Local Type:= P.ReadShort()
		Local DataSize:= P.ReadInt()

		Select Type
			Case MSG_TYPE_INTERNAL
				Local InternalType:= ReadInternalMessageHeader(P)

				Select InternalType
					Case INTERNAL_MSG_CONNECT
						If (IsClient) Then
							Return MSG_TYPE_ERROR
						Endif

						If (MultiConnection) Then
							Local C:= GetClient(Address)

							If (C = Null) Then
								If (Not HasCallback Or Callback.OnClientConnect(Self, Address)) Then
									Local C:Client

									Select SocketType
										Case SOCKET_TYPE_UDP
											C = New Client(Address)
										Case SOCKET_TYPE_TCP
											C = New Client(Source)
										Default
											Return MSG_TYPE_ERROR
									End Select

									Clients.AddLast(C)

									If (HasCallback) Then
										Callback.OnClientAccepted(Self, C)
									Endif
								Endif
							Else
								SendWarningMessage(InternalType, C)
							Endif
						Elseif (UDPSocket) Then
							SendForceDisconnect(Address)
						Else
							' Nothing so far.
						Endif
					Case INTERNAL_MSG_WARNING
						Local WarningType:= ReadInternalMessageType(P)

						'Print("WARNING: Incorrect usage of internal message: " + WarningType)
					Case INTERNAL_MSG_DISCONNECT
						' Somewhat poorly done:
						If (IsClient) Then
							Close()
						Endif
				End Select
			Default
				Local C:Client

				If (Not IsClient) Then
					C = GetClient(Address)

					If (C = Null) Then
						Return MSG_TYPE_ERROR
					Endif
				Else
					C = Remote
				Endif

				If (HasCallback) Then
					#Rem
						Local UserData:= AllocatePacket()

						' Ensure the size demanded by the inbound packet.
						UserData.SmartResize(DataSize)

						P.TransferAmount(UserData, DataSize)
					#End

					Local UserData:= P

					Callback.OnReceiveMessage(Self, C, Type, UserData, DataSize)

					'ReleasePacket(UserData)
				Endif
		End Select

		Return Type
	End

	' If the 'Input' argument is 'Null', it will be passively ignored.
	' The 'DefaultSize' argument is used if 'Input' is 'Null'.
	Method WriteMessage:Void(Output:Packet, Type:MessageType, Input:Packet=Null, DefaultSize:Int=0)
		Output.WriteShort(Type)

		If (Input <> Null) Then
			Output.WriteInt(Input.Length)

			Input.TransferTo(Output)
		Else
			Output.WriteInt(DefaultSize)
		Endif

		Return
	End

	Method ReadInternalMessageHeader:MessageType(P:Packet)
		Return ReadInternalMessageType(P)
	End

	Method WriteInternalMessageHeader:Void(P:Packet, InternalType:MessageType)
		WriteInternalMessageType(P, InternalType)

		Return
	End

	Method SendConnectMessage:Void()
		Local P:= AllocatePacket()

		WriteInternalMessageHeader(P, INTERNAL_MSG_CONNECT)

		Send(P, MSG_TYPE_INTERNAL)

		ReleasePacket(P)

		Return
	End

	Method SendWarningMessage:Void(PostType:MessageType, C:Client)
		Local P:= AllocatePacket()

		WriteInternalMessageHeader(P, INTERNAL_MSG_WARNING)
		WriteInternalMessageType(P, PostType)

		Send(P, C, MSG_TYPE_INTERNAL)

		ReleasePacket(P)

		Return
	End

	' This overload is UDP-only; use at your own risk.
	Method SendForceDisconnect:Void(Address:SocketAddress)
		' Not exactly efficient, but it works:
		Local DataSegment:= AllocatePacket()

		WriteInternalMessageHeader(DataSegment, INTERNAL_MSG_DISCONNECT)

		Local P:= BuildOutputMessage(DataSegment, MSG_TYPE_INTERNAL)

		' From this point on 'P' is handled internally.
		RawSend(Connection, P, Address)

		' Release our data-segment stream.
		ReleasePacket(DataSegment)

		Return
	End

	Method SendForceDisconnect:Void(C:Client)
		Local P:= AllocatePacket()

		WriteInternalMessageHeader(P, INTERNAL_MSG_DISCONNECT)

		Send(P, C, MSG_TYPE_INTERNAL)

		ReleasePacket(P)

		Return
	End

	Public

	' Properties (Public):
	Method Socket:Socket() Property
		Return Self.Connection
	End

	Method SocketType:SockType() Property
		Return Self._SocketType
	End

	Method Bound:Bool() Property
		If (Closed) Then
			Return False
		Endif

		Return Connection.IsBound
	End

	' While binding, this may not provide accurate results.
	Method Closed:Bool() Property
		Return (Connection = Null)
	End

	' A 'NetworkEngine' is only open when its socket has been bound.
	Method Open:Bool() Property
		Return (Not Closed And Connection.IsBound)
	End

	Method IsClient:Bool() Property
		Return Self._IsClient
	End

	Method MultiConnection:Bool() Property
		Return Self._MultiConnection
	End

	Method HasCallback:Bool() Property
		Return (Callback <> Null)
	End

	Method BigEndian:Bool() Property
		Return PacketPool.FixByteOrder
	End

	Method PacketSize:Int() Property
		Return PacketPool.PacketSize
	End

	Method UDPSocket:Bool() Property
		Return (SocketType = SOCKET_TYPE_UDP)
	End

	Method TCPSocket:Bool() Property
		Return (SocketType = SOCKET_TYPE_TCP)
	End

	' Properties (Protected):
	Protected

	Method IsClient:Void(Input:Bool) Property
		Self._IsClient = Input

		Return
	End

	Method MultiConnection:Void(Input:Bool) Property
		Self._MultiConnection = Input

		Return
	End

	Method SocketType:Void(Input:SockType) Property
		Self._SocketType = Input

		Return
	End

	Public

	' Fields (Public):
	' Nothing so far.

	' Fields (Protected):
	Protected

	' A pool of 'Packets'; used for async I/O.
	Field PacketPool:PacketPool

	' A container of packets waiting in transit.
	Field WaitingPackets:Stack<Packet>

	' This acts as the primary connection-socket.
	Field Connection:Socket

	Field Remote:Client = Null

	' A collection of connected clients.
	' For clients, the first entry should be the host.
	Field Clients:List<Client>

	' Used to route call-back routines.
	Field Callback:NetworkListener

	Field _SocketType:SockType = SOCKET_TYPE_UDP

	' Booleans / Flags:
	Field _IsClient:Bool

	' This may be used to toggle accepting multiple clients.
	Field _MultiConnection:Bool = Default_MultiConnection

	Public
End

Strict

Public

' Imports (Public):
Import client
Import packet

' Imports (Private):
Private

Import socket

Public

' Aliases:
Alias MessageType = Int ' Short
Alias SockType = Int

' Interfaces:
Interface NetworkListener
	' Methods:
	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)

	' The 'Message' object will be automatically released.
	' The 'MessageSize' argument specifies how many bytes are in the data-segment of 'Message'.
	Method OnReceiveMessage:Void(Network:NetworkEngine, C:Client, Type:MessageType, Message:Packet, MessageSize:Int)

	' This is called when a client attempts to connect.
	' The return-value of this command dictates if the client at 'Address' should be accepted.
	Method OnClientConnect:Bool(Network:NetworkEngine, Address:SocketAddress)

	' This is called once, at any time after 'OnClientConnect'.
	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)

	' The 'P' object represents the "real" 'Packet' that was sent. (Unlike 'OnReceiveMessage')
	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:SocketAddress, BytesSent:Int)
End

' Classes:
Class NetworkEngine Implements IOnBindComplete, IOnAcceptComplete, IOnConnectComplete, IOnSendComplete, IOnSendToComplete, IOnReceiveFromComplete, IOnReceiveComplete ' Final
	' Constant variable(s):
	Const PORT_AUTOMATIC:= 0

	' Socket types:
	Const SOCKET_TYPE_UDP:= 0
	Const SOCKET_TYPE_TCP:= 1

	' Message types:
	Const MSG_TYPE_ERROR:= -1
	Const MSG_TYPE_INTERNAL:= 4

	' You may use this as a starting-point for message types.
	Const MSG_TYPE_CUSTOM:= 1

	' Internal message types:
	Const INTERNAL_MSG_CONNECT:= 0
	Const INTERNAL_MSG_WARNING:= 1
	Const INTERNAL_MSG_DISCONNECT:= 2

	' Defaults:
	Const Default_PacketSize:= 4096
	Const Default_PacketPoolSize:= 4

	' Booleans / Flags:
	Const Default_FixByteOrder:Bool = True
	Const Default_MultiConnection:Bool = True

	' Functions:
	Function AddressesEqual:Bool(X:SocketAddress, Y:SocketAddress)
		If (X = Y) Then
			Return True
		Endif

		Return (X.Port = Y.Port And X.Host = Y.Host)
	End

	' Constructor(s) (Public):
	Method New(PacketSize:Int=Default_PacketSize, PacketPoolSize:Int=Default_PacketPoolSize, FixByteOrder:Bool=Default_FixByteOrder)
		PacketPool = New PacketPool(PacketSize, PacketPoolSize, FixByteOrder)
		WaitingPackets = New Stack<Packet>()
	End

	' Constructor(s) (Protected):
	Protected

	Method GenerateHostSocket:Void(ProtocolString:String="stream")
		If (Open) Then
			Close()
		Endif

		Connection = New Socket(ProtocolString)

		Return
	End

	Method Init:Void(Protocol:SockType, IsClient:Bool)
		Self.SocketType = Protocol
		Self.IsClient = IsClient

		Select Protocol
			Case SOCKET_TYPE_UDP
				GenerateHostSocket("datagram")
			Case SOCKET_TYPE_TCP
				If (IsClient) Then
					GenerateHostSocket("stream")
				Else
					GenerateHostSocket("server")
				Endif
		End Select

		If (Clients = Null) Then
			Clients = New List<Client>()
		Endif

		Return
	End

	Public

	' Destructor(s):
	Method Close:Void()
		If (Connection <> Null) Then
			Connection.Close()

			Connection = Null
		Endif

		If (Clients <> Null) Then
			For Local C:= Eachin Clients
				C.Close()
			Next

			Clients.Clear()
		Endif

		Remote = Null

		MultiConnection = Default_MultiConnection

		Return
	End

	' Methods:
	Method SetCallback:Void(Callback:NetworkListener)
		Self.Callback = Callback

		Return
	End

	Method Host:Bool(Port:Int, Async:Bool=False, Protocol:SockType=SOCKET_TYPE_UDP, MultiConnection:Bool=Default_MultiConnection, Hostname:String="")
		Init(Protocol, False)

		Self.MultiConnection = MultiConnection

		If (Not Bind(Port, Async, Hostname)) Then
			Close()

			Return False
		Endif

		' Return the default response.
		Return True
	End

	Method Connect:Bool(Address:SocketAddress, Async:Bool=False, Protocol:SockType=SOCKET_TYPE_UDP)
		Init(Protocol, True)

		Remote = New Client(Address, Connection)

		Clients.AddFirst(Remote)

		#Rem
			If (Not Bind(LocalPort, Async, LocalHostName)) Then
				Close()

				Return False
			Endif
		#End

		If (Not RawConnect(Address.Host, Address.Port, Async)) Then
			Close()

			Return False
		Endif

		' Return the default response.
		Return True
	End

	Method Connect:Bool(Host:String, Port:Int, Async:Bool=False, Protocol:SockType=SOCKET_TYPE_UDP)
		Return Connect(New SocketAddress(Host, Port), Async, Protocol)
	End

	Method Update:Void(AsyncEvents:Bool=False)
		If (AsyncEvents) Then
			UpdateAsyncEvents()
		Endif

		If (Not Open) Then
			Return
		Endif

		Return
	End

	' I/O related:

	#Rem
		When no address is specified, 'Send' will output to
		the host for clients, and everyone else for hosts.

		With an address, clients may formally send to hosts,
		and hosts may send to exact addresses. Clients sending
		to other end-points is currently undefined.
	#End

	Method Send:Void(P:Packet, Type:MessageType)
		Local RawPacket:= BuildOutputMessage(P, Type)

		If (Not IsClient And TCPSocket) Then
			RawSendToAll(RawPacket)

			#Rem
				For Local C:= Eachin Clients
					RawSend(C.Connection, BuildOutputMessage(P, Type))
				Next
			#End
		Else
			'Local RawPacket:= BuildOutputMessage(P, Type)

			RawSend(Connection, RawPacket)
		Endif

		Return
	End

	Method Send:Void(P:Packet, C:Client, Type:MessageType)
		Local MSG:= BuildOutputMessage(P, Type)

		Select SocketType
			Case SOCKET_TYPE_UDP
				RawSend(Connection, MSG, C.Address)
			Case SOCKET_TYPE_TCP
				RawSend(C.Connection, MSG)
		End Select

		Return
	End

	#Rem
		These commands may be used to send raw data.

		This can be useful, as you can generate an output packet yourself,
		then send it as you see fit. Use these commands with caution.
	#End

	Method RawSend:Void(Connection:Socket, RawPacket:Packet)
		If (IsClient Or TCPSocket) Then
			' Obtain a transit-reference.
			RawPacket.Obtain()

			Connection.SendAsync(RawPacket.Data, RawPacket.Offset, RawPacket.Length, Self)
		Else
			RawSendToAll(RawPacket)
		Endif

		Return
	End

	' This is only useful for hosts; clients will send normally.
	Method RawSendToAll:Void(RawPacket:Packet)
		If (UDPSocket) Then
			For Local C:= Eachin Clients
				RawSend(Connection, RawPacket, C.Address)
			Next
		Else
			For Local C:= Eachin Clients
				RawSend(C.Connection, RawPacket)
			Next
		Endif

		Return
	End

	' This may only be called by UDP sockets.
	Method RawSend:Void(Connection:Socket, RawPacket:Packet, Address:SocketAddress)
		If (IsClient And (Address = Null Or AddressesEqual(Address, Remote.Address))) Then
			RawSend(Connection, RawPacket)
		Else ' If (Not IsClient) Then
			' Obtain a transit-reference.
			RawPacket.Obtain()

			Connection.SendToAsync(RawPacket.Data, RawPacket.Offset, RawPacket.Length, Address, Self)
		Endif

		Return
	End

	' Used internally; use at your own risk.
	' This command produces a packet in the appropriate format.
	' This will generate an "intermediate" packet, which is handled by an internal system.
	' For details on the 'DefaultSize' argument, please see 'WriteMessage'.
	Method BuildOutputMessage:Packet(P:Packet, Type:MessageType, DefaultSize:Int=0)
		Local Output:= AllocateIntermediatePacket()

		WriteMessage(Output, Type, P, DefaultSize)

		Return Output
	End

	Method IsCallback:Bool(L:NetworkListener)
		Return (Callback = L)
	End

	Method AllocatePacket:Packet()
		Return PacketPool.Allocate()
	End

	Method ReleasePacket:Bool(P:Packet)
		Return PacketPool.Release(P)
	End

	Method GetClient:Client(Address:SocketAddress)
		For Local C:= Eachin Clients
			If (AddressesEqual(Address, C.Address)) Then
				Return C
			Endif
		Next

		Return Null
	End

	Method Connected:Bool(Address:SocketAddress)
		Return (GetClient(Address) <> Null)
	End

	' Methods (Protected):
	Protected

	' This will bind the socket specified, using this network.
	' If 'Async' is disabled, this will return whether the bind operation was successful.
	' If enabled, this will only return 'False' when an internal error occurs.
	Method Bind:Bool(Connection:Socket, Port:Int, Async:Bool=False, Hostname:String="")
		' Check for errors:
		If (Connection.IsBound) Then
			Return False
		Endif

		If (Not Async) Then
			Local Response:= Connection.Bind(Hostname, Port)

			OnBindComplete(Response, Connection)

			Return Response
		Else
			Connection.BindAsync(Hostname, Port, Self)
		Endif

		' Return the default response.
		Return True
	End

	' This will use the internal socket to perform a 'Bind' operation.
	Method Bind:Bool(Port:Int, Async:Bool=False, Hostname:String="")
		Return Bind(Self.Connection, Port, Async, Hostname)
	End

	Method RawConnect:Bool(Host:String, Port:Int, Async:Bool=False)
		If (Async) Then
			Connection.ConnectAsync(Host, Port, Self)
		Else
			Connection.Connect(Host, Port)
		Endif

		' Return the default response.
		Return True
	End

	' Call-backs:
	Method OnBindComplete:Void(Bound:Bool, Source:Socket)
		If (HasCallback) Then
			Callback.OnNetworkBind(Self, Bound)
		Endif

		If (Bound) Then
			If (Not IsClient) Then
				If (TCPSocket) Then
					Connection.AcceptAsync(Self)
				Else
					AutoLaunchReceive(Source)
				Endif
			Else
				AutoLaunchReceive(Source)
			Endif
		Endif

		Return
	End

	Method OnConnectComplete:Void(Connected:Bool, Source:Socket)
		OnBindComplete(Connected, Source)

		If (Connected) Then
			SendConnectMessage()
		Endif

		Return
	End

	Method OnAcceptComplete:Void(Socket:Socket, Source:Socket)
		AutoLaunchReceive(Socket)

		' Check if we can accept more connections:
		If (MultiConnection) Then
			Source.AcceptAsync(Self) ' Connection
		Endif

		Return
	End

	Method OnReceiveFromComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Address:SocketAddress, Source:Socket)
		#Rem
			If (Source <> Connection) Then
				Return
			Endif
		#End

		Local P:= RetrieveWaitingPacketHandle(Data)

		If (P <> Null) Then
			If (HasCallback) Then
				' Manually disable 'Socket' usage when using UDP:
				If (UDPSocket) Then
					ReadMessage(P, Address)
				Else
					ReadMessage(P, Address, Source)
				Endif
			Endif

			AutoLaunchReceive(Source, P)
		Endif

		Return
	End

	Method OnReceiveComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Source:Socket)
		If (UDPSocket) Then
			OnReceiveFromComplete(Data, Offset, Count, Remote.Address, Source)
		Else
			OnReceiveFromComplete(Data, Offset, Count, Source.RemoteAddress, Source)
		Endif

		Return
	End

	Method OnSendToComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Address:SocketAddress, Source:Socket)
		Local P:= RetrieveWaitingPacketHandle(Data)

		If (P <> Null) Then
			If (HasCallback) Then
				Callback.OnSendComplete(Self, P, Address, Count)
			Endif

			' Remove our transit-reference to this packet.
			P.Release()

			' Now that we've removed our transit-reference,
			' attempt to formally deallocate the packet in question.
			DeallocateIntermediatePacket(P)
		Endif

		Return
	End

	Method OnSendComplete:Void(Data:DataBuffer, Offset:Int, Count:Int, Source:Socket)
		If (UDPSocket) Then
			OnSendToComplete(Data, Offset, Count, Remote.Address, Source)
		Else
			OnSendToComplete(Data, Offset, Count, Source.RemoteAddress, Source)
		Endif

		Return
	End

	Method AutoLaunchReceive:Void(S:Socket, P:Packet)
		If (IsClient Or TCPSocket) Then
			LaunchAsyncReceive(S, P)
		Else
			LaunchAsyncReceiveFrom(S, P)
		Endif

		Return
	End

	Method AutoLaunchReceive:Void(S:Socket)
		AutoLaunchReceive(S, AllocateIntermediatePacket())

		Return
	End

	' The 'P' object must be added internally by an external source:
	Method LaunchAsyncReceive:Void(S:Socket, P:Packet)
		P.Reset()

		S.ReceiveAsync(P.Data, P.Offset, P.DataLength, Self)

		Return
	End

	Method LaunchAsyncReceiveFrom:Void(S:Socket, P:Packet)
		P.Reset()

		LaunchAsyncReceiveFrom(S, P, New SocketAddress())

		Return
	End

	Method LaunchAsyncReceiveFrom:Void(S:Socket, P:Packet, Address:SocketAddress)
		P.Reset()

		S.ReceiveFromAsync(P.Data, P.Offset, P.DataLength, Address, Self)

		Return
	End

	Method AllocateIntermediatePacket:Packet()
		Local P:= AllocatePacket()

		WaitingPackets.Push(P)

		Return P
	End

	' The return-value of this command specifies
	' if 'P' is no longer in use, and has been removed.
	Method DeallocateIntermediatePacket:Bool(P:Packet)
		If (ReleasePacket(P)) Then
			WaitingPackets.RemoveEach(P)

			Return True
		Endif

		' Return the default response.
		Return False
	End

	Method RetrieveWaitingPacketHandle:Packet(Data:DataBuffer)
		For Local P:= Eachin WaitingPackets
			If (P.Data = Data) Then
				Return P
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method RemoveWaitingPacket:Void(Data:DataBuffer)
		Local P:= RetrieveWaitingPacketHandle(Data)

		If (P <> Null) Then
			DeallocateIntermediatePacket(P)
		Endif

		Return
	End

	' I/O related:
	Method ReadInternalMessageType:MessageType(S:Stream)
		Return MessageType(S.ReadByte())
	End

	Method WriteInternalMessageType:Void(S:Stream, InternalType:MessageType)
		S.WriteByte(InternalType)

		Return
	End

	' If we are using TCP as our underlying protocol, then 'Source' must be specified.
	Method ReadMessage:MessageType(P:Packet, Address:SocketAddress, Source:Socket=Null)
		Local Type:= P.ReadShort()
		Local DataSize:= P.ReadInt()

		Select Type
			Case MSG_TYPE_INTERNAL
				Local InternalType:= ReadInternalMessageHeader(P)

				Select InternalType
					Case INTERNAL_MSG_CONNECT
						If (IsClient) Then
							Return MSG_TYPE_ERROR
						Endif

						If (MultiConnection) Then
							Local C:= GetClient(Address)

							If (C = Null) Then
								If (Not HasCallback Or Callback.OnClientConnect(Self, Address)) Then
									Local C:Client

									Select SocketType
										Case SOCKET_TYPE_UDP
											C = New Client(Address)
										Case SOCKET_TYPE_TCP
											C = New Client(Source)
										Default
											Return MSG_TYPE_ERROR
									End Select

									Clients.AddLast(C)

									If (HasCallback) Then
										Callback.OnClientAccepted(Self, C)
									Endif
								Endif
							Else
								SendWarningMessage(InternalType, C)
							Endif
						Elseif (UDPSocket) Then
							SendForceDisconnect(Address)
						Else
							' Nothing so far.
						Endif
					Case INTERNAL_MSG_WARNING
						Local WarningType:= ReadInternalMessageType(P)

						'Print("WARNING: Incorrect usage of internal message: " + WarningType)
					Case INTERNAL_MSG_DISCONNECT
						' Somewhat poorly done:
						If (IsClient) Then
							Close()
						Endif
				End Select
			Default
				Local C:Client

				If (Not IsClient) Then
					C = GetClient(Address)

					If (C = Null) Then
						Return MSG_TYPE_ERROR
					Endif
				Else
					C = Remote
				Endif

				If (HasCallback) Then
					#Rem
						Local UserData:= AllocatePacket()

						' Ensure the size demanded by the inbound packet.
						UserData.SmartResize(DataSize)

						P.TransferAmount(UserData, DataSize)
					#End

					Local UserData:= P

					Callback.OnReceiveMessage(Self, C, Type, UserData, DataSize)

					'ReleasePacket(UserData)
				Endif
		End Select

		Return Type
	End

	' If the 'Input' argument is 'Null', it will be passively ignored.
	' The 'DefaultSize' argument is used if 'Input' is 'Null'.
	Method WriteMessage:Void(Output:Packet, Type:MessageType, Input:Packet=Null, DefaultSize:Int=0)
		Output.WriteShort(Type)

		If (Input <> Null) Then
			Output.WriteInt(Input.Length)

			Input.TransferTo(Output)
		Else
			Output.WriteInt(DefaultSize)
		Endif

		Return
	End

	Method ReadInternalMessageHeader:MessageType(P:Packet)
		Return ReadInternalMessageType(P)
	End

	Method WriteInternalMessageHeader:Void(P:Packet, InternalType:MessageType)
		WriteInternalMessageType(P, InternalType)

		Return
	End

	Method SendConnectMessage:Void()
		Local P:= AllocatePacket()

		WriteInternalMessageHeader(P, INTERNAL_MSG_CONNECT)

		Send(P, MSG_TYPE_INTERNAL)

		ReleasePacket(P)

		Return
	End

	Method SendWarningMessage:Void(PostType:MessageType, C:Client)
		Local P:= AllocatePacket()

		WriteInternalMessageHeader(P, INTERNAL_MSG_WARNING)
		WriteInternalMessageType(P, PostType)

		Send(P, C, MSG_TYPE_INTERNAL)

		ReleasePacket(P)

		Return
	End

	' This overload is UDP-only; use at your own risk.
	Method SendForceDisconnect:Void(Address:SocketAddress)
		' Not exactly efficient, but it works:
		Local DataSegment:= AllocatePacket()

		WriteInternalMessageHeader(DataSegment, INTERNAL_MSG_DISCONNECT)

		Local P:= BuildOutputMessage(DataSegment, MSG_TYPE_INTERNAL)

		' From this point on 'P' is handled internally.
		RawSend(Connection, P, Address)

		' Release our data-segment stream.
		ReleasePacket(DataSegment)

		Return
	End

	Method SendForceDisconnect:Void(C:Client)
		Local P:= AllocatePacket()

		WriteInternalMessageHeader(P, INTERNAL_MSG_DISCONNECT)

		Send(P, C, MSG_TYPE_INTERNAL)

		ReleasePacket(P)

		Return
	End

	Public

	' Properties (Public):
	Method Socket:Socket() Property
		Return Self.Connection
	End

	Method SocketType:SockType() Property
		Return Self._SocketType
	End

	Method Bound:Bool() Property
		If (Closed) Then
			Return False
		Endif

		Return Connection.IsBound
	End

	' While binding, this may not provide accurate results.
	Method Closed:Bool() Property
		Return (Connection = Null)
	End

	' A 'NetworkEngine' is only open when its socket has been bound.
	Method Open:Bool() Property
		Return (Not Closed And Connection.IsBound)
	End

	Method IsClient:Bool() Property
		Return Self._IsClient
	End

	Method MultiConnection:Bool() Property
		Return Self._MultiConnection
	End

	Method HasCallback:Bool() Property
		Return (Callback <> Null)
	End

	Method BigEndian:Bool() Property
		Return PacketPool.FixByteOrder
	End

	Method PacketSize:Int() Property
		Return PacketPool.PacketSize
	End

	Method UDPSocket:Bool() Property
		Return (SocketType = SOCKET_TYPE_UDP)
	End

	Method TCPSocket:Bool() Property
		Return (SocketType = SOCKET_TYPE_TCP)
	End

	' Properties (Protected):
	Protected

	Method IsClient:Void(Input:Bool) Property
		Self._IsClient = Input

		Return
	End

	Method MultiConnection:Void(Input:Bool) Property
		Self._MultiConnection = Input

		Return
	End

	Method SocketType:Void(Input:SockType) Property
		Self._SocketType = Input

		Return
	End

	Public

	' Fields (Public):
	' Nothing so far.

	' Fields (Protected):
	Protected

	' A pool of 'Packets'; used for async I/O.
	Field PacketPool:PacketPool

	' A container of packets waiting in transit.
	Field WaitingPackets:Stack<Packet>

	' This acts as the primary connection-socket.
	Field Connection:Socket

	Field Remote:Client = Null

	' A collection of connected clients.
	' For clients, the first entry should be the host.
	Field Clients:List<Client>

	' Used to route call-back routines.
	Field Callback:NetworkListener

	Field _SocketType:SockType = SOCKET_TYPE_UDP

	' Booleans / Flags:
	Field _IsClient:Bool

	' This may be used to toggle accepting multiple clients.
	Field _MultiConnection:Bool = Default_MultiConnection

	Public
End


Reduce amount of New Vector2D in Sprite

Reduce amount of New Vector2D in Sprite

Strict

Private

Import align
Import basedisplayobject
Import bono.kernel
Import bono.utils
Import mojo.graphics

Public

Class Sprite Extends BaseDisplayObject Implements AppObserver
    Private

    Field currentFrame:Int
    Field frameCount:Int
    Field frameSize:Vector2D
    Field frameTimer:Int
    Field image:Image
    Field imageName:String
    Field _scale:Vector2D = New Vector2D(1, 1)
    Field _halign:Int = Align.LEFT
    Field _valign:Int = Align.TOP
    Field renderPos:Vector2D

    Public

    Field frameSpeed:Int
    Field loopAnimation:Bool
    Field rotation:Float

    Method New(imageName:String, pos:Vector2D=Null)
        SetNameAndPos(imageName, pos)
        LoadImage()
    End

    Method New(imageName:String, frameSize:Vector2D, frameCount:Int, frameSpeed:Int, pos:Vector2D=Null)
        Self.frameSize = frameSize
        Self.frameCount = frameCount
        Self.frameSpeed = frameSpeed

        SetNameAndPos(imageName, pos)
        LoadImage()
    End

    Method OnLoading:Void()
    End

    Method OnResume:Void()
    End

    Method OnSuspend:Void()
    End

    Method OnRender:Void()
        If Not renderPos Then CalculateRenderPos()
        If color Then color.Activate()
        DrawImage(image, renderPos.x, renderPos.y, rotation, scale.x, scale.y, currentFrame)
        If color Then color.Deactivate()
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If frameCount <= 0 Then Return
        If animationIsDone Then Return

        If frameTimer < frameSpeed
            frameTimer += deltaTimer.frameTime
            Return
        End

        If (currentFrame + 1) = frameCount
            If loopAnimation
                currentFrame = 1
            End
        Else
            currentFrame += 1
        End
        frameTimer = 0
    End

    Method Restart:Void()
        currentFrame = 0
    End

    Method Collide:Bool(checkPos:Vector2D)
        If checkPos.x < pos.x Or checkPos.x > pos.x + size.x Then Return False
        If checkPos.y < pos.y Or checkPos.y > pos.y + size.y Then Return False
        Return True
    End

    Method animationIsDone:Bool() Property
        If loopAnimation Then Return False
        Return (currentFrame = frameCount)
    End

    Method scale:Vector2D() Property
        Return _scale
    End

    Method scale:Void(newScale:Vector2D) Property
        If image Then size = size.Div(_scale).Mul(newScale)
        _scale = newScale
    End

    Method filepath:String() Property
        Return imageName
    End

    Method halign:Int() Property
        Return _halign
    End

    Method halign:Void(newAlign:Int) Property
        _halign = newAlign
        renderPos = Null
    End

    Method valign:Int() Property
        Return _valign
    End

    Method valign:Void(newAlign:Int) Property
        _valign = newAlign
        renderPos = Null
    End

    Method pos:Void(newPos:Vector2D) Property
        Super.pos(newPos)
        renderPos = Null
    End

    Method pos:Vector2D() Property
        Return Super.pos()
    End

    Private

    Method CalculateRenderPos:Void()
        renderPos = pos.Copy()
        Align.AdjustHorizontal(renderPos, Self, halign)
        Align.AdjustVertical(renderPos, Self, valign)
    End

    Method LoadImage:Void()
        If frameSize
            image = graphics.LoadImage(imageName, frameSize.x, frameSize.y, frameCount)
            size = frameSize.Copy()
        Else
            image = graphics.LoadImage(imageName)
            size = New Vector2D(image.Width(), image.Height())
        End

        If Not image Then Error("Unable to load: " + imageName)
        size.Mul(scale)
    End

    Method SetNameAndPos:Void(imageName:String, pos:Vector2D=Null)
        Self.imageName = imageName
        If Not pos Then pos = New Vector2D(0, 0)
        Self.pos = pos
    End
End

Strict

Private

Import align
Import basedisplayobject
Import bono.kernel
Import bono.utils
Import mojo.graphics

Public

Class Sprite Extends BaseDisplayObject Implements AppObserver
    Private

    Field currentFrame:Int
    Field frameCount:Int
    Field frameSize:Vector2D
    Field frameTimer:Int
    Field image:Image
    Field imageName:String
    Field _scale:Vector2D = New Vector2D(1, 1)

    Public

    Field halign:Int = Align.LEFT
    Field valign:Int = Align.TOP
    Field frameSpeed:Int
    Field loopAnimation:Bool
    Field rotation:Float

    Method New(imageName:String, pos:Vector2D=Null)
        SetNameAndPos(imageName, pos)
        LoadImage()
    End

    Method New(imageName:String, frameSize:Vector2D, frameCount:Int, frameSpeed:Int, pos:Vector2D=Null)
        Self.frameSize = frameSize
        Self.frameCount = frameCount
        Self.frameSpeed = frameSpeed

        SetNameAndPos(imageName, pos)
        LoadImage()
    End

    Method OnLoading:Void()
    End

    Method OnResume:Void()
    End

    Method OnSuspend:Void()
    End

    Method OnRender:Void()
        Local renderPos:Vector2D = pos.Copy()
        Align.AdjustHorizontal(renderPos, Self, halign)
        Align.AdjustVertical(renderPos, Self, valign)

        If color Then color.Activate()
        DrawImage(image, renderPos.x, renderPos.y, rotation, scale.x, scale.y, currentFrame)
        If color Then color.Deactivate()
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If frameCount <= 0 Then Return
        If animationIsDone Then Return

        If frameTimer < frameSpeed
            frameTimer += deltaTimer.frameTime
            Return
        End

        If (currentFrame + 1) = frameCount
            If loopAnimation
                currentFrame = 1
            End
        Else
            currentFrame += 1
        End
        frameTimer = 0
    End

    Method Restart:Void()
        currentFrame = 0
    End

    Method Collide:Bool(checkPos:Vector2D)
        If checkPos.x < pos.x Or checkPos.x > pos.x + size.x Then Return False
        If checkPos.y < pos.y Or checkPos.y > pos.y + size.y Then Return False
        Return True
    End

    Method animationIsDone:Bool() Property
        If loopAnimation Then Return False
        Return (currentFrame = frameCount)
    End

    Method scale:Vector2D() Property
        Return _scale
    End

    Method scale:Void(newScale:Vector2D) Property
        If image Then size = size.Div(_scale).Mul(newScale)
        _scale = newScale
    End

    Method filepath:String() Property
        Return imageName
    End

    Private

    Method LoadImage:Void()
        If frameSize
            image = graphics.LoadImage(imageName, frameSize.x, frameSize.y, frameCount)
            size = frameSize.Copy()
        Else
            image = graphics.LoadImage(imageName)
            size = New Vector2D(image.Width(), image.Height())
        End

        If Not image Then Error("Unable to load: " + imageName)
        size.Mul(scale)
    End

    Method SetNameAndPos:Void(imageName:String, pos:Vector2D=Null)
        Self.imageName = imageName
        If Not pos Then pos = New Vector2D(0, 0)
        Self.pos = pos
    End
End


Small optimization for new lines

Small optimization for new lines

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxg
Import system.flxcolor
Import system.flxassetsmanager

Private

#If FLX_TEXT_DRIVER = "angelfont"
	Import vendor.angelfont
#ElseIf FLX_TEXT_DRIVER = "fontmachine"
	Import vendor.fontmachine
#End

Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.${FLX_FONT_EXTENSION}"

Public

Class FlxText Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_CENTER:Float = 0.5
	Const ALIGN_RIGHT:Float = 1

	Const SYSTEM_FONT:String = "system"

Private
	Global _FontLoader:FlxFontLoader = New FlxFontLoader()

	Global _FontsManager:FlxResourcesManager<FlxBitmapFont> = New FlxResourcesManager<FlxBitmapFont>()

	Field _shadow:FlxColor

	Field _value:String

	Field _lines:FlxTextLine[]

	Field _countLines:Int

	Field _alignment:Float

	Field _fontFamily:String

	Field _fontSize:Int

	Field _fontHeight:Int

	Field _fontObject:FlxBitmapFont

Public
	Method New(x:Float, y:Float, width:Int = 0, text:String = "")
		Super.New(x, y)

		SetRenderer(Self)
		_shadow = New FlxColor(0)

		Self.width = width
		frameWidth = Self.width
		moves = False

		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

	Method Destroy:Void()
		Local i:Int = 0, l:Int = _lines.Length()

		While (i < l)
			_lines[i] = Null
			i += 1
		Wend

		_fontObject = Null
		_shadow = Null

		Super.Destroy()
	End Method

	Method SetFormat:FlxText(font:String = "", size:Int = 0, color:Int = FlxG.WHITE, alignment:Float = ALIGN_LEFT, shadowColor:Int = 0)
		_alignment = alignment
		_fontFamily = font
		_fontSize = FlxAssetsManager.GetFont(font).GetValidSize(size)

		Self.Color = color
		Shadow = shadowColor

		_ResetFont()
		_ResetHelpers()

		Return Self
	End Method

	Method SetWidth:Void(width:Float)
		Self.width = width
		frameWidth = Self.width
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _value
	End Method

	Method Text:Void(text:String) Property
		_value = text
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_fontSize = FlxAssetsManager.GetFont(_fontFamily).GetValidSize(size)
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _fontSize
	End Method

	Method Font:String() Property
		Return _fontFamily
	End Method

	Method Font:Void(font:String) Property
		_fontFamily = font
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Alignment:Float() Property
		Return _alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		If (_alignment = alignment) Return
		_alignment = alignment
		_ResetAlignment()
	End Method

	Method Shadow:Int() Property
		Return _shadow.argb
	End Method

	Method Shadow:Void(color:Int) Property
		_shadow.SetARGB(color)
	End Method

	Method SetFontShadowEnabled:Void(enabled:Bool)
	#If FLX_TEXT_DRIVER = "angelfont"
		Return
	#ElseIf FLX_TEXT_DRIVER = "fontmachine"
		_fontObject._drawShadow = enabled
	#End
	End Method

	Method SetFontBoderEnabled:Void(enabled:Bool)
	#If FLX_TEXT_DRIVER = "angelfont"
		Return
	#ElseIf FLX_TEXT_DRIVER = "fontmachine"
		_fontObject._drawBorder = enabled
	#End
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._LastDrawingColor
			Local oldAlpha:Int = FlxG._LastDrawingAlpha

			If (_camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_shadow, _camera._color)

				If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
					SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
				End If
			Else
				If (FlxG._LastDrawingColor <> _shadow.argb) Then
					SetColor(_shadow.r, _shadow.g, _shadow.b)
				End If
			End If

			If (_camera.Alpha < 1) Then
				Local _mixedAlpha:Float = _camera.Alpha * _shadow.a

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _shadow.a) Then
					SetAlpha(_shadow.a)
				End If
			End If

			_fontObject.DrawText(Self, x + 1, y + 1)

			_mixedColor.SetRGB(oldColor)
			SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
			SetAlpha(oldAlpha)
		End If

		_fontObject.DrawText(Self, x, y)
	End Method

Private
	Method _ResetHelpers:Void()
		If (_value.Length() > 0) Then
			If (width <> 0) Then
				_ParseText()
			Else
				_countLines = 0
				_AddLine(0, _value.Length(), _fontObject.GetTextWidth(Self, 0, _value.Length()))
				_ResetAlignment()
			End If

			Self.height = _GetHeight()
			frameHeight = Self.height
		Else
			Self.height = 0
			frameHeight = 0
		End If

		Super._ResetHelpers()
	End Method

	Method _ResetFont:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _fontSize

		_fontObject = _FontsManager.GetResource(_fontFamily + _fontSize, _FontLoader)
		_fontHeight = _fontObject.GetFontHeight(Self)
	End Method

	Method _ResetAlignment:Void()
		If (_value.Length() = 0) Return

		For Local line:Int = 0 Until _countLines
			_lines[line].xOffset = (width - _lines[line].width) * _alignment
		Next
	End Method

	Method _GetHeight:Int()
		If (width = 0) Then
			Return _fontHeight
		Else
			Return _fontObject.GetTextHeight(Self)
		End If
	End Method

	Method _ParseText:Void()
		_countLines = 0

		Local prevOffset:Int = 0
		Local offset:Int = -1

		Local i:Int = 0, l:Int = _value.Length()

		While (i < l)
			If (_value[i] = 10 Or _value[i] = KEY_ENTER) Then
				offset = i
				Exit
			End If

			i += 1
		Wend

		If (offset >= 0) Then
			While (offset >= 0)
				_BuildLines(prevOffset, offset)

				prevOffset = offset + 1

				i = prevOffset
				offset = -1

				While (i < l)
					If (_value[i] = 10 Or _value[i] = KEY_ENTER) Then
						offset = i
						Exit
					End If

					i += 1
				Wend
			Wend

			_BuildLines(prevOffset, _value.Length())
		Else
			_BuildLines(0, _value.Length())
		End If

		_ResetAlignment()
	End Method

	Method _BuildLines:Void(startPos:Int, endPos:Int)
		Local textWidth:Int = _fontObject.GetTextWidth(Self, startPos, endPos)

		If (width < textWidth) Then
			Local textLength:Int = endPos - startPos

			Local range:Int = Ceil(textLength / Float(Floor(textWidth / Float(width)) + 1))

			Repeat
				range += 1
				textWidth = _fontObject.GetTextWidth(Self, 0, range)
			Until (textWidth >= width)

			Local maxOffset:Int = range
			Local minOffset:Int = 0
			Local offset:Int = maxOffset
			Local tmpOffset:Int = 0
			Local dirty:Bool = False
			Local finalTextWidth:Int = 0
			Local success:Bool

			Repeat
				Repeat
					offset -= 1
					If (offset - minOffset <= 1) Then
						offset = minOffset + 1
						Exit
					End if

					tmpOffset = -1
					For Local i:Int = offset - 1 To minOffset Step - 1
						success = False

						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							tmpOffset = i
							success = True
							Exit
						End If
					Next

					If (tmpOffset < 0) Then
						tmpOffset = _GetMinOffset(minOffset, offset)
					Else
						If (_value[minOffset] = KEY_SPACE Or _value[minOffset] = KEY_TAB) Then
							For Local i:Int = minOffset To offset
								If (_value[i] <> KEY_SPACE And _value[i] <> KEY_TAB) Then
									minOffset = i
									Exit
								End If
							Next
						End If
					End If

					offset = tmpOffset
					textWidth = _fontObject.GetTextWidth(Self, minOffset, offset)
				Until (textWidth <= width)

				dirty = False
				finalTextWidth = _fontObject.GetTextWidth(Self, minOffset)

				If (finalTextWidth > width And textLength - minOffset > 1) Then
					For Local i:Int = minOffset Until offset
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = offset - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							offset -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - minOffset = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, offset, textWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, offset))
					End If

					minOffset = offset
					If (success) minOffset += 1

					maxOffset = minOffset + range
					offset = maxOffset
				Else
					Local l:Int = _value.Length()

					For Local i:Int = minOffset Until l
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = l - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							l -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (l - minOffset <= 0) Exit

					If ( Not dirty) Then
						_AddLine(minOffset, l, finalTextWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, l))
					End If

					Exit
				End If
			Forever
		Else
			_AddLine(startPos, endPos, textWidth)
		End If
	End Method

	Method _GetMinOffset:Int(startPos:Int, endPos:Int)
		Local offset:Int = endPos - startPos

		While (_fontObject.GetTextWidth(Self, startPos, startPos + offset) > width)
			offset -= 1
			If (offset = 1) Return offset
		Wend

		Return startPos + offset
	End Method

	Method _AddLine:FlxTextLine(startPos:Int, endPos:Int, width:Int)
		Local line:FlxTextLine

		If (_countLines = _lines.Length()) Then
			_lines = _lines.Resize(_countLines * 2 + 10)
			line = New FlxTextLine(startPos, endPos, width)
			_lines[_countLines] = line
		Else
			line = _lines[_countLines]
			If (line = Null) Then
				line = New FlxTextLine(startPos, endPos, width)
				_lines[_countLines] = line
			Else
				line.Reset(startPos, endPos, width)
			End If
		End If

		_countLines += 1
		Return line
	End Method

End Class

Private
Class FlxTextLine

	Field xOffset:Float

	Field startPos:Int

	Field endPos:Int

	Field width:Int

	Method New(startPos:Int, endPos:Int, width:Int)
		Reset(startPos, endPos, width)
	End Method

	Method Reset:Void(startPos:Int, endPos:Int, width:Int)
		Self.startPos = startPos
		Self.endPos = endPos
		Self.width = width
	End Method

End Class

Class FlxFontLoader Extends FlxResourceLoader<FlxBitmapFont>

	Field fontFamily:String = FlxText.SYSTEM_FONT
	Field fontSize:Int

	Method Load:FlxBitmapFont(name:String)
		Return New FlxBitmapFont(FlxAssetsManager.GetFont(fontFamily).GetPath(fontSize))
	End Method

End Class

#If FLX_TEXT_DRIVER = "angelfont"
	Class FlxBitmapFont
	Private
		Field image:Image[] = New Image[1]
		Field chars:Char[256]

		Field kernPairs:IntMap<IntMap<KernPair>> = New IntMap<IntMap<KernPair>>
		Global firstKp:IntMap<KernPair>
		Global secondKp:KernPair

		Field iniText:String

		Field xOffset:Int
		Field yOffset:Int

	Public
		Field useKerning:Bool = True

		Field height:Int = 0
		Field heightOffset:Int = 9999

		Field lineGap:Int = 5

		Method New(url:String)
			iniText = LoadString(url+".fnt")
			Local lines:String[] = iniText.Split(String.FromChar(10))
			Local firstLine:String = lines[0]
			If firstLine.Contains("<?xml")
				Local lineList:List<String> = New List<String>(lines)
				lineList.RemoveFirst()
				lines = lineList.ToArray()
				iniText = "~n".Join(lines)
			End

			Local pageCount:Int = 0

			Local config:= LoadConfig(iniText)

			Local nodes:= config.FindNodesByPath("font/chars/char")

			For Local node:= EachIn nodes
				Local id:Int = Int(node.GetAttribute("id"))
				Local page:Int = Int(node.GetAttribute("page"))
				If pageCount < page pageCount = page
				chars[id] = New Char(Int(node.GetAttribute("x")), Int(node.GetAttribute("y")), Int(node.GetAttribute("width")), Int(node.GetAttribute("height")),  Int(node.GetAttribute("xoffset")),  Int(node.GetAttribute("yoffset")),  Int(node.GetAttribute("xadvance")), page)
				Local ch := chars[id]
				If ch.height > Self.height Self.height = ch.height
				If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
			Next

			nodes = config.FindNodesByPath("font/kernings/kerning")

			If (nodes <> Null) Then
				For Local node:= EachIn nodes
					Local first:Int = Int(node.GetAttribute("first"))
					firstKp = kernPairs.Get(first)
					If firstKp = Null
						kernPairs.Add(first, New IntMap<KernPair>)
						firstKp = kernPairs.Get(first)
					End

					Local second:Int = Int(node.GetAttribute("second"))
					firstKp.Add(second, New KernPair(first, second, Int(node.GetAttribute("amount"))))
				End
			Else
				useKerning = False
			End If

			If pageCount = 0
				image[0] = LoadImage(url+".png")
				If image[0] = Null image[0] = LoadImage(url + "_0.png")
			Else
				For Local page:= 0 To pageCount
					If image.Length < page+1 image = image.Resize(page+1)
					image[page] = LoadImage(url+"_"+page+".png")
				End
			End
		End

		Method GetTextWidth:Int(txt:FlxText, startPos:Int = 0, endPos:Int = -1)
			Local prevChar:Int = 0
			Local width:Int = 0

			If (endPos < 0) endPos = txt._value.Length()
			Local asc:Int, ac:Char

			For Local i:= startPos Until endPos
				asc = txt._value[i]
				ac = chars[asc]

				If ac <> Null
					If useKerning
						Local firstKp:= kernPairs.Get(prevChar)
						If firstKp <> Null
							Local secondKp:= firstKp.Get(asc)
							If secondKp <> Null
								xOffset += secondKp.amount
							End
						Endif
					EndIf

					width += ac.xAdvance
					prevChar = asc
				EndIf
			Next

			Return width
		End Method

		Method GetFontHeight:Int(txt:FlxText)
			Local h:Int = 0

			If (txt._value.Length() <> 0) Then
				Local l:Int = txt._value.Length()

				For Local i:= 0 Until l
					Local asc:Int = txt._value[i]
					Local ac:Char = chars[asc]
					If ac.height+ac.yOffset > h h = ac.height+ac.yOffset
				Next
			Else
				h = chars[KEY_SPACE].height + chars[KEY_SPACE].yOffset
			End If

			Return h
		End

		Method GetTextHeight:Int(txt:FlxText)
			Return ( (txt._fontHeight + lineGap) * txt._countLines) - lineGap
		End Method

		Method DrawText:Void(txt:FlxText, x:Int, y:Int)
			Local lineIndex:Int = 0, countLines:Int = txt._countLines, line:FlxTextLine

			Local prevChar:Int = 0, xOffset:Int = 0, yOffset:Int = 0
			Local i:Int = 0, l:Int
			Local asc:Int, ac:Char
			Local lineHeight:Int = txt._fontHeight + lineGap

			While (lineIndex < countLines)
				line = txt._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				xOffset = 0

				While (i < l)
					asc = txt._value[i]
					ac = chars[asc]

					If ac <> Null
						If useKerning
							firstKp = kernPairs.Get(prevChar)
							If firstKp <> Null
								secondKp = firstKp.Get(asc)
								If secondKp <> Null
									xOffset += secondKp.amount
								End
							Endif
						Endif

						ac.Draw(image[ac.page], x + xOffset + line.xOffset, y + yOffset)
						xOffset += ac.xAdvance
						prevChar = asc
					Endif

					i += 1
				Wend

				lineIndex += 1
				yOffset += lineHeight
			Wend
		End Method

	End Class

#ElseIf FLX_TEXT_DRIVER = "fontmachine"

	Class FlxBitmapFont Implements Font

		Method New(fontDescriptionFilePath:String)
			Local text:String = LoadString(fontDescriptionFilePath + ".txt")
			if text = "" Then Print "FONT " + fontDescriptionFilePath + " WAS NOT FOUND!!!"
			LoadFontData(text, fontDescriptionFilePath, False)
		End

		Method GetTextWidth:Float(text:FlxText, fromChar:Int = 0, toChar:Int = -1)
			Local twidth:Float
			Local char:Int
			Local lastchar:Int = 0

			If (toChar < 0) toChar = text._value.Length()

			For Local i:Int = fromChar Until toChar
				char = text._value[i]

				If faceChars[char] <> Null Then
					lastchar = char
					twidth = twidth + faceChars[char].drawingMetrics.drawingWidth + Kerning.x
				End If
			Next

			Return twidth - faceChars[lastchar].drawingMetrics.drawingWidth + faceChars[lastchar].drawingMetrics.drawingSize.x
		End Method

		Method GetTextHeight:Float(text:FlxText)
			Return ( (text._fontHeight + Kerning.y) * text._countLines) - Kerning.y
		End

		Method GetFontHeight:Int(txt:FlxText)
			If faceChars[32] = Null Then Return 0
			Return faceChars[32].drawingMetrics.drawingSize.y
		End Method

		Method DrawText:Void(text:FlxText, x:Float, y:Float)
			If (_drawShadow) DrawChars(text, x, y, shadowChars)
			If (_drawBorder) DrawChars(text, x, y, borderChars)
			DrawChars(text, x, y, faceChars)
		End

		Method Kerning:drawingpoint.DrawingPoint() property
			if _kerning = null Then _kerning = New drawingpoint.DrawingPoint
			Return _kerning
		End

		Method Kerning:void(value:drawingpoint.DrawingPoint) property
			_kerning = value
		End

	Private

		Field _drawShadow:Bool = False
		Field _drawBorder:Bool = False
		Field borderChars:BitMapChar[]
		Field faceChars:BitMapChar[]
		Field shadowChars:BitMapChar[]

		Method LoadFontData:Void(Info:String, fontName:String, dynamicLoad:bool)
			if Info.StartsWith("P1") Then
				LoadPacked(Info,fontName,dynamicLoad)
				return
			EndIf
			Local tokenStream:String[] = Info.Split(",")
			local index:Int = 0
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local char:Int = 0
			while index<tokenStream.Length
				'We get char to load:
				Local strChar:String = tokenStream[index]
				if strChar.Trim() = "" Then
					'Print "This is going to fail..."
					index+=1
					Exit
				endif
				char = int(strChar)
				'Print "Loading char: " + char + " at index: " + index
				index+=1

				Local kind:String = tokenStream[index]
				'Print "Found kind= " + kind
				index +=1

				Select kind
					Case "{BR"
						index+=3 '3 control point for future use
						borderChars[char] = New BitMapChar
						borderChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						borderChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						borderChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						borderChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						borderChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad  = False then
							borderChars[char].image = LoadImage(prefixName + "_BORDER_" + char + ".png")
							borderChars[char].image.SetHandle(-borderChars[char].drawingMetrics.drawingOffset.x,-borderChars[char].drawingMetrics.drawingOffset.y)
						Else
							borderChars[char].SetImageResourceName  prefixName + "_BORDER_" + char + ".png"
						endif
						index+=5
						index += 1 ' control point for future use

						_drawBorder = True

					Case "{SH"
						index+=3 '3 control point for future use
						shadowChars[char] = New BitMapChar
						shadowChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						shadowChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						shadowChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						shadowChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						shadowChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						Local filename:String = prefixName + "_SHADOW_" + char + ".png"
						if dynamicLoad  = False then
							shadowChars[char].image = LoadImage(filename)
							shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)
						Else
							shadowChars[char].SetImageResourceName  filename
						endif

						'shadowChars[char].image = LoadImage(filename)
						'shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)

						index+=5
						index += 1 ' control point for future use

						_drawShadow = True

					Case "{FC"
						index+=3 '3 control point for future use
						faceChars[char] = New BitMapChar
						faceChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						faceChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						faceChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						faceChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						faceChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad = False then
							faceChars[char].image = LoadImage(prefixName + "_" + char + ".png")
							faceChars[char].image.SetHandle(-faceChars[char].drawingMetrics.drawingOffset.x,-faceChars[char].drawingMetrics.drawingOffset.y)
						Else
							faceChars[char].SetImageResourceName prefixName + "_" + char + ".png"
						endif
						index+=5
						index+=1 ' control point for future use

					Default
					Print "Error loading font! Char = " + char

				End
			Wend
			borderChars = borderChars[..char+1]
			faceChars = faceChars[..char+1]
			shadowChars = shadowChars[..char+1]
		End

		Field packedImages:Image[]

		Method LoadPacked:Void(info:String, fontName:String, dynamicLoad:bool)

			Local header:String = info[.. info.Find(",")]

			Local separator:String
			Select header
				Case "P1"
					separator = "."
				Case "P1.01"
					separator = "_P_"
			End Select
			info = info[info.Find(",")+1..]
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]
			packedImages = New Image[256]
			Local maxPacked:Int = 0
			Local maxChar:Int = 0

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local charList:string[] = info.Split(";")
			For local chr:String = EachIn charList

				Local chrdata:string[] = chr.Split(",")
				if chrdata.Length() <2 Then Exit
				Local char:bitmapchar.BitMapChar
				Local charIndex:Int = int(chrdata[0])
				if maxChar<charIndex Then maxChar = charIndex

				select chrdata[1]
					Case "B"
						borderChars[charIndex] = New BitMapChar
						char = borderChars[charIndex]
						_drawBorder = True
					Case "F"
						faceChars [charIndex] = New BitMapChar
						char = faceChars[charIndex]
					Case "S"
						shadowChars [charIndex] = New BitMapChar
						char = shadowChars[charIndex]
						_drawShadow = True
				End Select
				char.packedFontIndex = Int(chrdata[2])
				if packedImages[char.packedFontIndex] = null Then
					packedImages[char.packedFontIndex] = LoadImage(prefixName + separator + char.packedFontIndex +  ".png")
					if maxPacked<char.packedFontIndex Then maxPacked = char.packedFontIndex
				endif
				char.packedPosition.x = Int(chrdata[3])
				char.packedPosition.y = Int(chrdata[4])
				char.packedSize.x = Int(chrdata[5])
				char.packedSize.y = Int(chrdata[6])
				char.drawingMetrics.drawingOffset.x = Int(chrdata[8])
				char.drawingMetrics.drawingOffset.y = Int(chrdata[9])
				char.drawingMetrics.drawingSize.x = Int(chrdata[10])
				char.drawingMetrics.drawingSize.y = Int(chrdata[11])
				char.drawingMetrics.drawingWidth = Int(chrdata[12])

			Next

			borderChars = borderChars[..maxChar+1]
			faceChars = faceChars[..maxChar+1]
			shadowChars = shadowChars[..maxChar+1]
			packedImages = packedImages[..maxPacked+1]

		End

		Method DrawChars:Void(text:FlxText, x:Float, y:Float, target:BitMapChar[])
			Local lineIndex:Int = 0, countLines:Int = text._countLines, line:FlxTextLine

			Local drx:Int = x, dry:Int = y
			Local i:Int = 0, l:Int
			Local char:Int

			Local lineHeight:Int = text._fontHeight + Kerning.y
			Local targetLength:Int = target.Length()

			While (lineIndex < countLines)
				line = text._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				drx = x + line.xOffset

				While (i < l)
					char = text._value[i]

					If char >= 0 And char <= targetLength And target[char] <> Null Then
						If target[char].packedFontIndex > 0 Then
								DrawImageRect(packedImages[target[char].packedFontIndex], drx + target[char].drawingMetrics.drawingOffset.x, dry + target[char].drawingMetrics.drawingOffset.y, target[char].packedPosition.x, target[char].packedPosition.y, target[char].packedSize.x, target[char].packedSize.y)
						ElseIf target[char].image <> Null Then
							DrawImage(target[char].image, drx, dry)
						EndIf

						drx += faceChars[char].drawingMetrics.drawingWidth + Kerning.x
					End If

					i += 1
				Wend

				lineIndex += 1
				dry += lineHeight
			Wend
		End Method

		Field _kerning:drawingpoint.DrawingPoint
	End
#End

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxg
Import system.flxcolor
Import system.flxassetsmanager

Private

#If FLX_TEXT_DRIVER = "angelfont"
	Import vendor.angelfont
#ElseIf FLX_TEXT_DRIVER = "fontmachine"
	Import vendor.fontmachine
#End

Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.${FLX_FONT_EXTENSION}"

Public

Class FlxText Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_CENTER:Float = 0.5
	Const ALIGN_RIGHT:Float = 1

	Const SYSTEM_FONT:String = "system"

Private
	Global _FontLoader:FlxFontLoader = New FlxFontLoader()

	Global _FontsManager:FlxResourcesManager<FlxBitmapFont> = New FlxResourcesManager<FlxBitmapFont>()

	Field _shadow:FlxColor

	Field _value:String

	Field _lines:FlxTextLine[]

	Field _countLines:Int

	Field _alignment:Float

	Field _fontFamily:String

	Field _fontSize:Int

	Field _fontHeight:Int

	Field _fontObject:FlxBitmapFont

Public
	Method New(x:Float, y:Float, width:Int = 0, text:String = "")
		Super.New(x, y)

		SetRenderer(Self)
		_shadow = New FlxColor(0)

		Self.width = width
		frameWidth = Self.width
		moves = False

		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

	Method Destroy:Void()
		Local i:Int = 0, l:Int = _lines.Length()

		While (i < l)
			_lines[i] = Null
			i += 1
		Wend

		_fontObject = Null
		_shadow = Null

		Super.Destroy()
	End Method

	Method SetFormat:FlxText(font:String = "", size:Int = 0, color:Int = FlxG.WHITE, alignment:Float = ALIGN_LEFT, shadowColor:Int = 0)
		_alignment = alignment
		_fontFamily = font
		_fontSize = FlxAssetsManager.GetFont(font).GetValidSize(size)

		Self.Color = color
		Shadow = shadowColor

		_ResetFont()
		_ResetHelpers()

		Return Self
	End Method

	Method SetWidth:Void(width:Float)
		Self.width = width
		frameWidth = Self.width
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _value
	End Method

	Method Text:Void(text:String) Property
		_value = text
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_fontSize = FlxAssetsManager.GetFont(_fontFamily).GetValidSize(size)
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _fontSize
	End Method

	Method Font:String() Property
		Return _fontFamily
	End Method

	Method Font:Void(font:String) Property
		_fontFamily = font
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Alignment:Float() Property
		Return _alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		If (_alignment = alignment) Return
		_alignment = alignment
		_ResetAlignment()
	End Method

	Method Shadow:Int() Property
		Return _shadow.argb
	End Method

	Method Shadow:Void(color:Int) Property
		_shadow.SetARGB(color)
	End Method

	Method SetFontShadowEnabled:Void(enabled:Bool)
	#If FLX_TEXT_DRIVER = "angelfont"
		Return
	#ElseIf FLX_TEXT_DRIVER = "fontmachine"
		_fontObject._drawShadow = enabled
	#End
	End Method

	Method SetFontBoderEnabled:Void(enabled:Bool)
	#If FLX_TEXT_DRIVER = "angelfont"
		Return
	#ElseIf FLX_TEXT_DRIVER = "fontmachine"
		_fontObject._drawBorder = enabled
	#End
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._LastDrawingColor
			Local oldAlpha:Int = FlxG._LastDrawingAlpha

			If (_camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_shadow, _camera._color)

				If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
					SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
				End If
			Else
				If (FlxG._LastDrawingColor <> _shadow.argb) Then
					SetColor(_shadow.r, _shadow.g, _shadow.b)
				End If
			End If

			If (_camera.Alpha < 1) Then
				Local _mixedAlpha:Float = _camera.Alpha * _shadow.a

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _shadow.a) Then
					SetAlpha(_shadow.a)
				End If
			End If

			_fontObject.DrawText(Self, x + 1, y + 1)

			_mixedColor.SetRGB(oldColor)
			SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
			SetAlpha(oldAlpha)
		End If

		_fontObject.DrawText(Self, x, y)
	End Method

Private
	Method _ResetHelpers:Void()
		If (_value.Length() > 0) Then
			If (width <> 0) Then
				_ParseText()
			Else
				_countLines = 0
				_AddLine(0, _value.Length(), _fontObject.GetTextWidth(Self, 0, _value.Length()))
				_ResetAlignment()
			End If

			Self.height = _GetHeight()
			frameHeight = Self.height
		Else
			Self.height = 0
			frameHeight = 0
		End If

		Super._ResetHelpers()
	End Method

	Method _ResetFont:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _fontSize

		_fontObject = _FontsManager.GetResource(_fontFamily + _fontSize, _FontLoader)
		_fontHeight = _fontObject.GetFontHeight(Self)
	End Method

	Method _ResetAlignment:Void()
		If (_value.Length() = 0) Return

		For Local line:Int = 0 Until _countLines
			_lines[line].xOffset = (width - _lines[line].width) * _alignment
		Next
	End Method

	Method _GetHeight:Int()
		If (width = 0) Then
			Return _fontHeight
		Else
			Return _fontObject.GetTextHeight(Self)
		End If
	End Method

	Method _ParseText:Void()
		_countLines = 0

		Local prevOffset:Int = 0
		Local offsetN:Int = _value.Find("~n", 0)
		Local offsetR:Int = _value.Find("~r", 0)
		Local offset:Int = 0

		If (offsetN >= 0 And offsetR >= 0) Then
			offset = Min(offsetN, offsetR)
		Else
			offset = Max(offsetN, offsetR)
		End If

		If (offset >= 0) Then
			While (offset >= 0)
				_BuildLines(prevOffset, offset)

				prevOffset = offset + 1

				offsetN = _value.Find("~n", prevOffset)
				offsetR = _value.Find("~r", prevOffset)

				If (offsetN >= 0 And offsetR >= 0) Then
					offset = Min(offsetN, offsetR)
				Else
					offset = Max(offsetN, offsetR)
				End if
			Wend

			_BuildLines(prevOffset, _value.Length())
		Else
			_BuildLines(0, _value.Length())
		End If

		_ResetAlignment()
	End Method

	Method _BuildLines:Void(startPos:Int, endPos:Int)
		Local textWidth:Int = _fontObject.GetTextWidth(Self, startPos, endPos)

		If (width < textWidth) Then
			Local textLength:Int = endPos - startPos

			Local range:Int = Ceil(textLength / Float(Floor(textWidth / Float(width)) + 1))

			Repeat
				range += 1
				textWidth = _fontObject.GetTextWidth(Self, 0, range)
			Until (textWidth >= width)

			Local maxOffset:Int = range
			Local minOffset:Int = 0
			Local offset:Int = maxOffset
			Local tmpOffset:Int = 0
			Local dirty:Bool = False
			Local finalTextWidth:Int = 0
			Local success:Bool

			Repeat
				Repeat
					offset -= 1
					If (offset - minOffset <= 1) Then
						offset = minOffset + 1
						Exit
					End if

					tmpOffset = -1
					For Local i:Int = offset - 1 To minOffset Step - 1
						success = False

						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							tmpOffset = i
							success = True
							Exit
						End If
					Next

					If (tmpOffset < 0) Then
						tmpOffset = _GetMinOffset(minOffset, offset)
					Else
						If (_value[minOffset] = KEY_SPACE Or _value[minOffset] = KEY_TAB) Then
							For Local i:Int = minOffset To offset
								If (_value[i] <> KEY_SPACE And _value[i] <> KEY_TAB) Then
									minOffset = i
									Exit
								End If
							Next
						End If
					End If

					offset = tmpOffset
					textWidth = _fontObject.GetTextWidth(Self, minOffset, offset)
				Until (textWidth <= width)

				dirty = False
				finalTextWidth = _fontObject.GetTextWidth(Self, minOffset)

				If (finalTextWidth > width And textLength - minOffset > 1) Then
					For Local i:Int = minOffset Until offset
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = offset - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							offset -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - minOffset = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, offset, textWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, offset))
					End If

					minOffset = offset
					If (success) minOffset += 1

					maxOffset = minOffset + range
					offset = maxOffset
				Else
					Local l:Int = _value.Length()

					For Local i:Int = minOffset Until l
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = l - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							l -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (l - minOffset <= 0) Exit

					If ( Not dirty) Then
						_AddLine(minOffset, l, finalTextWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, l))
					End If

					Exit
				End If
			Forever
		Else
			_AddLine(startPos, endPos, textWidth)
		End If
	End Method

	Method _GetMinOffset:Int(startPos:Int, endPos:Int)
		Local offset:Int = endPos - startPos

		While (_fontObject.GetTextWidth(Self, startPos, startPos + offset) > width)
			offset -= 1
			If (offset = 1) Return offset
		Wend

		Return startPos + offset
	End Method

	Method _AddLine:FlxTextLine(startPos:Int, endPos:Int, width:Int)
		Local line:FlxTextLine

		If (_countLines = _lines.Length()) Then
			_lines = _lines.Resize(_countLines * 2 + 10)
			line = New FlxTextLine(startPos, endPos, width)
			_lines[_countLines] = line
		Else
			line = _lines[_countLines]
			If (line = Null) Then
				line = New FlxTextLine(startPos, endPos, width)
				_lines[_countLines] = line
			Else
				line.Reset(startPos, endPos, width)
			End If
		End If

		_countLines += 1
		Return line
	End Method

End Class

Private
Class FlxTextLine

	Field xOffset:Float

	Field startPos:Int

	Field endPos:Int

	Field width:Int

	Method New(startPos:Int, endPos:Int, width:Int)
		Reset(startPos, endPos, width)
	End Method

	Method Reset:Void(startPos:Int, endPos:Int, width:Int)
		Self.startPos = startPos
		Self.endPos = endPos
		Self.width = width
	End Method

End Class

Class FlxFontLoader Extends FlxResourceLoader<FlxBitmapFont>

	Field fontFamily:String = FlxText.SYSTEM_FONT
	Field fontSize:Int

	Method Load:FlxBitmapFont(name:String)
		Return New FlxBitmapFont(FlxAssetsManager.GetFont(fontFamily).GetPath(fontSize))
	End Method

End Class

#If FLX_TEXT_DRIVER = "angelfont"
	Class FlxBitmapFont
	Private
		Field image:Image[] = New Image[1]
		Field chars:Char[256]

		Field kernPairs:IntMap<IntMap<KernPair>> = New IntMap<IntMap<KernPair>>
		Global firstKp:IntMap<KernPair>
		Global secondKp:KernPair

		Field iniText:String

		Field xOffset:Int
		Field yOffset:Int

	Public
		Field useKerning:Bool = True

		Field height:Int = 0
		Field heightOffset:Int = 9999

		Field lineGap:Int = 5

		Method New(url:String)
			iniText = LoadString(url+".fnt")
			Local lines:String[] = iniText.Split(String.FromChar(10))
			Local firstLine:String = lines[0]
			If firstLine.Contains("<?xml")
				Local lineList:List<String> = New List<String>(lines)
				lineList.RemoveFirst()
				lines = lineList.ToArray()
				iniText = "~n".Join(lines)
			End

			Local pageCount:Int = 0

			Local config:= LoadConfig(iniText)

			Local nodes:= config.FindNodesByPath("font/chars/char")

			For Local node:= EachIn nodes
				Local id:Int = Int(node.GetAttribute("id"))
				Local page:Int = Int(node.GetAttribute("page"))
				If pageCount < page pageCount = page
				chars[id] = New Char(Int(node.GetAttribute("x")), Int(node.GetAttribute("y")), Int(node.GetAttribute("width")), Int(node.GetAttribute("height")),  Int(node.GetAttribute("xoffset")),  Int(node.GetAttribute("yoffset")),  Int(node.GetAttribute("xadvance")), page)
				Local ch := chars[id]
				If ch.height > Self.height Self.height = ch.height
				If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
			Next

			nodes = config.FindNodesByPath("font/kernings/kerning")

			If (nodes <> Null) Then
				For Local node:= EachIn nodes
					Local first:Int = Int(node.GetAttribute("first"))
					firstKp = kernPairs.Get(first)
					If firstKp = Null
						kernPairs.Add(first, New IntMap<KernPair>)
						firstKp = kernPairs.Get(first)
					End

					Local second:Int = Int(node.GetAttribute("second"))
					firstKp.Add(second, New KernPair(first, second, Int(node.GetAttribute("amount"))))
				End
			Else
				useKerning = False
			End If

			If pageCount = 0
				image[0] = LoadImage(url+".png")
				If image[0] = Null image[0] = LoadImage(url + "_0.png")
			Else
				For Local page:= 0 To pageCount
					If image.Length < page+1 image = image.Resize(page+1)
					image[page] = LoadImage(url+"_"+page+".png")
				End
			End
		End

		Method GetTextWidth:Int(txt:FlxText, startPos:Int = 0, endPos:Int = -1)
			Local prevChar:Int = 0
			Local width:Int = 0

			If (endPos < 0) endPos = txt._value.Length()
			Local asc:Int, ac:Char

			For Local i:= startPos Until endPos
				asc = txt._value[i]
				ac = chars[asc]

				If ac <> Null
					If useKerning
						Local firstKp:= kernPairs.Get(prevChar)
						If firstKp <> Null
							Local secondKp:= firstKp.Get(asc)
							If secondKp <> Null
								xOffset += secondKp.amount
							End
						Endif
					EndIf

					width += ac.xAdvance
					prevChar = asc
				EndIf
			Next

			Return width
		End Method

		Method GetFontHeight:Int(txt:FlxText)
			Local h:Int = 0

			If (txt._value.Length() <> 0) Then
				Local l:Int = txt._value.Length()

				For Local i:= 0 Until l
					Local asc:Int = txt._value[i]
					Local ac:Char = chars[asc]
					If ac.height+ac.yOffset > h h = ac.height+ac.yOffset
				Next
			Else
				h = chars[KEY_SPACE].height + chars[KEY_SPACE].yOffset
			End If

			Return h
		End

		Method GetTextHeight:Int(txt:FlxText)
			Return ( (txt._fontHeight + lineGap) * txt._countLines) - lineGap
		End Method

		Method DrawText:Void(txt:FlxText, x:Int, y:Int)
			Local lineIndex:Int = 0, countLines:Int = txt._countLines, line:FlxTextLine

			Local prevChar:Int = 0, xOffset:Int = 0, yOffset:Int = 0
			Local i:Int = 0, l:Int
			Local asc:Int, ac:Char
			Local lineHeight:Int = txt._fontHeight + lineGap

			While (lineIndex < countLines)
				line = txt._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				xOffset = 0

				While (i < l)
					asc = txt._value[i]
					ac = chars[asc]

					If ac <> Null
						If useKerning
							firstKp = kernPairs.Get(prevChar)
							If firstKp <> Null
								secondKp = firstKp.Get(asc)
								If secondKp <> Null
									xOffset += secondKp.amount
								End
							Endif
						Endif

						ac.Draw(image[ac.page], x + xOffset + line.xOffset, y + yOffset)
						xOffset += ac.xAdvance
						prevChar = asc
					Endif

					i += 1
				Wend

				lineIndex += 1
				yOffset += lineHeight
			Wend
		End Method

	End Class

#ElseIf FLX_TEXT_DRIVER = "fontmachine"

	Class FlxBitmapFont Implements Font

		Method New(fontDescriptionFilePath:String)
			Local text:String = LoadString(fontDescriptionFilePath + ".txt")
			if text = "" Then Print "FONT " + fontDescriptionFilePath + " WAS NOT FOUND!!!"
			LoadFontData(text, fontDescriptionFilePath, False)
		End

		Method GetTextWidth:Float(text:FlxText, fromChar:Int = 0, toChar:Int = -1)
			Local twidth:Float
			Local char:Int
			Local lastchar:Int = 0

			If (toChar < 0) toChar = text._value.Length()

			For Local i:Int = fromChar Until toChar
				char = text._value[i]

				If faceChars[char] <> Null Then
					lastchar = char
					twidth = twidth + faceChars[char].drawingMetrics.drawingWidth + Kerning.x
				End If
			Next

			Return twidth - faceChars[lastchar].drawingMetrics.drawingWidth + faceChars[lastchar].drawingMetrics.drawingSize.x
		End Method

		Method GetTextHeight:Float(text:FlxText)
			Return ( (text._fontHeight + Kerning.y) * text._countLines) - Kerning.y
		End

		Method GetFontHeight:Int(txt:FlxText)
			If faceChars[32] = Null Then Return 0
			Return faceChars[32].drawingMetrics.drawingSize.y
		End Method

		Method DrawText:Void(text:FlxText, x:Float, y:Float)
			If (_drawShadow) DrawChars(text, x, y, shadowChars)
			If (_drawBorder) DrawChars(text, x, y, borderChars)
			DrawChars(text, x, y, faceChars)
		End

		Method Kerning:drawingpoint.DrawingPoint() property
			if _kerning = null Then _kerning = New drawingpoint.DrawingPoint
			Return _kerning
		End

		Method Kerning:void(value:drawingpoint.DrawingPoint) property
			_kerning = value
		End

	Private

		Field _drawShadow:Bool = False
		Field _drawBorder:Bool = False
		Field borderChars:BitMapChar[]
		Field faceChars:BitMapChar[]
		Field shadowChars:BitMapChar[]

		Method LoadFontData:Void(Info:String, fontName:String, dynamicLoad:bool)
			if Info.StartsWith("P1") Then
				LoadPacked(Info,fontName,dynamicLoad)
				return
			EndIf
			Local tokenStream:String[] = Info.Split(",")
			local index:Int = 0
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local char:Int = 0
			while index<tokenStream.Length
				'We get char to load:
				Local strChar:String = tokenStream[index]
				if strChar.Trim() = "" Then
					'Print "This is going to fail..."
					index+=1
					Exit
				endif
				char = int(strChar)
				'Print "Loading char: " + char + " at index: " + index
				index+=1

				Local kind:String = tokenStream[index]
				'Print "Found kind= " + kind
				index +=1

				Select kind
					Case "{BR"
						index+=3 '3 control point for future use
						borderChars[char] = New BitMapChar
						borderChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						borderChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						borderChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						borderChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						borderChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad  = False then
							borderChars[char].image = LoadImage(prefixName + "_BORDER_" + char + ".png")
							borderChars[char].image.SetHandle(-borderChars[char].drawingMetrics.drawingOffset.x,-borderChars[char].drawingMetrics.drawingOffset.y)
						Else
							borderChars[char].SetImageResourceName  prefixName + "_BORDER_" + char + ".png"
						endif
						index+=5
						index += 1 ' control point for future use

						_drawBorder = True

					Case "{SH"
						index+=3 '3 control point for future use
						shadowChars[char] = New BitMapChar
						shadowChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						shadowChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						shadowChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						shadowChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						shadowChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						Local filename:String = prefixName + "_SHADOW_" + char + ".png"
						if dynamicLoad  = False then
							shadowChars[char].image = LoadImage(filename)
							shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)
						Else
							shadowChars[char].SetImageResourceName  filename
						endif

						'shadowChars[char].image = LoadImage(filename)
						'shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)

						index+=5
						index += 1 ' control point for future use

						_drawShadow = True

					Case "{FC"
						index+=3 '3 control point for future use
						faceChars[char] = New BitMapChar
						faceChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						faceChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						faceChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						faceChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						faceChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad = False then
							faceChars[char].image = LoadImage(prefixName + "_" + char + ".png")
							faceChars[char].image.SetHandle(-faceChars[char].drawingMetrics.drawingOffset.x,-faceChars[char].drawingMetrics.drawingOffset.y)
						Else
							faceChars[char].SetImageResourceName prefixName + "_" + char + ".png"
						endif
						index+=5
						index+=1 ' control point for future use

					Default
					Print "Error loading font! Char = " + char

				End
			Wend
			borderChars = borderChars[..char+1]
			faceChars = faceChars[..char+1]
			shadowChars = shadowChars[..char+1]
		End

		Field packedImages:Image[]

		Method LoadPacked:Void(info:String, fontName:String, dynamicLoad:bool)

			Local header:String = info[.. info.Find(",")]

			Local separator:String
			Select header
				Case "P1"
					separator = "."
				Case "P1.01"
					separator = "_P_"
			End Select
			info = info[info.Find(",")+1..]
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]
			packedImages = New Image[256]
			Local maxPacked:Int = 0
			Local maxChar:Int = 0

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local charList:string[] = info.Split(";")
			For local chr:String = EachIn charList

				Local chrdata:string[] = chr.Split(",")
				if chrdata.Length() <2 Then Exit
				Local char:bitmapchar.BitMapChar
				Local charIndex:Int = int(chrdata[0])
				if maxChar<charIndex Then maxChar = charIndex

				select chrdata[1]
					Case "B"
						borderChars[charIndex] = New BitMapChar
						char = borderChars[charIndex]
						_drawBorder = True
					Case "F"
						faceChars [charIndex] = New BitMapChar
						char = faceChars[charIndex]
					Case "S"
						shadowChars [charIndex] = New BitMapChar
						char = shadowChars[charIndex]
						_drawShadow = True
				End Select
				char.packedFontIndex = Int(chrdata[2])
				if packedImages[char.packedFontIndex] = null Then
					packedImages[char.packedFontIndex] = LoadImage(prefixName + separator + char.packedFontIndex +  ".png")
					if maxPacked<char.packedFontIndex Then maxPacked = char.packedFontIndex
				endif
				char.packedPosition.x = Int(chrdata[3])
				char.packedPosition.y = Int(chrdata[4])
				char.packedSize.x = Int(chrdata[5])
				char.packedSize.y = Int(chrdata[6])
				char.drawingMetrics.drawingOffset.x = Int(chrdata[8])
				char.drawingMetrics.drawingOffset.y = Int(chrdata[9])
				char.drawingMetrics.drawingSize.x = Int(chrdata[10])
				char.drawingMetrics.drawingSize.y = Int(chrdata[11])
				char.drawingMetrics.drawingWidth = Int(chrdata[12])

			Next

			borderChars = borderChars[..maxChar+1]
			faceChars = faceChars[..maxChar+1]
			shadowChars = shadowChars[..maxChar+1]
			packedImages = packedImages[..maxPacked+1]

		End

		Method DrawChars:Void(text:FlxText, x:Float, y:Float, target:BitMapChar[])
			Local lineIndex:Int = 0, countLines:Int = text._countLines, line:FlxTextLine

			Local drx:Int = x, dry:Int = y
			Local i:Int = 0, l:Int
			Local char:Int

			Local lineHeight:Int = text._fontHeight + Kerning.y
			Local targetLength:Int = target.Length()

			While (lineIndex < countLines)
				line = text._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				drx = x + line.xOffset

				While (i < l)
					char = text._value[i]

					If char >= 0 And char <= targetLength And target[char] <> Null Then
						If target[char].packedFontIndex > 0 Then
								DrawImageRect(packedImages[target[char].packedFontIndex], drx + target[char].drawingMetrics.drawingOffset.x, dry + target[char].drawingMetrics.drawingOffset.y, target[char].packedPosition.x, target[char].packedPosition.y, target[char].packedSize.x, target[char].packedSize.y)
						ElseIf target[char].image <> Null Then
							DrawImage(target[char].image, drx, dry)
						EndIf

						drx += faceChars[char].drawingMetrics.drawingWidth + Kerning.x
					End If

					i += 1
				Wend

				lineIndex += 1
				dry += lineHeight
			Wend
		End Method

		Field _kerning:drawingpoint.DrawingPoint
	End
#End