#12 Oops! FlxGroup.GetRandom was unsafe

#12 Oops! FlxGroup.GetRandom was unsafe

#rem
	header:This module contains the FlxGroup class
#end
Strict

Import flxextern
Import flxbasic
Import flxobject
Import system.flxarray

#Rem
summary:This is an organizational class that can update and render a bunch of FlxBasics.
[i][b]NOTE:[/b] Although FlxGroup extends [a flxbasic.monkey.html]FlxBasic[/a], it will not automatically add itself to the global collisions quad tree, it will only add its members.[/i]
#End
Class FlxGroup Extends FlxBasic

	Global ClassObject:FlxClass = New FlxGroupClass()

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in ascending order.
	#End
	Const ASCENDING:Bool = False

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in descending order.
	#End
	Const DESCENDING:Bool = True

Private
	Field _maxSize:Int

	Field _marker:Int

	Field _length:Int

	Field _members:FlxBasic[]

	Field _sortComparator:FlxBasicComparator

	Field _sortDescending:Bool

Public
	#Rem
	summary:Constructor
	Params:
	[list]
	[*]maxSize:Int - the maximum capacity of this group.
	[/list]
	#End
	Method New(maxSize:Int = 0)
		Super.New()
		_maxSize = maxSize
		_marker = 0
		_length = 0
		_cameras = Null
	End Method

	#Rem
	summary:The number of entries in the members array.
	For performance and safety you should check this variable instead of members.length unless you really know what you're doing!
	#End
	Method Length:Int() Property
		Return _length
	End Method

	#Rem
	summary:See detail.
	Array of all the [a flxbasic.monkey.html]FlxBasic[/a]s that exist in this group.
	#End
	Method Members:FlxBasic[]() Property
		Return _members
	End Method

	#Rem
	summary:Override this function to handle any deleting or "shutdown" type operations you might need,such as removing traditional Monkey/Mojo children like Images objects.
	#End
	Method Destroy:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_length = 0
		_members = _members.Resize(_length)
		_sortComparator = Null
		_cameras = Null
	End Method

	#Rem
	summary:Just making sure we don't increment the active objects count.
	#End
	Method PreUpdate:Void()

	End Method

	#Rem
	summary:Automatically goes through and calls update on everything you added.
	#End
	Method Update:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.active) Then
				basic.PreUpdate()
				basic.Update()
				basic.PostUpdate()
			End If
			i+=1
		Wend
	End Method

	#Rem
	summary:Automatically goes through and calls render on everything you added.
	#End
	Method Draw:Void()
		If (_cameras <> Null And Not _cameras.Contains(FlxG._CurrentCamera.ID)) Return

		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.visible) basic.Draw()
			i+=1
		Wend
	End Method

	#Rem
	summary:The maximum capacity of this group.  Default is 0, meaning no max capacity, and the group can just grow.
	#End
	Method MaxSize:Int() Property
		Return _maxSize
	End Method

	Method MaxSize:Void(size:Int) Property
		_maxSize = size

		If (_marker >= _maxSize) _marker = 0
		If (_maxSize = 0 Or _maxSize >= _length) Return

		Local basic:FlxBasic
		Local i:Int = _maxSize
		Local l:Int = _length

		While(i < l)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_members = _members.Resize(_maxSize)
		_length = _maxSize
	End Method

	#Rem
	summary:See details.
	Adds a new [a flxbasic.monkey.html]FlxBasic[/a] subclass ([a flxbasic.monkey.html]FlxBasic[/a], [a flxsprite.monkey.html]FlxSprite[/a], Enemy, etc) to the group. FlxGroup will try to replace a null member of the array first. Failing that, FlxGroup will add it to the end of the member array, assuming there is room for it, and doubling the size of the array if necessary.

	[b]WARNING[/b]: If the group has a maxSize that has already been met, the object will NOT be added to the group!

	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - the object you want to add to the group.
	[/list]
	Return the same [a flxbasic.monkey.html]FlxBasic[/a] object that was passed in.
	#End
	Method Add:FlxBasic(object:FlxBasic)
		If (_IndexOf(object) >= 0) Return object

		Local i:Int = 0

		While (i < _length)
			If (_members[i] = Null) Then
				_members[i] = object
				If (i >= _length) _length = i+1
				Return object
			End If
			i+=1
		Wend

		If (_maxSize > 0) Then
			If (_length = _members.Length()) Then
				If (_length >= _maxSize) Then
					Return object
				ElseIf (_length*2 + 10 <= _maxSize)
					_members = _members.Resize(_length*2 + 10)
				Else
					_members = _members.Resize(_maxSize)
				End If
			End if
		Else
			If (_length = _members.Length()) Then
				_members = _members.Resize(_length*2 + 10)
			End If
		End If

		_members[i] = object
		_length+=1

		Return object
	End Method

	#Rem
	summary:Recycling is designed to help you reuse game objects without always re-allocating or "newing" them.
	If you specified a maximum size for this group (like in [a flxemitter.monkey.html]FlxEmitter[/a],then recycle will employ what we're calling "rotating" recycling. Recycle() will first check to see if the group is at capacity yet. If group is not yet at capacity, Recycle() returns a new object. If the group IS at capacity, then Recycle() just returns the next object in line

	If you did NOT specify a maximum size for this group, then Recycle() will employ what we're calling "grow-style" recycling. Recycle() will return either the first object with exists = false, or, finding none, add a new object to the array, doubling the size of the array if necessary.

	[b]WARNING:[/b] If this function needs to create a new object, and no object class was provided, it will return null instead of a valid object!

	Params:
	[list]
	[*]objectClass:FlxClass - the type objectClass implements FlxClass you want to recycle (e.g. FlxSpriteClass, EvilRobotClass, etc). Do NOT "new" the class in the parameter!
	[/list]
	Return a reference to the object that was created. Don't forget to cast it back to the objectClass you want (e.g. myObject = myObjectClass(myGroup.recycle(myObjectClassClass))).
	#End
	Method Recycle:FlxBasic(objectClass:FlxClass = null)
		If (_maxSize > 0) Then
			If (_length < _maxSize) Then
				If (objectClass = Null) Return Null
				Return Add(FlxBasic(objectClass.CreateInstance()))
			Else
				Local basic:FlxBasic = _members[_marker]
				_marker+=1
				If (_marker >= _maxSize) _marker = 0
				Return basic
			End If
		Else
			Local basic:FlxBasic = GetFirstAvailable(objectClass)
			If (basic <> Null) Return basic
			If (objectClass = Null) Return Null
			Return Add(FlxBasic(objectClass.CreateInstance()))
		End If
	End Method

	#Rem
	summary:Removes an object from the group.
	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - The [a flxbasic.monkey.html]FlxBasic[/a] you want to remove.
	[*]splice:Bool - whether the object should be cut from the array entirely or not.
	[/list]
	Return the removed object.
	#End
	Method Remove:FlxBasic(object:FlxBasic, splice:Bool = False)
		Local index:Int = _IndexOf(object)

		If (index < 0) Return Null

		If (splice) Then
			Local i:Int = index
			Local l:Int = _length

			While(i < _length - 1)
				_members[i] = _members[i+1]
				i+=1
			Wend

			_length-=1
		Else
			_members[index] = Null
		End If

		Return object
	End Method

	#Rem
	summary:Replaces an existing [a flxbasic.monkey.html]FlxBasic[/a] with a new one.
	Params:
	[list]
	[*][a flxbasic.monkey.html]oldObject:FlxBasic[/a] - the object you want to replace.
	[*][a flxbasic.monkey.html]newObject:FlxBasic[/a] - the new object you want to use instead.
	[/list]
	Return the new object.
	#End
	Method Replace:FlxBasic(oldObject:FlxBasic, newObject:FlxBasic)
		Local index:Int  = _IndexOf(oldObject)
		If (index < 0) Return Null

		_members[index] = newObject
		Return newObject
	End Method

	#Rem
	summary:Call this function to sort the group according to a particular value and order.
	For example, to sort game objects for Zelda-style overlaps you might call myGroup.Sort(FlxObject.YComparator, FlxGroup.ASCENDING) at the bottom of your [a flxstate.monkey.html]FlxState.Update()[/a] override. To sort all existing objects after a big explosion or bomb attack, you might call myGroup.Sort(FlxBasic.ExistsComparator, FlxGroup.DESCENDING)
	Params:
	[list]
	[*]comparator:FlxBasicComparator - The FlxBasicComparator you want to sort on. Default value is FlxObject.YComparator.
	[*]order:Bool - A FlxGroup constant that defines the sort order. Possible values are ASCENDING and DESCENDING. Default value is ASCENDING.
	[/list]
	Return the new object.
	#End
	Method Sort:Void(comparator:FlxBasicComparator = FlxObject.YComparator, order:Bool = ASCENDING)
		_sortComparator = comparator
		_sortDescending = order
		_QSort(0, _length - 1)
	End Method

	Method SetAll:Void(setter:FlxBasicSetter, value:Object, recurse:Bool = True)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) Then
					FlxGroup(basic).SetAll(setter, value, recurse)
				Else
					setter.Set(basic, value)
				End If
			End If
			i+=1
		Wend
	End Method

	Method CallAll:Void(invoker:FlxBasicInvoker, recurse:Bool = True)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) Then
					FlxGroup(basic).CallAll(invoker, recurse)
				Else
					invoker.Invoke(basic)
				End If
			End If
			i+=1
		Wend
	End Method

	Method GetFirstAvailable:FlxBasic(objectClass:FlxClass = null)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.exists And
					(objectClass = Null Or objectClass.InstanceOf(basic))) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstNull:Int()
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = Null) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method GetFirstExtant:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstAlive:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstDead:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method CountLiving:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (basic.exists And basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method CountDead:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (Not basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method GetRandom:FlxBasic(startIndex:Int = 0, length:Int = 0)
		If (length = 0) length = _length
		Return FlxArray<FlxBasic>.GetSafeRandom(_members, startIndex, length)
	End Method

	Method Clear:Void()
		_length = 0
	End Method

	Method Kill:Void()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) basic.Kill()
			i+=1
		Wend

		Super.Kill()
	End Method

	Method ObjectEnumerator:Enumerator()
		Return New Enumerator(Self)
	End

	Method ToString:String()
		Return "FlxGroup"
	End Method

Private

	Method _IndexOf:Int(object:FlxBasic)
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = object) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method _QSort:Void(left:Int, right:Int)
		If (right > left) Then
			Local pivot:Int = left + (right-left)/2
			Local newPivot:Int = _DoSort(left, right, pivot)

			_QSort(left, newPivot - 1)
			_QSort(newPivot + 1, right)
		End If
	End Method

	Method _DoSort:Int(left:Int, right:Int, pivot:Int)
		Local basic:FlxBasic = _members[pivot]

		_members[pivot] = _members[right]
		_members[right] = basic

		Local store:Int = left
		Local basicToCompare:FlxBasic
		Local i:Int = left

		While (i < right)
			basicToCompare = _members[i]

			If (_sortDescending) Then
				If (_sortComparator.Compare(basicToCompare, basic) >= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			Else
				If (_sortComparator.Compare(basicToCompare, basic) <= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			End If

			i+=1
		Wend

		basic = _members[store]
		_members[store] = _members[right]
		_members[right] = basic
		Return store
	End Method

End Class

Class Enumerator

	Method New(group:FlxGroup)
		_group = group
	End

	Method HasNext:Bool()
		Return _index < _group.Length
	End

	Method NextObject:FlxBasic()
		_index+=1
		Return _group.Members[_index-1]
	End

Private

	Field _group:FlxGroup
	Field _index:Int

End

Private
Class FlxGroupClass Implements FlxClass

	Method CreateInstance:Object()
		Return New FlxGroup()
	End Method

	Method InstanceOf:Bool(object:Object)
		Return FlxGroup(object) <> Null
	End Method

End Class

#Rem
footer:Flixel is an open source game-making library that is completely free for personal or commercial use.
[quote]Copyright: Flixel - 2009-2011 Adam 'Atomic' Saltsman
Copyright: Monkey port - 2011 Arthur 'devolonter' Bikmullin

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

NOTE FROM THE AUTHOR: As far as I know, you only have to include
this license if you are redistributing source code that includes
the Flixel library.  There is no need (or way, afaik) to include
it in your compiled flash games and apps![/quote]
#End

#rem
	header:This module contains the FlxGroup class
#end
Strict

Import flxextern
Import flxbasic
Import flxobject
Import system.flxarray

#Rem
summary:This is an organizational class that can update and render a bunch of FlxBasics.
[i][b]NOTE:[/b] Although FlxGroup extends [a flxbasic.monkey.html]FlxBasic[/a], it will not automatically add itself to the global collisions quad tree, it will only add its members.[/i]
#End
Class FlxGroup Extends FlxBasic

	Global ClassObject:FlxClass = New FlxGroupClass()

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in ascending order.
	#End
	Const ASCENDING:Bool = False

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in descending order.
	#End
	Const DESCENDING:Bool = True

Private
	Field _maxSize:Int

	Field _marker:Int

	Field _length:Int

	Field _members:FlxBasic[]

	Field _sortComparator:FlxBasicComparator

	Field _sortDescending:Bool

Public
	#Rem
	summary:Constructor
	Params:
	[list]
	[*]maxSize:Int - the maximum capacity of this group.
	[/list]
	#End
	Method New(maxSize:Int = 0)
		Super.New()
		_maxSize = maxSize
		_marker = 0
		_length = 0
		_cameras = Null
	End Method

	#Rem
	summary:The number of entries in the members array.
	For performance and safety you should check this variable instead of members.length unless you really know what you're doing!
	#End
	Method Length:Int() Property
		Return _length
	End Method

	#Rem
	summary:See detail.
	Array of all the [a flxbasic.monkey.html]FlxBasic[/a]s that exist in this group.
	#End
	Method Members:FlxBasic[]() Property
		Return _members
	End Method

	#Rem
	summary:Override this function to handle any deleting or "shutdown" type operations you might need,such as removing traditional Monkey/Mojo children like Images objects.
	#End
	Method Destroy:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_length = 0
		_members = _members.Resize(_length)
		_sortComparator = Null
		_cameras = Null
	End Method

	#Rem
	summary:Just making sure we don't increment the active objects count.
	#End
	Method PreUpdate:Void()

	End Method

	#Rem
	summary:Automatically goes through and calls update on everything you added.
	#End
	Method Update:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.active) Then
				basic.PreUpdate()
				basic.Update()
				basic.PostUpdate()
			End If
			i+=1
		Wend
	End Method

	#Rem
	summary:Automatically goes through and calls render on everything you added.
	#End
	Method Draw:Void()
		If (_cameras <> Null And Not _cameras.Contains(FlxG._CurrentCamera.ID)) Return

		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.visible) basic.Draw()
			i+=1
		Wend
	End Method

	#Rem
	summary:The maximum capacity of this group.  Default is 0, meaning no max capacity, and the group can just grow.
	#End
	Method MaxSize:Int() Property
		Return _maxSize
	End Method

	Method MaxSize:Void(size:Int) Property
		_maxSize = size

		If (_marker >= _maxSize) _marker = 0
		If (_maxSize = 0 Or _maxSize >= _length) Return

		Local basic:FlxBasic
		Local i:Int = _maxSize
		Local l:Int = _length

		While(i < l)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_members = _members.Resize(_maxSize)
		_length = _maxSize
	End Method

	#Rem
	summary:See details.
	Adds a new [a flxbasic.monkey.html]FlxBasic[/a] subclass ([a flxbasic.monkey.html]FlxBasic[/a], [a flxsprite.monkey.html]FlxSprite[/a], Enemy, etc) to the group. FlxGroup will try to replace a null member of the array first. Failing that, FlxGroup will add it to the end of the member array, assuming there is room for it, and doubling the size of the array if necessary.

	[b]WARNING[/b]: If the group has a maxSize that has already been met, the object will NOT be added to the group!

	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - the object you want to add to the group.
	[/list]
	Return the same [a flxbasic.monkey.html]FlxBasic[/a] object that was passed in.
	#End
	Method Add:FlxBasic(object:FlxBasic)
		If (_IndexOf(object) >= 0) Return object

		Local i:Int = 0

		While (i < _length)
			If (_members[i] = Null) Then
				_members[i] = object
				If (i >= _length) _length = i+1
				Return object
			End If
			i+=1
		Wend

		If (_maxSize > 0) Then
			If (_length = _members.Length()) Then
				If (_length >= _maxSize) Then
					Return object
				ElseIf (_length*2 + 10 <= _maxSize)
					_members = _members.Resize(_length*2 + 10)
				Else
					_members = _members.Resize(_maxSize)
				End If
			End if
		Else
			If (_length = _members.Length()) Then
				_members = _members.Resize(_length*2 + 10)
			End If
		End If

		_members[i] = object
		_length+=1

		Return object
	End Method

	#Rem
	summary:Recycling is designed to help you reuse game objects without always re-allocating or "newing" them.
	If you specified a maximum size for this group (like in [a flxemitter.monkey.html]FlxEmitter[/a],then recycle will employ what we're calling "rotating" recycling. Recycle() will first check to see if the group is at capacity yet. If group is not yet at capacity, Recycle() returns a new object. If the group IS at capacity, then Recycle() just returns the next object in line

	If you did NOT specify a maximum size for this group, then Recycle() will employ what we're calling "grow-style" recycling. Recycle() will return either the first object with exists = false, or, finding none, add a new object to the array, doubling the size of the array if necessary.

	[b]WARNING:[/b] If this function needs to create a new object, and no object class was provided, it will return null instead of a valid object!

	Params:
	[list]
	[*]objectClass:FlxClass - the type objectClass implements FlxClass you want to recycle (e.g. FlxSpriteClass, EvilRobotClass, etc). Do NOT "new" the class in the parameter!
	[/list]
	Return a reference to the object that was created. Don't forget to cast it back to the objectClass you want (e.g. myObject = myObjectClass(myGroup.recycle(myObjectClassClass))).
	#End
	Method Recycle:FlxBasic(objectClass:FlxClass = null)
		If (_maxSize > 0) Then
			If (_length < _maxSize) Then
				If (objectClass = Null) Return Null
				Return Add(FlxBasic(objectClass.CreateInstance()))
			Else
				Local basic:FlxBasic = _members[_marker]
				_marker+=1
				If (_marker >= _maxSize) _marker = 0
				Return basic
			End If
		Else
			Local basic:FlxBasic = GetFirstAvailable(objectClass)
			If (basic <> Null) Return basic
			If (objectClass = Null) Return Null
			Return Add(FlxBasic(objectClass.CreateInstance()))
		End If
	End Method

	#Rem
	summary:Removes an object from the group.
	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - The [a flxbasic.monkey.html]FlxBasic[/a] you want to remove.
	[*]splice:Bool - whether the object should be cut from the array entirely or not.
	[/list]
	Return the removed object.
	#End
	Method Remove:FlxBasic(object:FlxBasic, splice:Bool = False)
		Local index:Int = _IndexOf(object)

		If (index < 0) Return Null

		If (splice) Then
			Local i:Int = index
			Local l:Int = _length

			While(i < _length - 1)
				_members[i] = _members[i+1]
				i+=1
			Wend

			_length-=1
		Else
			_members[index] = Null
		End If

		Return object
	End Method

	#Rem
	summary:Replaces an existing [a flxbasic.monkey.html]FlxBasic[/a] with a new one.
	Params:
	[list]
	[*][a flxbasic.monkey.html]oldObject:FlxBasic[/a] - the object you want to replace.
	[*][a flxbasic.monkey.html]newObject:FlxBasic[/a] - the new object you want to use instead.
	[/list]
	Return the new object.
	#End
	Method Replace:FlxBasic(oldObject:FlxBasic, newObject:FlxBasic)
		Local index:Int  = _IndexOf(oldObject)
		If (index < 0) Return Null

		_members[index] = newObject
		Return newObject
	End Method

	#Rem
	summary:Call this function to sort the group according to a particular value and order.
	For example, to sort game objects for Zelda-style overlaps you might call myGroup.Sort(FlxObject.YComparator, FlxGroup.ASCENDING) at the bottom of your [a flxstate.monkey.html]FlxState.Update()[/a] override. To sort all existing objects after a big explosion or bomb attack, you might call myGroup.Sort(FlxBasic.ExistsComparator, FlxGroup.DESCENDING)
	Params:
	[list]
	[*]comparator:FlxBasicComparator - The FlxBasicComparator you want to sort on. Default value is FlxObject.YComparator.
	[*]order:Bool - A FlxGroup constant that defines the sort order. Possible values are ASCENDING and DESCENDING. Default value is ASCENDING.
	[/list]
	Return the new object.
	#End
	Method Sort:Void(comparator:FlxBasicComparator = FlxObject.YComparator, order:Bool = ASCENDING)
		_sortComparator = comparator
		_sortDescending = order
		_QSort(0, _length - 1)
	End Method

	Method SetAll:Void(setter:FlxBasicSetter, value:Object, recurse:Bool = True)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) Then
					FlxGroup(basic).SetAll(setter, value, recurse)
				Else
					setter.Set(basic, value)
				End If
			End If
			i+=1
		Wend
	End Method

	Method CallAll:Void(invoker:FlxBasicInvoker, recurse:Bool = True)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) Then
					FlxGroup(basic).CallAll(invoker, recurse)
				Else
					invoker.Invoke(basic)
				End If
			End If
			i+=1
		Wend
	End Method

	Method GetFirstAvailable:FlxBasic(objectClass:FlxClass = null)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.exists And
					(objectClass = Null Or objectClass.InstanceOf(basic))) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstNull:Int()
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = Null) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method GetFirstExtant:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstAlive:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstDead:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method CountLiving:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (basic.exists And basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method CountDead:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (Not basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method GetRandom:FlxBasic(startIndex:Int = 0, length:Int = 0)
		If (length = 0) length = _length
		Return FlxArray<FlxBasic>.GetRandom(_members, startIndex, length)
	End Method

	Method Clear:Void()
		_length = 0
	End Method

	Method Kill:Void()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) basic.Kill()
			i+=1
		Wend

		Super.Kill()
	End Method

	Method ObjectEnumerator:Enumerator()
		Return New Enumerator(Self)
	End

	Method ToString:String()
		Return "FlxGroup"
	End Method

Private

	Method _IndexOf:Int(object:FlxBasic)
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = object) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method _QSort:Void(left:Int, right:Int)
		If (right > left) Then
			Local pivot:Int = left + (right-left)/2
			Local newPivot:Int = _DoSort(left, right, pivot)

			_QSort(left, newPivot - 1)
			_QSort(newPivot + 1, right)
		End If
	End Method

	Method _DoSort:Int(left:Int, right:Int, pivot:Int)
		Local basic:FlxBasic = _members[pivot]

		_members[pivot] = _members[right]
		_members[right] = basic

		Local store:Int = left
		Local basicToCompare:FlxBasic
		Local i:Int = left

		While (i < right)
			basicToCompare = _members[i]

			If (_sortDescending) Then
				If (_sortComparator.Compare(basicToCompare, basic) >= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			Else
				If (_sortComparator.Compare(basicToCompare, basic) <= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			End If

			i+=1
		Wend

		basic = _members[store]
		_members[store] = _members[right]
		_members[right] = basic
		Return store
	End Method

End Class

Class Enumerator

	Method New(group:FlxGroup)
		_group = group
	End

	Method HasNext:Bool()
		Return _index < _group.Length
	End

	Method NextObject:FlxBasic()
		_index+=1
		Return _group.Members[_index-1]
	End

Private

	Field _group:FlxGroup
	Field _index:Int

End

Private
Class FlxGroupClass Implements FlxClass

	Method CreateInstance:Object()
		Return New FlxGroup()
	End Method

	Method InstanceOf:Bool(object:Object)
		Return FlxGroup(object) <> Null
	End Method

End Class

#Rem
footer:Flixel is an open source game-making library that is completely free for personal or commercial use.
[quote]Copyright: Flixel - 2009-2011 Adam 'Atomic' Saltsman
Copyright: Monkey port - 2011 Arthur 'devolonter' Bikmullin

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

NOTE FROM THE AUTHOR: As far as I know, you only have to include
this license if you are redistributing source code that includes
the Flixel library.  There is no need (or way, afaik) to include
it in your compiled flash games and apps![/quote]
#End


Added max and min to stop the fling items going to far

#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#End

#Rem
Header: Provides the SnapFlingStack class.
#End

Strict

Import diddy

Class SnapFlingStack Extends DiddyStack<SnapFlingObject>
	Field selected:SnapFlingObject
	Field endPositionX:Int = SCREEN_WIDTH2
	Field timerSpeed:Float = 0.04
	Field maxX:Int = SCREEN_WIDTH
	Field minX:Int = 0
	Field farLeftItem:SnapFlingObject
	Field farLeftX:Int = SCREEN_WIDTH
	Field farRightItem:SnapFlingObject
	Field farRightX:Int = 0

	Method Update:Void()
		If farLeftItem = Null
			For Local i:SnapFlingObject = EachIn Self
				If i.GetX() < farLeftX
					farLeftX = i.GetX()
					farLeftItem = i
				End
			Next
		End
		If farRightItem = Null
			For Local i:SnapFlingObject = EachIn Self
				If i.GetX() > farRightX
					farRightX = i.GetX()
					farRightItem = i
				End
			Next
		End

		If farRightItem.GetX() < minX Or farLeftItem.GetX() > maxX
			For Local i:SnapFlingObject = EachIn Self
				i.SetDx(i.GetDx() * 0.5)
			Next
		End

		For Local i:SnapFlingObject = EachIn Self
			i.Update()
			If i <> selected
				' slow down the objects
				i.SetDx(i.GetDx() * 0.8)
				' stop run away floats
				If (i.GetDx() <> 0 and Abs(i.GetDx()) < 0.3) Then
					i.SetDx(0)
					GetSelectedObject()
				End

				i.MoveByXY(i.GetDx() * dt.delta, 0)
			End
		Next
		If selected
			If selected.GetTimer() <> 1

				If selected.GetTimer() < 1
					Local t:Float = selected.GetTimer()
					t += selected.GetTimerSpeed() * dt.delta
					selected.SetTimer(t)

					Local mx:Float = BackEaseOutTween(selected.GetOx(), selected.GetEx(), selected.GetTimer())

					Local selectedDiff:Float = mx - selected.GetX()

					For Local i:SnapFlingObject = EachIn Self
						If i <> selected
							i.MoveByXY(selectedDiff, 0)
						End
					Next
					selected.MoveToXY(mx, selected.GetY())
				Else
					selected.SetTimer(1)
				End
			End
		End
	End

	Method OnTouchHit:Void(x:Int, y:Int, pointer:Int)
		For Local i:SnapFlingObject = EachIn Self
			i.SetDx(0)
			i.SetDy(0)
			selected = Null
		End
	End

	Method OnTouchFling:Void(releaseX:Int, releaseY:Int, velocityX:Float, velocityY:Float, velocitySpeed:Float, pointer:Int)
		For Local i:SnapFlingObject = EachIn Self
			i.SetDx(velocityX / 100)
			selected = Null
		End
	End

	Method OnTouchDragged:Void(x:Int, y:Int, dx:Int, dy:Int, pointer:Int)
		For Local i:SnapFlingObject = EachIn Self
			i.SetDx(dx)
			selected = Null
		End
	End

	Method OnTouchReleased:Void(x:Int, y:Int, pointer:Int)
		GetSelectedObject()
	End

	Method GetSelectedObject:Void()
		' find the object most out of view percentage
		Local oldPercentage:Float = 0
		For Local i:SnapFlingObject = EachIn Self
			If i.GetPercentageOnScreen() < oldPercentage
				oldPercentage = i.GetPercentageOnScreen()
			End
		Next

		' find the object which is the most in view
		Local found:Bool = False
		For Local i:SnapFlingObject = EachIn Self
			If i.GetDx() = 0
				If i.GetPercentageOnScreen() > oldPercentage
					oldPercentage = i.GetPercentageOnScreen()
					selected = i
					found = True
				End
			End
		Next
		If found
			If selected
				selected.SetEx(endPositionX)
				selected.SetOx(selected.GetX())
				selected.SetTimer(0)
				selected.SetTimerSpeed(timerSpeed)
			End
		End
	End

	Method Render:Void()
		For Local i:SnapFlingObject = EachIn Self
			i.Render()
		End
	End
End

Interface SnapFlingObject
	Method MoveByXY:Void(dx:Float, dy:Float)
	Method MoveToXY:Void(x:Float, y:Float)
	Method GetPercentageOnScreen:Float()
	Method Update:Void()
	Method Render:Void()

	' getters / setters
	Method GetX:Float()
	Method GetY:Float()
	Method GetDx:Float()
	Method GetDy:Float()
	Method SetX:Void(val:Float)
	Method SetY:Void(val:Float)
	Method SetDx:Void(val:Float)
	Method SetDy:Void(val:Float)
	Method GetOx:Float()
	Method SetOx:Void(val:Float)
	Method GetEx:Float()
	Method SetEx:Void(val:Float)
	Method GetTimer:Float()
	Method SetTimer:Void(val:Float)
	Method GetTimerSpeed:Float()
	Method SetTimerSpeed:Void(val:Float)
End

#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#End

#Rem
Header: Provides the SnapFlingStack class.
#End

Strict

Import diddy

Class SnapFlingStack Extends DiddyStack<SnapFlingObject>
	Field selected:SnapFlingObject
	Field endPositionX:Int = SCREEN_WIDTH2
	Field timerSpeed:Float = 0.04

	Method Update:Void()
		For Local i:SnapFlingObject = EachIn Self
			i.Update()
			If i <> selected
				' slow down the objects
				i.SetDx(i.GetDx() * 0.95)
				' stop run away floats
				If (i.GetDx() <> 0 and Abs(i.GetDx()) < 0.3) Then
					i.SetDx(0)
					GetSelectedObject()
				End
				i.MoveByXY(i.GetDx() * dt.delta, 0)
			End
		Next
		If selected
			If selected.GetTimer() <> 1
				Local mx:Float = BackEaseOutTween(selected.GetOx(), selected.GetEx(), selected.GetTimer())

				Local selectedDiff:Float = mx - selected.GetX()

				For Local i:SnapFlingObject = EachIn Self
					If i <> selected
						i.MoveByXY(selectedDiff, 0)
					End
				Next
				selected.MoveToXY(mx, selected.GetY())

				If selected.GetTimer() < 1
					Local t:Float = selected.GetTimer()
					t += selected.GetTimerSpeed() * dt.delta
					selected.SetTimer(t)
				Else
					selected.SetTimer(1)
				End
			End
		End
	End

	Method OnTouchHit:Void(x:Int, y:Int, pointer:Int)
		For Local i:SnapFlingObject = EachIn Self
			i.SetDx(0)
			i.SetDy(0)
			selected = Null
		End
	End

	Method OnTouchFling:Void(releaseX:Int, releaseY:Int, velocityX:Float, velocityY:Float, velocitySpeed:Float, pointer:Int)
		For Local i:SnapFlingObject = EachIn Self
			i.SetDx(velocityX / 100)
			selected = Null
		End
	End

	Method OnTouchDragged:Void(x:Int, y:Int, dx:Int, dy:Int, pointer:Int)
		For Local i:SnapFlingObject = EachIn Self
			i.SetDx(dx)
			selected = Null
		End
	End

	Method OnTouchReleased:Void(x:Int, y:Int, pointer:Int)
		GetSelectedObject()
	End

	Method GetSelectedObject:Void()
		' find the object most out of view percentage
		Local oldPercentage:Float = 0
		For Local i:SnapFlingObject = EachIn Self
			If i.GetPercentageOnScreen() < oldPercentage
				oldPercentage = i.GetPercentageOnScreen()
			End
		Next

		' find the object which is the most in view
		Local found:Bool = False
		For Local i:SnapFlingObject = EachIn Self
			If i.GetDx() = 0
				If i.GetPercentageOnScreen() > oldPercentage
					oldPercentage = i.GetPercentageOnScreen()
					selected = i
					found = True
				End
			End
		Next
		If found
			If selected
				selected.SetEx(endPositionX)
				selected.SetOx(selected.GetX())
				selected.SetTimer(0)
				selected.SetTimerSpeed(timerSpeed)
			End
		End
	End

	Method Render:Void()
		For Local i:SnapFlingObject = EachIn Self
			i.Render()
		End
	End
End

Interface SnapFlingObject
	Method MoveByXY:Void(dx:Float, dy:Float)
	Method MoveToXY:Void(x:Float, y:Float)
	Method GetPercentageOnScreen:Float()
	Method Update:Void()
	Method Render:Void()

	' getters / setters
	Method GetX:Float()
	Method GetY:Float()
	Method GetDx:Float()
	Method GetDy:Float()
	Method SetX:Void(val:Float)
	Method SetY:Void(val:Float)
	Method SetDx:Void(val:Float)
	Method SetDy:Void(val:Float)
	Method GetOx:Float()
	Method SetOx:Void(val:Float)
	Method GetEx:Float()
	Method SetEx:Void(val:Float)
	Method GetTimer:Float()
	Method SetTimer:Void(val:Float)
	Method GetTimerSpeed:Float()
	Method SetTimerSpeed:Void(val:Float)
End


Added basic movement behavior based on sent positions.

#Rem
	A basic networking demo showcasing I/O
	abstraction, network integration, etc.
#End

Strict

Public

' Preprocessor related:
#CURSOR_DEMO_CRAZY_MODE = False ' True

' If enabled, this could cause timeouts.
#MOJO_AUTO_SUSPEND_ENABLED = False

' GLFW related:
#GLFW_USE_MINGW=True

#GLFW_WINDOW_TITLE="Monkey Game"
#GLFW_WINDOW_WIDTH=640
#GLFW_WINDOW_HEIGHT=480
#GLFW_WINDOW_SAMPLES=0
#GLFW_WINDOW_RESIZABLE=True
#GLFW_WINDOW_DECORATED=True
#GLFW_WINDOW_FLOATING=False
#GLFW_WINDOW_FULLSCREEN=False

' Imports:
Import mojo

Import brl.stream

Import networking

' Classes:
Class Game Extends App Implements NetworkListener Final
	' Constant variable(s):
	Const PORT:= 27015 ' 5029

	'Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_UDP
	Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_TCP

	' Message types:
	Const MSG_TYPE_WELCOME:= NetworkEngine.MSG_TYPE_CUSTOM
	Const MSG_TYPE_CREATE_PLAYER:= (MSG_TYPE_WELCOME+1)
	Const MSG_TYPE_CREATE_PLAYERS_IN_BULK:= (MSG_TYPE_CREATE_PLAYER+1)
	Const MSG_TYPE_STATE:= (MSG_TYPE_CREATE_PLAYERS_IN_BULK+1)
	Const MSG_TYPE_STATES_IN_BULK:= (MSG_TYPE_STATE+1)
	Const MSG_TYPE_DELETE_PLAYER:= (MSG_TYPE_STATES_IN_BULK+1)

	' State types:
	Const STATE_WAITING:= 0
	Const STATE_GAMEPLAY:= 1

	' Functions:

	' I/O related:
	Function ReadWelcomeMessage:Void(S:Stream, PlayerHandle:LocalPlayer)
		PlayerHandle.ID = S.ReadInt()
		PlayerHandle.Load(S)

		Return
	End

	Function WriteWelcomeMessage:Void(S:Stream, PlayerHandle:NetPlayer)
		S.WriteInt(PlayerHandle.ID)
		PlayerHandle.Save(S)

		Return
	End

	' Constructor(s):
	Method OnCreate:Int()
		SetUpdateRate(0)
		'SetSwapInterval(0)

		Players = New List<Player>()

		SendTime = 50 ' 33.333 ' 50 ' Milliseconds.

		' Return the default response.
		Return 0
	End

	' Methods:
	Method InitNetwork:Void()
		Network = New NetworkEngine()

		Network.SetCallback(Self)

		' Just for the sake of doing it, set the current random-seed.
		Seed = Millisecs()

		Return
	End

	Method Host:Void(Port:Int=PORT)
		InitNetwork()

		Network.Host(Port, True, PROTOCOL)

		Print("Hosting server...")

		Return
	End

	Method Connect:Void(Hostname:String, Port:Int=PORT)
		InitNetwork()

		Network.Connect(Hostname, Port, True, PROTOCOL)

		Print("Connecting to " + Hostname + ":" + Port + "...")

		Return
	End

	Method OnUpdate:Int()
		UpdateAsyncEvents()

		Select State
			Case STATE_WAITING
				If (WhileWaiting()) Then
					State = STATE_GAMEPLAY
				Endif
			Case STATE_GAMEPLAY
				Update()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnRender:Int()
		#If Not CURSOR_DEMO_CRAZY_MODE
			Cls(205.0, 205.0, 205.0)
		#End

		Select State
			Case STATE_WAITING
				DrawText("Press F1 or click/tap to host.", 16.0, 16.0)
				DrawText("Press F2 to connect locally.", 16.0, 32.0)
			Case STATE_GAMEPLAY
				For Local P:= Eachin Players
					P.Render()
				Next
		End Select

		' Return the default response.
		Return 0
	End

	Method WhileWaiting:Bool()
		If (KeyHit(KEY_F1) Or MouseHit(MOUSE_LEFT)) Then
			Host()

			LocalCursor = New LocalPlayer()

			LocalCursor.RandomizeColor()

			Players.AddLast(LocalCursor)

			' Switch to gameplay.
			Return True
		Endif

		If (KeyHit(KEY_F2)) Then
			Connect("127.0.0.1")

			' Switch to gameplay.
			Return True
		Endif

		' Return the default response.
		Return False
	End

	' Gameplay update routine:
	Method Update:Void()
		For Local P:= Eachin Players
			P.Update()
		Next

		If (LocalCursor <> Null) Then
			If (Network.Open()) Then
				If (Millisecs()-SendTimer >= SendTime) Then
					If (Network.IsClient) Then
						SendPlayerState(LocalCursor)
					Else
						SendPlayerStatesInBulk()
					Endif

					SendTimer = Millisecs()
				Endif
			Else
				OnClose()

				Return
			Endif
		Endif

		Return
	End

	' I/O related:
	Method ReadCreatePlayerMessage:Void(S:Stream, C:Client)
		Local ID:= S.ReadInt()

		Local P:= New NetPlayer(ID, C)

		P.Load(S)

		Players.AddLast(P)

		Return
	End

	Method WriteCreatePlayerMessage:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Save(S)

		Return
	End

	Method ReadDeletePlayerMessage:Void(S:Stream)
		Local ID:= S.ReadInt()

		If (LocalCursor.ID = ID) Then
			Network.CloseAsync()
		Else
			RemovePlayer(ID)
		Endif

		Return
	End

	Method WriteDeletePlayerMessage:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		Return
	End

	Method ReadPlayersInBulk:Void(S:Stream, C:Client)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadCreatePlayerMessage(S, C)
		Next

		Return
	End

	Method WritePlayersInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WriteCreatePlayerMessage(S, P)
		Next

		Return
	End

	Method ReadPlayerState:Player(S:Stream)
		Local ID:= S.ReadInt()

		For Local P:= Eachin Players
			If (P.ID = ID) Then
				P.Read(S)

				Return P
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method WritePlayerState:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Write(S)

		Return
	End

	Method ReadPlayerStatesInBulk:Void(S:Stream)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadPlayerState(S)
		Next

		Return
	End

	Method WritePlayerStatesInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WritePlayerState(S, P)
		Next

		Return
	End

	' This tells 'C' about the specified 'Player'.
	Method SendPlayer:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WriteCreatePlayerMessage(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYER, True)

		Network.ReleasePacket(P)

		Return
	End

	' This tells 'C' about every active player.
	Method SendPlayersInBulk:Void(C:Client)
		Local P:= Network.AllocatePacket()

		WritePlayersInBulk(P)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYERS_IN_BULK, True)

		Network.ReleasePacket(P)

		Return
	End

	Method DisconnectPlayer:Void(PlayerHandle:Player)
		RemovePlayer(PlayerHandle)

		Local P:= Network.AllocatePacket()

		WriteDeletePlayerMessage(P, PlayerHandle)

		Network.Send(P, MSG_TYPE_DELETE_PLAYER, True)

		Network.ReleasePacket(P)

		Return
	End

	Method SendWelcomeMessage:Void(PlayerHandle:NetPlayer)
		Local P:= Network.AllocatePacket()

		WriteWelcomeMessage(P, PlayerHandle)

		Network.Send(P, PlayerHandle.NetworkHandle, MSG_TYPE_WELCOME, True)

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to send to everyone, and clients to send to servers.
	Method SendPlayerState:Void(PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to relay/update player states for specific 'Clients'.
	Method SendPlayerState:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is mainly used by hosts; sends all of the player states.
	Method SendPlayerStatesInBulk:Void()
		Local P:= Network.AllocatePacket()

		WritePlayerStatesInBulk(P)

		Network.Send(P, MSG_TYPE_STATES_IN_BULK, True) ' False

		Network.ReleasePacket(P)

		Return
	End

	Method RemovePlayer:Void(ID:Int)
		For Local P:= Eachin Players
			If (P.ID = ID) Then
				RemovePlayer(P)

				Return
			Endif
		Next

		Return
	End

	Method RemovePlayer:Void(PlayerHandle:Player)
		Players.RemoveEach(PlayerHandle)

		Return
	End

	' Call-backs:
	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)
		If (Not Successful) Then
			#If CONFIG = "debug"
				DebugStop()
			#End

			OnClose()

			Return
		Endif

		Print("Socket bound.")

		Return
	End

	Method OnReceiveMessage:Void(Network:NetworkEngine, C:Client, Type:MessageType, Message:Stream, MessageSize:Int)
		Select Type
			Case MSG_TYPE_WELCOME
				Print("Received welcome message.")

				If (Network.IsClient) Then
					LocalCursor = New LocalPlayer()

					ReadWelcomeMessage(Message, LocalCursor)

					Players.AddLast(LocalCursor)
				Endif
			Case MSG_TYPE_CREATE_PLAYER
				ReadCreatePlayerMessage(Message, C)
			Case MSG_TYPE_CREATE_PLAYERS_IN_BULK
				ReadPlayersInBulk(Message, C)
			Case MSG_TYPE_STATE
				Local P:= ReadPlayerState(Message)

				#Rem
					' Relay the message to everyone else:
					For Local OtherClient:= Eachin Network
						If (OtherClient = C) Then
							Continue
						Endif

						SendPlayerState(OtherClient, P)
					Next
				#End
			Case MSG_TYPE_STATES_IN_BULK
				ReadPlayerStatesInBulk(Message)
			Case MSG_TYPE_DELETE_PLAYER
				ReadDeletePlayerMessage(Message)
		End Select

		Return
	End

	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:SocketAddress, BytesSent:Int)
		'Print("Sending operation complete.")

		Return
	End

	Method OnClientConnect:Bool(Network:NetworkEngine, Address:SocketAddress)
		' Return the default response. (Accept the new connection)
		Return True
	End

	' This is called once, at any time after 'OnClientConnect'.
	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)
		' Tell 'C' to create 'NetPlayer' objects.
		SendPlayersInBulk(C)

		' Lazy, but it works; get the next player ID.
		Local ID:= NextPlayerID; NextPlayerID += 1

		' Create a 'NetPlayer' to represent 'C'.
		Local P:= New NetPlayer(ID, C); P.RandomizeColor()

		' Tell all of the current players about 'P':
		For Local CurrentPlayer:= Eachin Players
			If (CurrentPlayer = LocalCursor) Then
				Continue
			Endif

			SendPlayer(CurrentPlayer.NetworkHandle, P)
		Next

		' Now that we've got that settled, add 'P' internally. (Must be done after)
		Players.AddLast(P)

		' Set the meta-flag for client-existence.
		ClientCreated = True

		' Welcome our new 'NetPlayer'.
		SendWelcomeMessage(P)

		Print("Client accepted: " + C.Address)

		Return
	End

	Method OnClientDisconnected:Void(Network:NetworkEngine, C:Client)
		#If CONFIG = "debug"
			DebugStop()
		#End

		Print("Client disconnected: " + C.Address)

		For Local P:= Eachin Players
			If (P.NetworkHandle = C) Then
				DisconnectPlayer(P)
			Endif
		Next

		Return
	End

	Method OnDisconnected:Void(Network:NetworkEngine)
		Print("Disconnected.")

		Return
	End

	' 'MegaPacket' callback layer:
	Method OnMegaPacketRequestAccepted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestSucceeded:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestFailed:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestAborted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketFinished:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketSent:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketDownSize:Bool(Network:NetworkEngine, MP:MegaPacket)
		Return False
	End

	' Fields:
	Field State:Int = STATE_WAITING
	Field SendTimer:Int, SendTime:Int

	Field LocalCursor:LocalPlayer
	Field NextPlayerID:= 1

	Field Network:NetworkEngine
	Field Players:List<Player>

	' Booleans / Flags:
	Field ClientCreated:Bool
End

Class Player Abstract
	' Constructor(s):
	Method New(X:Float, Y:Float)
		Self.X = X
		Self.Y = Y
	End

	' Methods (Abstract):
	Method Update:Void() Abstract

	' Methods (Implemented):
	Method RandomizeColor:Void()
		' Not the best, but it gets the job done.
		R = Rnd(0.0, 255.0)
		G = Rnd(0.0, 255.0)
		B = Rnd(0.0, 255.0)

		Return
	End

	Method Render:Void()
		PushMatrix()

		SetColor(R, G, B)

		Translate(-(Width / 2.0), -(Height / 2.0))
		DrawRect(X, Y, Width, Height)

		SetColor(255.0, 255.0, 255.0)

		PopMatrix()

		Return
	End

	Method Load:Void(S:Stream)
		R = S.ReadFloat()
		G = S.ReadFloat()
		B = S.ReadFloat()

		Return
	End

	Method Save:Void(S:Stream)
		S.WriteFloat(R)
		S.WriteFloat(G)
		S.WriteFloat(B)

		Return
	End

	Method Read:Void(S:Stream)
		DstX = S.ReadFloat()
		DstY = S.ReadFloat()

		Return
	End

	Method Write:Void(S:Stream)
		S.WriteFloat(X)
		S.WriteFloat(Y)

		Return
	End

	' Properties:
	Method Width:Float() Property
		Return 32.0 ' 64.0
	End

	Method Height:Float() Property
		Return 32.0 ' 64.0
	End

	' Fields:

	' Meta:
	Field ID:Int = 0

	' Meta:

	' If we're the server, this will be the actual 'Client'.
	' If we're a client, this will be a handle to the server.
	Field NetworkHandle:Client

	' Position:
	Field X:Float = 0.0
	Field Y:Float = 0.0

	Field DstX:Float, DstY:Float

	' Color (Defaults to white):
	Field R:Float = 255.0
	Field G:Float = 255.0
	Field B:Float = 255.0
End

Class LocalPlayer Extends Player ' Final
	' Constructor(s):
	Method New(X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)
	End

	' Methods:
	Method Update:Void()
		X = MouseX()
		Y = MouseY()

		Return
	End
End

Class NetPlayer Extends Player Final
	' Constructor(s):
	Method New(ID:Int, Handle:Client, X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)

		Self.ID = ID
		Self.NetworkHandle = Handle
	End

	' Methods:
	Method Update:Void()
		Const Speed:= 0.25

		X -= ((X - DstX) * Speed)
		Y -= ((Y - DstY) * Speed)

		Return
	End
End

' Functions:
Function Main:Int()
	New Game()

	' Return the default response.
	Return 0
End

#Rem
	A basic networking demo showcasing I/O
	abstraction, network integration, etc.
#End

Strict

Public

' Preprocessor related:
#CURSOR_DEMO_CRAZY_MODE = True

' If enabled, this could cause timeouts.
#MOJO_AUTO_SUSPEND_ENABLED = False

' GLFW related:
#GLFW_USE_MINGW=True

#GLFW_WINDOW_TITLE="Monkey Game"
#GLFW_WINDOW_WIDTH=640
#GLFW_WINDOW_HEIGHT=480
#GLFW_WINDOW_SAMPLES=0
#GLFW_WINDOW_RESIZABLE=True
#GLFW_WINDOW_DECORATED=True
#GLFW_WINDOW_FLOATING=False
#GLFW_WINDOW_FULLSCREEN=False

' Imports:
Import mojo

Import brl.stream

Import networking

' Classes:
Class Game Extends App Implements NetworkListener Final
	' Constant variable(s):
	Const PORT:= 27015 ' 5029

	'Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_UDP
	Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_TCP

	' Message types:
	Const MSG_TYPE_WELCOME:= NetworkEngine.MSG_TYPE_CUSTOM
	Const MSG_TYPE_CREATE_PLAYER:= (MSG_TYPE_WELCOME+1)
	Const MSG_TYPE_CREATE_PLAYERS_IN_BULK:= (MSG_TYPE_CREATE_PLAYER+1)
	Const MSG_TYPE_STATE:= (MSG_TYPE_CREATE_PLAYERS_IN_BULK+1)
	Const MSG_TYPE_STATES_IN_BULK:= (MSG_TYPE_STATE+1)
	Const MSG_TYPE_DELETE_PLAYER:= (MSG_TYPE_STATES_IN_BULK+1)

	' State types:
	Const STATE_WAITING:= 0
	Const STATE_GAMEPLAY:= 1

	' Functions:

	' I/O related:
	Function ReadWelcomeMessage:Void(S:Stream, PlayerHandle:LocalPlayer)
		PlayerHandle.ID = S.ReadInt()
		PlayerHandle.Load(S)

		Return
	End

	Function WriteWelcomeMessage:Void(S:Stream, PlayerHandle:NetPlayer)
		S.WriteInt(PlayerHandle.ID)
		PlayerHandle.Save(S)

		Return
	End

	' Constructor(s):
	Method OnCreate:Int()
		SetUpdateRate(0)
		SetSwapInterval(0)

		Players = New List<Player>()

		SendTime = 50 ' Milliseconds.

		' Return the default response.
		Return 0
	End

	' Methods:
	Method InitNetwork:Void()
		Network = New NetworkEngine()

		Network.SetCallback(Self)

		' Just for the sake of doing it, set the current random-seed.
		Seed = Millisecs()

		Return
	End

	Method Host:Void(Port:Int=PORT)
		InitNetwork()

		Network.Host(Port, True, PROTOCOL)

		Print("Hosting server...")

		Return
	End

	Method Connect:Void(Hostname:String, Port:Int=PORT)
		InitNetwork()

		Network.Connect(Hostname, Port, True, PROTOCOL)

		Print("Connecting to " + Hostname + ":" + Port + "...")

		Return
	End

	Method OnUpdate:Int()
		UpdateAsyncEvents()

		Select State
			Case STATE_WAITING
				If (WhileWaiting()) Then
					State = STATE_GAMEPLAY
				Endif
			Case STATE_GAMEPLAY
				Update()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnRender:Int()
		#If Not CURSOR_DEMO_CRAZY_MODE
			Cls(205.0, 205.0, 205.0)
		#End

		Select State
			Case STATE_WAITING
				DrawText("Press F1 or click/tap to host.", 16.0, 16.0)
				DrawText("Press F2 to connect locally.", 16.0, 32.0)
			Case STATE_GAMEPLAY
				For Local P:= Eachin Players
					P.Render()
				Next
		End Select

		' Return the default response.
		Return 0
	End

	Method WhileWaiting:Bool()
		If (KeyHit(KEY_F1) Or MouseHit(MOUSE_LEFT)) Then
			Host()

			LocalCursor = New LocalPlayer()

			LocalCursor.RandomizeColor()

			Players.AddLast(LocalCursor)

			' Switch to gameplay.
			Return True
		Endif

		If (KeyHit(KEY_F2)) Then
			Connect("127.0.0.1")

			' Switch to gameplay.
			Return True
		Endif

		' Return the default response.
		Return False
	End

	' Gameplay update routine:
	Method Update:Void()
		For Local P:= Eachin Players
			P.Update()
		Next

		If (LocalCursor <> Null) Then
			If (Network.Open()) Then
				If (Millisecs()-SendTimer >= SendTime) Then
					If (Network.IsClient) Then
						SendPlayerState(LocalCursor)
					Else
						SendPlayerStatesInBulk()
					Endif

					SendTimer = Millisecs()
				Endif
			Else
				OnClose()

				Return
			Endif
		Endif

		Return
	End

	' I/O related:
	Method ReadCreatePlayerMessage:Void(S:Stream, C:Client)
		Local ID:= S.ReadInt()

		Local P:= New NetPlayer(ID, C)

		P.Load(S)

		Players.AddLast(P)

		Return
	End

	Method WriteCreatePlayerMessage:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Save(S)

		Return
	End

	Method ReadDeletePlayerMessage:Void(S:Stream)
		Local ID:= S.ReadInt()

		If (LocalCursor.ID = ID) Then
			Network.CloseAsync()
		Else
			RemovePlayer(ID)
		Endif

		Return
	End

	Method WriteDeletePlayerMessage:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		Return
	End

	Method ReadPlayersInBulk:Void(S:Stream, C:Client)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadCreatePlayerMessage(S, C)
		Next

		Return
	End

	Method WritePlayersInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WriteCreatePlayerMessage(S, P)
		Next

		Return
	End

	Method ReadPlayerState:Player(S:Stream)
		Local ID:= S.ReadInt()

		For Local P:= Eachin Players
			If (P.ID = ID) Then
				P.Read(S)

				Return P
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method WritePlayerState:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Write(S)

		Return
	End

	Method ReadPlayerStatesInBulk:Void(S:Stream)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadPlayerState(S)
		Next

		Return
	End

	Method WritePlayerStatesInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WritePlayerState(S, P)
		Next

		Return
	End

	' This tells 'C' about the specified 'Player'.
	Method SendPlayer:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WriteCreatePlayerMessage(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYER, True)

		Network.ReleasePacket(P)

		Return
	End

	' This tells 'C' about every active player.
	Method SendPlayersInBulk:Void(C:Client)
		Local P:= Network.AllocatePacket()

		WritePlayersInBulk(P)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYERS_IN_BULK, True)

		Network.ReleasePacket(P)

		Return
	End

	Method DisconnectPlayer:Void(PlayerHandle:Player)
		RemovePlayer(PlayerHandle)

		Local P:= Network.AllocatePacket()

		WriteDeletePlayerMessage(P, PlayerHandle)

		Network.Send(P, MSG_TYPE_DELETE_PLAYER, True)

		Network.ReleasePacket(P)

		Return
	End

	Method SendWelcomeMessage:Void(PlayerHandle:NetPlayer)
		Local P:= Network.AllocatePacket()

		WriteWelcomeMessage(P, PlayerHandle)

		Network.Send(P, PlayerHandle.NetworkHandle, MSG_TYPE_WELCOME, True)

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to send to everyone, and clients to send to servers.
	Method SendPlayerState:Void(PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to relay/update player states for specific 'Clients'.
	Method SendPlayerState:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is mainly used by hosts; sends all of the player states.
	Method SendPlayerStatesInBulk:Void()
		Local P:= Network.AllocatePacket()

		WritePlayerStatesInBulk(P)

		Network.Send(P, MSG_TYPE_STATES_IN_BULK, True) ' False

		Network.ReleasePacket(P)

		Return
	End

	Method RemovePlayer:Void(ID:Int)
		For Local P:= Eachin Players
			If (P.ID = ID) Then
				RemovePlayer(P)

				Return
			Endif
		Next

		Return
	End

	Method RemovePlayer:Void(PlayerHandle:Player)
		Players.RemoveEach(PlayerHandle)

		Return
	End

	' Call-backs:
	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)
		If (Not Successful) Then
			#If CONFIG = "debug"
				DebugStop()
			#End

			OnClose()

			Return
		Endif

		Print("Socket bound.")

		Return
	End

	Method OnReceiveMessage:Void(Network:NetworkEngine, C:Client, Type:MessageType, Message:Stream, MessageSize:Int)
		Select Type
			Case MSG_TYPE_WELCOME
				Print("Received welcome message.")

				If (Network.IsClient) Then
					LocalCursor = New LocalPlayer()

					ReadWelcomeMessage(Message, LocalCursor)

					Players.AddLast(LocalCursor)
				Endif
			Case MSG_TYPE_CREATE_PLAYER
				ReadCreatePlayerMessage(Message, C)
			Case MSG_TYPE_CREATE_PLAYERS_IN_BULK
				ReadPlayersInBulk(Message, C)
			Case MSG_TYPE_STATE
				Local P:= ReadPlayerState(Message)

				#Rem
					' Relay the message to everyone else:
					For Local OtherClient:= Eachin Network
						If (OtherClient = C) Then
							Continue
						Endif

						SendPlayerState(OtherClient, P)
					Next
				#End
			Case MSG_TYPE_STATES_IN_BULK
				ReadPlayerStatesInBulk(Message)
			Case MSG_TYPE_DELETE_PLAYER
				ReadDeletePlayerMessage(Message)
		End Select

		Return
	End

	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:SocketAddress, BytesSent:Int)
		'Print("Sending operation complete.")

		Return
	End

	Method OnClientConnect:Bool(Network:NetworkEngine, Address:SocketAddress)
		' Return the default response. (Accept the new connection)
		Return True
	End

	' This is called once, at any time after 'OnClientConnect'.
	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)
		' Tell 'C' to create 'NetPlayer' objects.
		SendPlayersInBulk(C)

		' Lazy, but it works; get the next player ID.
		Local ID:= NextPlayerID; NextPlayerID += 1

		' Create a 'NetPlayer' to represent 'C'.
		Local P:= New NetPlayer(ID, C); P.RandomizeColor()

		' Tell all of the current players about 'P':
		For Local CurrentPlayer:= Eachin Players
			If (CurrentPlayer = LocalCursor) Then
				Continue
			Endif

			SendPlayer(CurrentPlayer.NetworkHandle, P)
		Next

		' Now that we've got that settled, add 'P' internally. (Must be done after)
		Players.AddLast(P)

		' Set the meta-flag for client-existence.
		ClientCreated = True

		' Welcome our new 'NetPlayer'.
		SendWelcomeMessage(P)

		Print("Client accepted: " + C.Address)

		Return
	End

	Method OnClientDisconnected:Void(Network:NetworkEngine, C:Client)
		#If CONFIG = "debug"
			DebugStop()
		#End

		Print("Client disconnected: " + C.Address)

		For Local P:= Eachin Players
			If (P.NetworkHandle = C) Then
				DisconnectPlayer(P)
			Endif
		Next

		Return
	End

	Method OnDisconnected:Void(Network:NetworkEngine)
		Print("Disconnected.")

		Return
	End

	' 'MegaPacket' callback layer:
	Method OnMegaPacketRequestAccepted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestSucceeded:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestFailed:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestAborted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketFinished:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketSent:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketDownSize:Bool(Network:NetworkEngine, MP:MegaPacket)
		Return False
	End

	' Fields:
	Field State:Int = STATE_WAITING
	Field SendTimer:Int, SendTime:Int

	Field LocalCursor:LocalPlayer
	Field NextPlayerID:= 1

	Field Network:NetworkEngine
	Field Players:List<Player>

	' Booleans / Flags:
	Field ClientCreated:Bool
End

Class Player Abstract
	' Constructor(s):
	Method New(X:Float, Y:Float)
		Self.X = X
		Self.Y = Y
	End

	' Methods (Abstract):
	Method Update:Void() Abstract

	' Methods (Implemented):
	Method RandomizeColor:Void()
		' Not the best, but it gets the job done.
		R = Rnd(0.0, 255.0)
		G = Rnd(0.0, 255.0)
		B = Rnd(0.0, 255.0)

		Return
	End

	Method Render:Void()
		PushMatrix()

		SetColor(R, G, B)

		Translate(-(Width / 2.0), -(Height / 2.0))
		DrawRect(X, Y, Width, Height)

		SetColor(255.0, 255.0, 255.0)

		PopMatrix()

		Return
	End

	Method Load:Void(S:Stream)
		R = S.ReadFloat()
		G = S.ReadFloat()
		B = S.ReadFloat()

		Return
	End

	Method Save:Void(S:Stream)
		S.WriteFloat(R)
		S.WriteFloat(G)
		S.WriteFloat(B)

		Return
	End

	Method Read:Void(S:Stream)
		X = S.ReadFloat()
		Y = S.ReadFloat()

		Return
	End

	Method Write:Void(S:Stream)
		S.WriteFloat(X)
		S.WriteFloat(Y)

		Return
	End

	' Properties:
	Method Width:Float() Property
		Return 32.0 ' 64.0
	End

	Method Height:Float() Property
		Return 32.0 ' 64.0
	End

	' Fields:

	' Meta:
	Field ID:Int = 0

	' Meta:

	' If we're the server, this will be the actual 'Client'.
	' If we're a client, this will be a handle to the server.
	Field NetworkHandle:Client

	' Position:
	Field X:Float = 0.0
	Field Y:Float = 0.0

	' Color (Defaults to white):
	Field R:Float = 255.0
	Field G:Float = 255.0
	Field B:Float = 255.0
End

Class LocalPlayer Extends Player ' Final
	' Constructor(s):
	Method New(X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)
	End

	' Methods:
	Method Update:Void()
		X = MouseX()
		Y = MouseY()

		Return
	End
End

Class NetPlayer Extends Player Final
	' Constructor(s):
	Method New(ID:Int, Handle:Client, X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)

		Self.ID = ID
		Self.NetworkHandle = Handle
	End

	' Methods:
	Method Update:Void()
		' Nothing so far.

		Return
	End
End

' Functions:
Function Main:Int()
	New Game()

	' Return the default response.
	Return 0
End


reformat exception message body of failed asserts

Strict

Private

Import assertionfailedexception

Function Fail:Void(message:String)
    Throw New AssertionFailedException("Failed asserting that " + message)
End

Function FailWithDetails:Void(message:String, expected:String, actual:String)
    Fail(message + "~n" +
        "  Expected: " + expected + "~n" +
        "  Actual  : " + actual)
End

Public

Class Assert Abstract
    Function AssertEquals:Void(first:Int, second:Int)
        If first = second Then Return
        FailWithDetails("two integers are equal", first, second)
    End

    Function AssertEquals:Void(first:Float, second:Float)
        If first = second Then Return
        FailWithDetails("two floats are equal", first, second)
    End

    Function AssertEquals:Void(first:String, second:String)
        If first = second Then Return
        FailWithDetails("two strings are equal", first, second)
    End

    Function AssertNotEquals:Void(first:Int, second:Int)
        If first <> second Then Return
        Fail("two integers are NOT equal")
    End

    Function AssertNotEquals:Void(first:Float, second:Float)
        If first <> second Then Return
        Fail("two floats are NOT equal")
    End

    Function AssertNotEquals:Void(first:String, second:String)
        If first <> second Then Return
        Fail("two strings are NOT equal")
    End

    Function AssertGreaterThan:Void(expected:Int, actual:Int)
        If expected < actual Then Return
        Fail(actual + " is greater than " + expected)
    End

    Function AssertGreaterThan:Void(expected:Float, actual:Float)
        If expected < actual Then Return
        Fail(actual + " is greater than " + expected)
    End

    Function AssertGreaterThanOrEqual:Void(expected:Int, actual:Int)
        If expected <= actual Then Return
        Fail(actual + " is greater than or equal to " + expected)
    End

    Function AssertGreaterThanOrEqual:Void(expected:Float, actual:Float)
        If expected <= actual Then Return
        Fail(actual + " is greater than or equal to " + expected)
    End

    Function AssertLessThan:Void(expected:Int, actual:Int)
        If expected > actual Then Return
        Fail(actual + " is less than " + expected)
    End

    Function AssertLessThan:Void(expected:Float, actual:Float)
        If expected > actual Then Return
        Fail(actual + " is less than " + expected)
    End

    Function AssertLessThanOrEqual:Void(expected:Int, actual:Int)
        If expected >= actual Then Return
        Fail(actual + " is less than or equal to " + expected)
    End

    Function AssertLessThanOrEqual:Void(expected:Float, actual:Float)
        If expected >= actual Then Return
        Fail(actual + " is less than or equal to " + expected)
    End

    Function AssertStringStartsWith:Void(needle:String, haystack:String)
        If haystack.StartsWith(needle) Then Return
        FailWithDetails(
            "a string starts with a given prefix",
            needle, haystack)
    End

    Function AssertStringNotStartsWith:Void(needle:String, haystack:String)
        If Not haystack.StartsWith(needle) Then Return
        FailWithDetails(
            "a string does NOT starts with a given prefix",
            needle, haystack)
    End

    Function AssertStringEndsWith:Void(needle:String, haystack:String)
        If haystack.EndsWith(needle) Then Return
        FailWithDetails(
            "a string ends with a given prefix",
            needle, haystack)
    End

    Function AssertStringNotEndsWith:Void(needle:String, haystack:String)
        If Not haystack.EndsWith(needle) Then Return
        FailWithDetails(
            "a string does NOT ends with a given prefix",
            needle, haystack)
    End

    Function AssertStringContains:Void(needle:String, haystack:String)
        If haystack.Contains(needle) Then Return
        FailWithDetails(
            "a string contains a given sub-string",
            needle, haystack)
    End

    Function AssertStringNotContains:Void(needle:String, haystack:String)
        If Not haystack.Contains(needle) Then Return
        FailWithDetails(
            "a string does NOT contains a given sub-string",
            needle, haystack)
    End

    Function AssertTrue:Void(condition:Bool)
        If condition Then Return
        Fail("false is true")
    End

    Function AssertFalse:Void(condition:Bool)
        If Not condition Then Return
        Fail("true is false")
    End

    Function AssertNull:Void(obj:Object)
        If obj = Null Then Return
        Fail("some object is null")
    End

    Function AssertNotNull:Void(obj:Object)
        If obj <> Null Then Return
        Fail("some object is NOT null")
    End
End

Strict

Private

Import assertionfailedexception

Function Fail:Void(message:String)
    Throw New AssertionFailedException("Failed asserting that " + message)
End

Function FailWithDetails:Void(message:String, expected:String, actual:String)
    Fail(message + "~n" +
        "Expected: " + expected + "~n" +
        "Actual  : " + actual)
End

Public

Class Assert Abstract
    Function AssertEquals:Void(first:Int, second:Int)
        If first = second Then Return
        FailWithDetails("two integers are equal", first, second)
    End

    Function AssertEquals:Void(first:Float, second:Float)
        If first = second Then Return
        FailWithDetails("two floats are equal", first, second)
    End

    Function AssertEquals:Void(first:String, second:String)
        If first = second Then Return
        FailWithDetails("two strings are equal", first, second)
    End

    Function AssertNotEquals:Void(first:Int, second:Int)
        If first <> second Then Return
        Fail("two integers are NOT equal")
    End

    Function AssertNotEquals:Void(first:Float, second:Float)
        If first <> second Then Return
        Fail("two floats are NOT equal")
    End

    Function AssertNotEquals:Void(first:String, second:String)
        If first <> second Then Return
        Fail("two strings are NOT equal")
    End

    Function AssertGreaterThan:Void(expected:Int, actual:Int)
        If expected < actual Then Return
        Fail(actual + " is greater than " + expected)
    End

    Function AssertGreaterThan:Void(expected:Float, actual:Float)
        If expected < actual Then Return
        Fail(actual + " is greater than " + expected)
    End

    Function AssertGreaterThanOrEqual:Void(expected:Int, actual:Int)
        If expected <= actual Then Return
        Fail(actual + " is greater than or equal to " + expected)
    End

    Function AssertGreaterThanOrEqual:Void(expected:Float, actual:Float)
        If expected <= actual Then Return
        Fail(actual + " is greater than or equal to " + expected)
    End

    Function AssertLessThan:Void(expected:Int, actual:Int)
        If expected > actual Then Return
        Fail(actual + " is less than " + expected)
    End

    Function AssertLessThan:Void(expected:Float, actual:Float)
        If expected > actual Then Return
        Fail(actual + " is less than " + expected)
    End

    Function AssertLessThanOrEqual:Void(expected:Int, actual:Int)
        If expected >= actual Then Return
        Fail(actual + " is less than or equal to " + expected)
    End

    Function AssertLessThanOrEqual:Void(expected:Float, actual:Float)
        If expected >= actual Then Return
        Fail(actual + " is less than or equal to " + expected)
    End

    Function AssertStringStartsWith:Void(needle:String, haystack:String)
        If haystack.StartsWith(needle) Then Return
        FailWithDetails(
            "a string starts with a given prefix",
            needle, haystack)
    End

    Function AssertStringNotStartsWith:Void(needle:String, haystack:String)
        If Not haystack.StartsWith(needle) Then Return
        FailWithDetails(
            "a string does NOT starts with a given prefix",
            needle, haystack)
    End

    Function AssertStringEndsWith:Void(needle:String, haystack:String)
        If haystack.EndsWith(needle) Then Return
        FailWithDetails(
            "a string ends with a given prefix",
            needle, haystack)
    End

    Function AssertStringNotEndsWith:Void(needle:String, haystack:String)
        If Not haystack.EndsWith(needle) Then Return
        FailWithDetails(
            "a string does NOT ends with a given prefix",
            needle, haystack)
    End

    Function AssertStringContains:Void(needle:String, haystack:String)
        If haystack.Contains(needle) Then Return
        FailWithDetails(
            "a string contains a given sub-string",
            needle, haystack)
    End

    Function AssertStringNotContains:Void(needle:String, haystack:String)
        If Not haystack.Contains(needle) Then Return
        FailWithDetails(
            "a string does NOT contains a given sub-string",
            needle, haystack)
    End

    Function AssertTrue:Void(condition:Bool)
        If condition Then Return
        Fail("false is true")
    End

    Function AssertFalse:Void(condition:Bool)
        If Not condition Then Return
        Fail("true is false")
    End

    Function AssertNull:Void(obj:Object)
        If obj = Null Then Return
        Fail("some object is null")
    End

    Function AssertNotNull:Void(obj:Object)
        If obj <> Null Then Return
        Fail("some object is NOT null")
    End
End