Fixed bug reading self-closing tags.

Fixed bug reading self-closing tags.

--HG--
extra : convert_revision : svn%3Ae934d3cd-0d57-f34a-94d0-9e8d31ae1c29/trunk%40397

' Diddy XML parser
' This is a very simple XML parser, and expects a document to be as close to well-formed as possible.
' The only real leniency it has is that attributes may be defined without surrounding quotes/double quotes.
' All XML characters should be escaped in strings. > < etc.
' Currently it supports regular tag and attribute definitions, processing instructions, and skipping comments.
' For now, exported indentation is hardcoded to 2 spaces.
' Written from scratch, using as little string manipulation as possible.

Import assert
Import functions
Import collections

Class XMLParser
	Const TAG_DEFAULT:Int = 0
	Const TAG_COMMENT:Int = 1
	Const TAG_CDATA:Int = 2
	Const TAG_DOCTYPE:Int = 3

	Field str:String
	Field tags:Int[] ' tag character indexes, alternating < and >
	Field tagType:Int[]
	Field tagCount:Int = 0
	Field tagsLength:Int
	Field quotes:Int[] ' quote character indexes, alternating opening and closing
	Field quoteCount:Int = 0
	Field quotesLength:Int
	Field pis:Int[] ' pi character indexes, alternating <? And ?> (index is on <>)
	Field piCount:Int = 0
	Field pisLength:Int

	Method CacheControlCharacters:Void()
		tagsLength = 128
		quotesLength = 128
		pisLength = 128
		tags = New Int[tagsLength]
		tagType = New Int[tagsLength]
		quotes = New Int[quotesLength]
		pis = New Int[quotesLength]
		tagCount = 0
		quoteCount = 0
		piCount = 0
		Local inTag:Bool = False
		Local inQuote:Bool = False
		Local inComment:Bool = False
		Local inCdata:Bool = False
		Local inDoctype:Bool = False
		Local inPi:Bool = False
		Local strlen:Int = str.Length
		For Local i:Int = 0 Until strlen
			' if we're in a comment, we're only looking for -->
			If inComment Then
				If str[i] = ASC_GREATER_THAN And str[i-1] = ASC_HYPHEN And str[i-2] = ASC_HYPHEN Then
					If tagCount = tagsLength Then
						tagsLength *= 2
						tags = tags.Resize(tagsLength)
						tagType = tagType.Resize(tagsLength)
					End
					tags[tagCount] = i
					tagType[tagCount] = TAG_COMMENT
					tagCount += 1
					inComment = False
				End
			' if we're in a cdata, we're only looking for ]]>
			ElseIf inCdata Then
				If str[i] = ASC_GREATER_THAN And str[i-1] = ASC_CLOSE_BRACKET And str[i-2] = ASC_CLOSE_BRACKET Then
					If tagCount = tagsLength Then
						tagsLength *= 2
						tags = tags.Resize(tagsLength)
						tagType = tagType.Resize(tagsLength)
					End
					tags[tagCount] = i
					tagType[tagCount] = TAG_CDATA
					tagCount += 1
					inCdata = False
				End
			' if we're in a quoted string, we're only looking for "
			ElseIf inQuote Then
				If str[i] = ASC_DOUBLE_QUOTE Then
					If quoteCount = quotesLength Then
						quotesLength *= 2
						quotes = quotes.Resize(quotesLength)
					End
					quotes[quoteCount] = i
					quoteCount += 1
					inQuote = False
				End
			' check if we should start a new quoted string
			ElseIf str[i] = ASC_DOUBLE_QUOTE Then
				If quoteCount = quotesLength Then
					quotesLength *= 2
					quotes = quotes.Resize(quotesLength)
				End
				quotes[quoteCount] = i
				quoteCount += 1
				inQuote = True
			' if we're in a processing instruction, we're only looking for ?>
			ElseIf inPi Then
				If str[i] = ASC_GREATER_THAN And str[i-1] = ASC_QUESTION Then
					If piCount = pisLength Then
						pisLength *= 2
						pis = pis.Resize(pisLength)
					End
					pis[piCount] = i
					piCount += 1
					inPi = False
				End
			' if we're in a doctype, we're only looking for >
			ElseIf inDoctype Then
				If str[i] = ASC_GREATER_THAN Then
					If tagCount = tagsLength Then
						tagsLength *= 2
						tags = tags.Resize(tagsLength)
						tagType = tagType.Resize(tagsLength)
					End
					tags[tagCount] = i
					tagType[tagCount] = TAG_DOCTYPE
					tagCount += 1
					inDoctype = False
				End
			' less than
			ElseIf str[i] = ASC_LESS_THAN Then
				' if we're in a tag, die
				If inTag Then AssertError "Invalid less than!"
				' check for prolog
				If str[i+1] = ASC_EXCLAMATION Then
					' comment?
					If str[i+2] = ASC_HYPHEN And str[i+3] = ASC_HYPHEN Then
						If tagCount = tagsLength Then
							tagsLength *= 2
							tags = tags.Resize(tagsLength)
							tagType = tagType.Resize(tagsLength)
						End
						tags[tagCount] = i
						tagType[tagCount] = TAG_COMMENT
						tagCount += 1
						inComment = True
					' cdata?
					ElseIf str[i+2] = ASC_OPEN_BRACKET And
							(str[i+3] = ASC_UPPER_C Or str[i+3] = ASC_LOWER_C) And
							(str[i+4] = ASC_UPPER_D Or str[i+4] = ASC_LOWER_D) And
							(str[i+5] = ASC_UPPER_A Or str[i+5] = ASC_LOWER_A) And
							(str[i+6] = ASC_UPPER_T Or str[i+6] = ASC_LOWER_T) And
							(str[i+7] = ASC_UPPER_A Or str[i+7] = ASC_LOWER_A) And
							str[i+8] = ASC_OPEN_BRACKET Then
						If tagCount = tagsLength Then
							tagsLength *= 2
							tags = tags.Resize(tagsLength)
							tagType = tagType.Resize(tagsLength)
						End
						tags[tagCount] = i
						tagType[tagCount] = TAG_CDATA
						tagCount += 1
						inCdata = True
					' doctype?
					ElseIf (str[i+2] = ASC_UPPER_D Or str[i+2] = ASC_LOWER_D) And
							(str[i+3] = ASC_UPPER_O Or str[i+3] = ASC_LOWER_O) And
							(str[i+4] = ASC_UPPER_C Or str[i+4] = ASC_LOWER_C) And
							(str[i+5] = ASC_UPPER_T Or str[i+5] = ASC_LOWER_T) And
							(str[i+6] = ASC_UPPER_Y Or str[i+6] = ASC_LOWER_Y) And
							(str[i+7] = ASC_UPPER_P Or str[i+7] = ASC_LOWER_P) And
							(str[i+8] = ASC_UPPER_E Or str[i+8] = ASC_LOWER_E) Then
						If tagCount = tagsLength Then
							tagsLength *= 2
							tags = tags.Resize(tagsLength)
							tagType = tagType.Resize(tagsLength)
						End
						tags[tagCount] = i
						tagType[tagCount] = TAG_DOCTYPE
						tagCount += 1
						inDoctype = True
					Else
						AssertError "Invalid prolog."
					End
				' check for processing instruction
				ElseIf str[i+1] = ASC_QUESTION Then
					If piCount = pisLength Then
						pisLength *= 2
						pis = pis.Resize(pisLength)
					End
					pis[piCount] = i
					piCount += 1
					inPi = True
				' finally, it must just be opening a tag
				Else
					If tagCount = tagsLength Then
						tagsLength *= 2
						tags = tags.Resize(tagsLength)
						tagType = tagType.Resize(tagsLength)
					End
					tags[tagCount] = i
					tagType[tagCount] = TAG_DEFAULT
					tagCount += 1
					inTag = True
				End
			' greater than
			ElseIf str[i] = ASC_GREATER_THAN Then
				If Not inTag Then AssertError "Invalid greater than!"
				If tagCount = tagsLength Then
					tagsLength *= 2
					tags = tags.Resize(tagsLength)
					tagType = tagType.Resize(tagsLength)
				End
				tags[tagCount] = i
				tagType[tagCount] = TAG_DEFAULT
				tagCount += 1
				inTag = False
			End
		Next
		If inQuote Then AssertError "Unclosed quote!"
		If inTag Then AssertError "Unclosed tag!"
		If inComment Then AssertError "Unclosed comment!"
		If inCdata Then AssertError "Unclosed cdata!"
		If inPi Then AssertError "Unclosed processing instruction!"
	End

	' get the contents of a tag (given start and end)
	Method GetTagContents:XMLElement(startIndex:Int, endIndex:Int)
		' die if empty tag
		If startIndex = endIndex Then AssertError("Empty tag detected.")
		' our element
		Local e:XMLElement = New XMLElement
		Local a:Int, singleQuoted:Bool, doubleQuoted:Bool, key:String, value:String

		' get the name
		a = startIndex
		While a < endIndex
			If str[a]=ASC_SPACE Or str[a]=ASC_TAB Or str[a]=ASC_LF Or str[a]=ASC_CR Or a = endIndex-1 Then
				If a = endIndex-1 Then
					e.name = str[startIndex..endIndex]
				Else
					e.name = str[startIndex..a]
				End
				a += 1
				Exit
			End
			a += 1
		End
		startIndex = a

		' TODO: validate tag name is alphanumeric
		' if no name, die
		If e.name = "" Then AssertError("Error reading tag name.")

		' loop on all tokens
		While startIndex < endIndex
			' trim leading whitespace
			While startIndex < endIndex And (str[startIndex]=ASC_SPACE Or str[startIndex]=ASC_TAB Or str[startIndex]=ASC_LF Or str[startIndex]=ASC_CR)
				startIndex += 1
			End

			' clear check variables
			singleQuoted = False
			doubleQuoted = False
			key = ""
			value = ""

			' find the key
			a = startIndex
			While a < endIndex
				If str[a] = ASC_EQUALS Or str[a]=ASC_SPACE Or str[a]=ASC_TAB Or str[a]=ASC_LF Or str[a]=ASC_CR Or a = endIndex-1 Then
					If a=endIndex-1 Then
						key = str[startIndex..endIndex]
					Else
						key = str[startIndex..a]
					End
					a += 1
					Exit
				End
				a += 1
			End
			startIndex = a

			' if the key is empty, there was an error (unless we've hit the end of the string)
			If key = "" Then
				If a < endIndex Then
					AssertError("Error reading attribute key.")
				Else
					Exit
				End
			End

			' if it stopped on an equals, get the value
			If str[a-1] = ASC_EQUALS Then
				singleQuoted = False
				doubleQuoted = False
				While a < endIndex
					' check if it's a single quote
					If str[a] = ASC_SINGLE_QUOTE And Not doubleQuoted Then
						' if this is the first index, mark it as quoted
						If a = startIndex Then
							singleQuoted = True
						' otherwise, if we're not quoted at all, die
						ElseIf Not singleQuoted And Not doubleQuoted Then
							AssertError("Unexpected single quote detected in attribute value.")
						Else
							' we must be ending the quote here, so grab it and break out
							singleQuoted = False
							value = str[startIndex+1..a]
							a += 1
							Exit
						End

					' check if it's a double quote
					ElseIf str[a] = ASC_DOUBLE_QUOTE And Not singleQuoted Then
						' if this is the first index, mark it as quoted
						If a = startIndex Then
							doubleQuoted = True
						' otherwise, if we're not quoted at all, die
						ElseIf Not singleQuoted And Not doubleQuoted Then
							AssertError("Unexpected double quote detected in attribute value.")
						Else
							' we must be ending the quote here, so break out
							doubleQuoted = False
							value = str[startIndex+1..a]
							a += 1
							Exit
						End

					' should we be ending the attribute?
					ElseIf a = endIndex-1 Or (Not singleQuoted And Not doubleQuoted And (str[a]=ASC_SPACE Or str[a]=ASC_TAB Or str[a]=ASC_LF Or str[a]=ASC_CR))
						If a=endIndex-1 Then
							value = str[startIndex..endIndex]
						Else
							value = str[startIndex..a]
						End
						a += 1
						Exit
					End
					a += 1
				End
				startIndex = a
				value = UnescapeXMLString(value)

				If singleQuoted Or doubleQuoted Then AssertError("Unclosed quote detected.")
			End

			' set the attribute
			e.SetAttribute(key, value)

			If a >= endIndex Then Exit
		End
		Return e
	End

	Method ParseFile:XMLDocument(filename:String)
		Return ParseString(LoadString(filename))
	End

	Method TrimString:Void(startIdx:Int, endIdx:Int, trimmed:Int[])
		Local trimStart:Int = startIdx, trimEnd:Int = endIdx
		While trimEnd > trimStart
			Local ch:Int = str[trimEnd-1]
			If ch = ASC_CR Or ch = ASC_LF Or ch = ASC_SPACE Or ch = ASC_TAB Then
				trimEnd -= 1
			Else
				Exit
			End
		End
		While trimStart < trimEnd
			Local ch:Int = str[trimStart]
			If ch = ASC_CR Or ch = ASC_LF Or ch = ASC_SPACE Or ch = ASC_TAB Then
				trimStart += 1
			Else
				Exit
			End
		End
		trimmed[0] = trimStart
		trimmed[1] = trimEnd
	End

	' parses an xml doc, currently doesn't support nested PI or prolog
	Method ParseString:XMLDocument(str:String)
		Self.str = str

		Local doc:XMLDocument = New XMLDocument
		Local elements:ArrayList<XMLElement> = New ArrayList<XMLElement>
		Local thisE:XMLElement = Null, newE:XMLElement = Null
		Local index:Int = 0, a:Int, b:Int, c:Int, nextIndex:Int
		Local trimmed:Int[] = New Int[2]

		' cache all the control characters
		CacheControlCharacters()

		' find first opening tag
		If tagCount = 0 Then AssertError "Something seriously wrong... no tags!"

		' parse processing instructions
		index = 0
		a = pis[index]+2
		b = pis[index+1]-1
		While index < piCount
			TrimString(a, b, trimmed)
			If trimmed[0] <> trimmed[1] Then
				newE = GetTagContents(trimmed[0], trimmed[1])
				newE.pi = True
				doc.pi.Add(newE)
				newE = Null
			Else
				AssertError "Empty processing instruction."
			End
			index += 2
		End

		' loop on tags
		index = 0
		While index+1 < tagCount
			' we skip comments
			If tagType[index] = TAG_COMMENT Then
				' skip comments

			' if it's cdata
			ElseIf tagType[index] = TAG_CDATA Then
				' get the text between < and >
				a = tags[index]+9 ' "![CDATA[".Length
				b = tags[index+1]-2 ' "]]".Length

				' add a cdata element
				newE = New XMLElement
				newE.cdata = True
				newE.value = str[a..b]
				newE.parent = thisE
				thisE.AddChild(newE)
				newE = Null

			' otherwise we do normal tag stuff
			Else
				' get the text between < and >
				a = tags[index]+1
				b = tags[index+1]

				' trim the string
				TrimString(a, b, trimmed)

				' if it's a completely empty tag name, die
				If trimmed[0] = trimmed[1] Then AssertError "Empty tag."

				' check if the first character is a slash (end tag)
				If str[trimmed[0]] = ASC_SLASH Then
					' if no current element, die
					If thisE = Null Then AssertError("Closing tag found outside main document tag.")

					' strip the slash
					trimmed[0] += 1

					' check that the tag name length matches
					If trimmed[1] - trimmed[0] <> thisE.name.Length Then AssertError("Closing tag ~q"+str[trimmed[0]..trimmed[1]]+"~q does not match opening tag ~q"+thisE.name+"~q")

					' check that the tag name matches (manually so that we don't create an entire string slice when the first character could be wrong!)
					For Local nameIdx:Int = 0 Until thisE.name.Length
						If str[trimmed[0]+nameIdx] <> thisE.name[nameIdx] Then AssertError("Closing tag ~q"+str[trimmed[0]..trimmed[1]]+"~q does not match opening tag ~q"+thisE.name+"~q")
					Next

					' pop the element from the stack, or set the document root
					If Not elements.IsEmpty() Then
						thisE = elements.RemoveLast()
					Else
						'doc.root = thisE
						Exit
					End

				' check if the last character is a slash (self closing tag)
				ElseIf str[trimmed[1]-1] = ASC_SLASH Then
					' strip the slash
					trimmed[1] -= 1

					' create an element from the tag
					newE = GetTagContents(trimmed[0], trimmed[1])

					' add as a child or set as the root
					If doc.root = Null Then doc.root = newE
					If thisE <> Null Then
						thisE.AddChild(newE)
					Else
						'doc.root = newE
						Exit
					End
					newE = Null

				' otherwise it's an opening tag
				Else
					' create an element from the tag
					newE = GetTagContents(trimmed[0], trimmed[1])

					If doc.root = Null Then doc.root = newE

					' add as a child if we already have an element
					If thisE <> Null Then
						thisE.AddChild(newE)
					End

					' push this element
					elements.AddLast(thisE)

					' and set as the current
					thisE = newE
					newE = Null
				End
			End

			' get any text between tags
			index += 1
			If index < tagCount Then
				a = tags[index]+1
				b = tags[index+1]
				TrimString(a, b, trimmed)
				If trimmed[0] <> trimmed[1] Then
					If thisE <> Null Then
						thisE.value += UnescapeXMLString(str[trimmed[0]..trimmed[1]])
					Else
						'AssertError("Loose text outside of any tag!") - Getting this for some reason, I'll fix it later :/
					End
				End
			End

			' next tag
			index += 1
		End
		If doc.root = Null Then AssertError("Error parsing XML: no document tag found.")
		Return doc
	End
End

Class XMLDocument
Private
	Field xmlVersion:String = "1.0"
	Field xmlEncoding:String = "UTF-8"
	Field root:XMLElement
	Field pi:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field prologs:ArrayList<XMLElement> = New ArrayList<XMLElement>

Public

' Constructors
	Method New(rootName:String="")
		If rootName <> "" Then root = New XMLElement(rootName)
	End

	Method New(root:XMLElement)
		Self.root = root
	End

' Methods
	Method ExportString:String(formatXML:Bool = True)
		' we start with the xml instruction
		Local output:String = "<?xml version=~q"+xmlVersion+"~q encoding=~q"+xmlEncoding+"~q?>"
		If formatXML Then output += "~n"
		' root node
		output += root.ToString(formatXML) + "~n"
		' done!
		Return output
	End

	Method SaveFile:Void(filename:String)
		' TODO when file IO is ready!
	End

' Properties
	Method Root:XMLElement() Property
		Return root
	End

	Method Prologs:ArrayList<XMLElement>() Property
		Return prologs
	End

	Method ProcessingInstructions:ArrayList<XMLElement>() Property
		Return pi
	End

	Method Version:String() Property
		Return xmlVersion
	End

	Method Encoding:String() Property
		Return xmlEncoding
	End
End

Class XMLAttribute
Private
	Field name:String
	Field value:String

' Constructors
	Method New(name:String, value:String)
		Self.name = name
		Self.value = value
	End
End

Class XMLElement
Private
	Field openTagStart:String = "<"
	Field openTagEnd:String = ">"
	Field selfCloseEnd:String = " />"
	Field closeTagStart:String = "</"
	Field closeTagEnd:String = ">"

	Field name:String
	Field attributes:ArrayList<XMLAttribute> = New ArrayList<XMLAttribute>
	Field children:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field value:String
	Field parent:XMLElement

	Field pi:Bool
	Field prolog:Bool
	Field cdata:Bool

Public
' Constructors
	Method New()
	End

	Method New(name:String, parent:XMLElement = Null)
		Self.parent = parent
		Self.name = name
		If parent <> Null Then parent.children.Add(Self)
	End

' Methods
	Method IsProcessingInstruction:Bool()
		Return pi
	End

	Method IsProlog:Bool()
		Return prolog
	End

	Method IsCharacterData:Bool()
		Return cdata
	End

	' avoid using this method if you can, because you should try not to have "floating" elements
	Method AddChild:Void(child:XMLElement)
		If children.Contains(child) Return
		children.Add(child)
		child.parent = Self
	End

	Method HasAttribute:Bool(name:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then Return True
		Next
		Return False
	End

	Method GetAttribute:String(name:String, defaultValue:String = "")
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then Return att.value
		Next
		Return defaultValue
	End

	Method SetAttribute:String(name:String, value:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				Local old:String = att.value
				att.value = value
				Return old
			End
		Next
		attributes.Add(New XMLAttribute(name, value))
		Return ""
	End

	Method ClearAttribute:String(name:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				attributes.Remove(att)
				Return att.value
			End
		Next
		Return ""
	End

	Method Dispose:Void(removeSelf:Bool = True)
		' dispose children
		Local en:IEnumerator<XMLElement> = children.Enumerator()
		While en.HasNext()
			Local element:XMLElement = en.NextObject()
			element.Dispose(False)
			en.Remove()
		End
		' remove self from parent if this is not recursing
		If removeSelf And parent <> Null Then parent.children.Remove(Self)
		' clear out the parent
		parent = Null
	End

	Method ToString:String(formatXML:Bool=True, indentation:Int=0)
		Local rv:String = ""
		If formatXML Then
			For Local i% = 0 Until indentation
				rv += "  "
			Next
		End
		rv += openTagStart + name
		If Not attributes.IsEmpty() Then
			For Local i% = 0 Until attributes.Size
				Local att:XMLAttribute = attributes.Get(i)
				rv += " " + att.name + "=~q" + EscapeXMLString(att.value) + "~q"
			Next
		End
		If children.IsEmpty() Then
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() = 0
				rv += selfCloseEnd
			Else
				rv += openTagEnd + value.Trim() + closeTagStart + name + closeTagEnd
			End
		Else
			rv += openTagEnd
			If formatXML Then
				rv += "~n"
			End
			For Local i% = 0 Until children.Size
				rv += children.Get(i).ToString(formatXML, indentation + 1)
			End
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() > 0 Then
				If Not formatXML Then
					rv += esc
				Else
					rv += "~n" + esc + "~n"
				End
			End
			If formatXML Then
				For Local i% = 0 Until indentation
					rv += "  "
				Next
			End
			rv += closeTagStart + name + closeTagEnd
		End
		If formatXML Then
			rv += "~n"
		End
		Return rv
	End

	Method GetChildrenByName:ArrayList<XMLElement>(findName:String)
		Local rv:ArrayList<XMLElement> = New ArrayList<XMLElement>
		For Local element:XMLElement = Eachin children
			If element.name = findName Then rv.Add(element)
		Next
		Return rv
	End

	Method GetFirstChildByName:XMLElement(findName:String)
		For Local element:XMLElement = Eachin children
			If element.name = findName Then Return element
		Next
		Return Null
	End

' Properties
	Method Children:ArrayList<XMLElement>() Property
		Return children
	End

	Method Parent:XMLElement() Property
		Return parent
	End

	Method Name:String() Property
		Return name
	End

	Method Value:String() Property
		Return value
	End
End

' TODO: disabled escaping for performance, will fix these in the next commit

Function EscapeXMLString:String(str:String)
	'str = str.Replace("&", "&")
	'str = str.Replace("<", "<")
	'str = str.Replace(">", ">")
	'str = str.Replace("'", "'")
	'str = str.Replace("~q", """)
	Return str
End

Function UnescapeXMLString:String(str:String)
	'str = str.Replace(""", "~q")
	'str = str.Replace("'", "'")
	'str = str.Replace(">", ">")
	'str = str.Replace("<", "<")
	'str = str.Replace("&", "&")
	Return str
End

' Diddy XML parser
' This is a very simple XML parser, and expects a document to be as close to well-formed as possible.
' The only real leniency it has is that attributes may be defined without surrounding quotes/double quotes.
' All XML characters should be escaped in strings. > < etc.
' Currently it supports regular tag and attribute definitions, processing instructions, and skipping comments.
' For now, exported indentation is hardcoded to 2 spaces.
' Written from scratch, using as little string manipulation as possible.

Import assert
Import functions
Import collections

Class XMLParser
	Const TAG_DEFAULT:Int = 0
	Const TAG_COMMENT:Int = 1
	Const TAG_CDATA:Int = 2

	Field str:String
	Field tags:Int[] ' tag character indexes, alternating < and >
	Field tagType:Int[]
	Field tagCount:Int = 0
	Field tagsLength:Int
	Field quotes:Int[] ' quote character indexes, alternating opening and closing
	Field quoteCount:Int = 0
	Field quotesLength:Int
	Field pis:Int[] ' pi character indexes, alternating <? And ?> (index is on <>)
	Field piCount:Int = 0
	Field pisLength:Int

	Method CacheControlCharacters:Void()
		tagsLength = 128
		quotesLength = 128
		pisLength = 128
		tags = New Int[tagsLength]
		tagType = New Int[tagsLength]
		quotes = New Int[quotesLength]
		pis = New Int[quotesLength]
		tagCount = 0
		quoteCount = 0
		piCount = 0
		Local inTag:Bool = False
		Local inQuote:Bool = False
		Local inComment:Bool = False
		Local inCdata:Bool = False
		Local inPi:Bool = False
		Local strlen:Int = str.Length
		For Local i:Int = 0 Until strlen
			' if we're in a comment, we're only looking for -->
			If inComment Then
				If str[i] = ASC_GREATER_THAN And str[i-1] = ASC_HYPHEN And str[i-2] = ASC_HYPHEN Then
					If tagCount = tagsLength Then
						tagsLength *= 2
						tags = tags.Resize(tagsLength)
						tagType = tagType.Resize(tagsLength)
					End
					tags[tagCount] = i
					tagType[tagCount] = TAG_COMMENT
					tagCount += 1
					inComment = False
				End
			' if we're in a cdata, we're only looking for ]]>
			ElseIf inCdata Then
				If str[i] = ASC_GREATER_THAN And str[i-1] = ASC_CLOSE_BRACKET And str[i-2] = ASC_CLOSE_BRACKET Then
					If tagCount = tagsLength Then
						tagsLength *= 2
						tags = tags.Resize(tagsLength)
						tagType = tagType.Resize(tagsLength)
					End
					tags[tagCount] = i
					tagType[tagCount] = TAG_CDATA
					tagCount += 1
					inCdata = False
				End
			' if we're in a processing instruction, we're only looking for ?>
			ElseIf inPi Then
				If str[i] = ASC_GREATER_THAN And str[i-1] = ASC_QUESTION Then
					If piCount = pisLength Then
						pisLength *= 2
						pis = pis.Resize(pisLength)
					End
					pis[piCount] = i
					piCount += 1
					inPi = False
				End
			' if we're in a quoted string, we're only looking for "
			ElseIf inQuote Then
				If str[i] = ASC_DOUBLE_QUOTE Then
					If quoteCount = quotesLength Then
						quotesLength *= 2
						quotes = quotes.Resize(quotesLength)
					End
					quotes[quoteCount] = i
					quoteCount += 1
					inQuote = False
				End
			' check if we should start a new quoted string
			ElseIf str[i] = ASC_DOUBLE_QUOTE Then
				If quoteCount = quotesLength Then
					quotesLength *= 2
					quotes = quotes.Resize(quotesLength)
				End
				quotes[quoteCount] = i
				quoteCount += 1
				inQuote = True
			' less than
			ElseIf str[i] = ASC_LESS_THAN Then
				' if we're in a tag, die
				If inTag Then AssertError "Invalid less than!"
				' check for prolog
				If str[i+1] = ASC_EXCLAMATION Then
					' comment?
					If str[i+2] = ASC_HYPHEN And str[i+3] = ASC_HYPHEN Then
						If tagCount = tagsLength Then
							tagsLength *= 2
							tags = tags.Resize(tagsLength)
							tagType = tagType.Resize(tagsLength)
						End
						tags[tagCount] = i
						tagType[tagCount] = TAG_COMMENT
						tagCount += 1
						inComment = True
					' cdata?
					ElseIf str[i+2] = ASC_OPEN_BRACKET And
							str[i+3] = ASC_UPPER_C And
							str[i+4] = ASC_UPPER_D And
							str[i+5] = ASC_UPPER_A And
							str[i+6] = ASC_UPPER_T And
							str[i+7] = ASC_UPPER_A And
							str[i+8] = ASC_OPEN_BRACKET Then
						If tagCount = tagsLength Then
							tagsLength *= 2
							tags = tags.Resize(tagsLength)
							tagType = tagType.Resize(tagsLength)
						End
						tags[tagCount] = i
						tagType[tagCount] = TAG_CDATA
						tagCount += 1
						inCdata = True
					Else
						AssertError "Invalid prolog."
					End
				' check for processing instruction
				ElseIf str[i+1] = ASC_QUESTION Then
					If piCount = pisLength Then
						pisLength *= 2
						pis = pis.Resize(pisLength)
					End
					pis[piCount] = i
					piCount += 1
					inPi = True
				' finally, it must just be opening a tag
				Else
					If tagCount = tagsLength Then
						tagsLength *= 2
						tags = tags.Resize(tagsLength)
						tagType = tagType.Resize(tagsLength)
					End
					tags[tagCount] = i
					tagType[tagCount] = TAG_DEFAULT
					tagCount += 1
					inTag = True
				End
			' greater than
			ElseIf str[i] = ASC_GREATER_THAN Then
				If Not inTag Then AssertError "Invalid greater than!"
				If tagCount = tagsLength Then
					tagsLength *= 2
					tags = tags.Resize(tagsLength)
					tagType = tagType.Resize(tagsLength)
				End
				tags[tagCount] = i
				tagType[tagCount] = TAG_DEFAULT
				tagCount += 1
				inTag = False
			End
		Next
		If inQuote Then AssertError "Unclosed quote!"
		If inTag Then AssertError "Unclosed tag!"
		If inComment Then AssertError "Unclosed comment!"
		If inCdata Then AssertError "Unclosed cdata!"
		If inPi Then AssertError "Unclosed processing instruction!"
	End

	' get the contents of a tag (given start and end)
	Method GetTagContents:XMLElement(startIndex:Int, endIndex:Int)
		' die if empty tag
		If startIndex = endIndex Then AssertError("Empty tag detected.")
		' our element
		Local e:XMLElement = New XMLElement
		Local a:Int, singleQuoted:Bool, doubleQuoted:Bool, key:String, value:String

		' get the name
		a = startIndex
		While a < endIndex
			If str[a]=ASC_SPACE Or str[a]=ASC_TAB Or str[a]=ASC_LF Or str[a]=ASC_CR Or a = endIndex-1 Then
				If a = endIndex-1 Then
					e.name = str[startIndex..endIndex]
				Else
					e.name = str[startIndex..a]
				End
				a += 1
				Exit
			End
			a += 1
		End
		startIndex = a

		' TODO: validate tag name is alphanumeric
		' if no name, die
		If e.name = "" Then AssertError("Error reading tag name.")

		' loop on all tokens
		While startIndex < endIndex
			' trim leading whitespace
			While startIndex < endIndex And (str[startIndex]=ASC_SPACE Or str[startIndex]=ASC_TAB Or str[startIndex]=ASC_LF Or str[startIndex]=ASC_CR)
				startIndex += 1
			End

			' clear check variables
			singleQuoted = False
			doubleQuoted = False
			key = ""
			value = ""

			' find the key
			a = startIndex
			While a < endIndex
				If str[a] = ASC_EQUALS Or str[a]=ASC_SPACE Or str[a]=ASC_TAB Or str[a]=ASC_LF Or str[a]=ASC_CR Or a = endIndex-1 Then
					If a=endIndex-1 Then
						key = str[startIndex..endIndex]
					Else
						key = str[startIndex..a]
					End
					a += 1
					Exit
				End
				a += 1
			End
			startIndex = a

			' if the key is empty, there was an error (unless we've hit the end of the string)
			If key = "" Then
				If a < endIndex Then
					AssertError("Error reading attribute key.")
				Else
					Exit
				End
			End

			' if it stopped on an equals, get the value
			If str[a-1] = ASC_EQUALS Then
				singleQuoted = False
				doubleQuoted = False
				While a < endIndex
					' check if it's a single quote
					If str[a] = ASC_SINGLE_QUOTE And Not doubleQuoted Then
						' if this is the first index, mark it as quoted
						If a = startIndex Then
							singleQuoted = True
						' otherwise, if we're not quoted at all, die
						ElseIf Not singleQuoted And Not doubleQuoted Then
							AssertError("Unexpected single quote detected in attribute value.")
						Else
							' we must be ending the quote here, so grab it and break out
							singleQuoted = False
							value = str[startIndex+1..a]
							a += 1
							Exit
						End

					' check if it's a double quote
					ElseIf str[a] = ASC_DOUBLE_QUOTE And Not singleQuoted Then
						' if this is the first index, mark it as quoted
						If a = startIndex Then
							doubleQuoted = True
						' otherwise, if we're not quoted at all, die
						ElseIf Not singleQuoted And Not doubleQuoted Then
							AssertError("Unexpected double quote detected in attribute value.")
						Else
							' we must be ending the quote here, so break out
							doubleQuoted = False
							value = str[startIndex+1..a]
							a += 1
							Exit
						End

					' should we be ending the attribute?
					ElseIf a = endIndex-1 Or (Not singleQuoted And Not doubleQuoted And (str[a]=ASC_SPACE Or str[a]=ASC_TAB Or str[a]=ASC_LF Or str[a]=ASC_CR))
						If a=endIndex-1 Then
							value = str[startIndex..endIndex]
						Else
							value = str[startIndex..a]
						End
						a += 1
						Exit
					End
					a += 1
				End
				startIndex = a
				value = UnescapeXMLString(value)

				If singleQuoted Or doubleQuoted Then AssertError("Unclosed quote detected.")
			End

			' set the attribute
			e.SetAttribute(key, value)

			If a >= endIndex Then Exit
		End
		Return e
	End

	Method ParseFile:XMLDocument(filename:String)
		Return ParseString(LoadString(filename))
	End

	Method TrimString:Void(startIdx:Int, endIdx:Int, trimmed:Int[])
		Local trimStart:Int = startIdx, trimEnd:Int = endIdx
		While trimEnd > trimStart
			Local ch:Int = str[trimEnd-1]
			If ch = ASC_CR Or ch = ASC_LF Or ch = ASC_SPACE Or ch = ASC_TAB Then
				trimEnd -= 1
			Else
				Exit
			End
		End
		While trimStart < trimEnd
			Local ch:Int = str[trimStart]
			If ch = ASC_CR Or ch = ASC_LF Or ch = ASC_SPACE Or ch = ASC_TAB Then
				trimStart += 1
			Else
				Exit
			End
		End
		trimmed[0] = trimStart
		trimmed[1] = trimEnd
	End

	' parses an xml doc, currently doesn't support nested PI or prolog
	Method ParseString:XMLDocument(str:String)
		Self.str = str

		Local doc:XMLDocument = New XMLDocument
		Local elements:ArrayList<XMLElement> = New ArrayList<XMLElement>
		Local thisE:XMLElement = Null, newE:XMLElement = Null
		Local index:Int = 0, a:Int, b:Int, c:Int, nextIndex:Int
		Local trimmed:Int[] = New Int[2]

		' cache all the control characters
		CacheControlCharacters()

		' find first opening tag
		If tagCount = 0 Then AssertError "Something seriously wrong... no tags!"

		' parse processing instructions
		index = 0
		a = pis[index]+2
		b = pis[index+1]-1
		While index < piCount
			TrimString(a, b, trimmed)
			If trimmed[0] <> trimmed[1] Then
				newE = GetTagContents(trimmed[0], trimmed[1])
				newE.pi = True
				doc.pi.Add(newE)
				newE = Null
			Else
				AssertError "Empty processing instruction."
			End
			index += 2
		End

		' loop on tags
		index = 0
		While index+1 < tagCount
			' we skip comments
			If tagType[index] = TAG_COMMENT Then
				' skip comments

			' if it's cdata
			ElseIf tagType[index] = TAG_CDATA Then
				' get the text between < and >
				a = tags[index]+9 ' "![CDATA[".Length
				b = tags[index+1]-2 ' "]]".Length

				' TODO: add a cdata element

			' otherwise we do normal tag stuff
			Else
				' get the text between < and >
				a = tags[index]+1
				b = tags[index+1]

				' trim the string
				TrimString(a, b, trimmed)

				' if it's a completely empty tag name, die
				If trimmed[0] = trimmed[1] Then AssertError "Empty tag."

				' check if the first character is a slash (end tag)
				If str[trimmed[0]] = ASC_SLASH Then
					' if no current element, die
					If thisE = Null Then AssertError("Closing tag found outside main document tag.")

					' strip the slash
					trimmed[0] += 1

					' check that the tag name length matches
					If trimmed[1] - trimmed[0] <> thisE.name.Length Then AssertError("Closing tag ~q"+str[trimmed[0]..trimmed[1]]+"~q does not match opening tag ~q"+thisE.name+"~q")

					' check that the tag name matches (manually so that we don't create an entire string slice when the first character could be wrong!)
					For Local nameIdx:Int = 0 Until thisE.name.Length
						If str[trimmed[0]+nameIdx] <> thisE.name[nameIdx] Then AssertError("Closing tag ~q"+str[trimmed[0]..trimmed[1]]+"~q does not match opening tag ~q"+thisE.name+"~q")
					Next

					' pop the element from the stack, or set the document root
					If Not elements.IsEmpty() Then
						thisE = elements.RemoveLast()
					Else
						'doc.root = thisE
						Exit
					End

				' check if the last character is a slash (self closing tag)
				ElseIf str[trimmed[1]] = ASC_SLASH Then
					' strip the slash
					trimmed[1] -= 1

					' create an element from the tag
					newE = GetTagContents(trimmed[0], trimmed[1])

					' add as a child or set as the root
					If doc.root = Null Then doc.root = newE
					If thisE <> Null Then
						thisE.AddChild(newE)
					Else
						'doc.root = newE
						Exit
					End
					newE = Null

				' otherwise it's an opening tag
				Else
					' create an element from the tag
					newE = GetTagContents(trimmed[0], trimmed[1])

					If doc.root = Null Then doc.root = newE

					' add as a child if we already have an element
					If thisE <> Null Then
						thisE.AddChild(newE)
					End

					' push this element
					elements.AddLast(thisE)

					' and set as the current
					thisE = newE
					newE = Null
				End
			End

			' get any text between tags
			index += 1
			If index < tagCount Then
				a = tags[index]+1
				b = tags[index+1]
				TrimString(a, b, trimmed)
				If trimmed[0] <> trimmed[1] Then
					If thisE <> Null Then
						thisE.value += UnescapeXMLString(str[trimmed[0]..trimmed[1]])
					Else
						'AssertError("Loose text outside of any tag!") - Getting this for some reason, I'll fix it later :/
					End
				End
			End

			' next tag
			index += 1
		End
		If doc.root = Null Then AssertError("Error parsing XML: no document tag found.")
		Return doc
	End
End

Class XMLDocument
Private
	Field xmlVersion:String = "1.0"
	Field xmlEncoding:String = "UTF-8"
	Field root:XMLElement
	Field pi:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field prologs:ArrayList<XMLElement> = New ArrayList<XMLElement>

Public

' Constructors
	Method New(rootName:String="")
		If rootName <> "" Then root = New XMLElement(rootName)
	End

	Method New(root:XMLElement)
		Self.root = root
	End

' Methods
	Method ExportString:String(formatXML:Bool = True)
		' we start with the xml instruction
		Local output:String = "<?xml version=~q"+xmlVersion+"~q encoding=~q"+xmlEncoding+"~q?>"
		If formatXML Then output += "~n"
		' root node
		output += root.ToString(formatXML) + "~n"
		' done!
		Return output
	End

	Method SaveFile:Void(filename:String)
		' TODO when file IO is ready!
	End

' Properties
	Method Root:XMLElement() Property
		Return root
	End

	Method Prologs:ArrayList<XMLElement>() Property
		Return prologs
	End

	Method ProcessingInstructions:ArrayList<XMLElement>() Property
		Return pi
	End

	Method Version:String() Property
		Return xmlVersion
	End

	Method Encoding:String() Property
		Return xmlEncoding
	End
End

Class XMLAttribute
Private
	Field name:String
	Field value:String

' Constructors
	Method New(name:String, value:String)
		Self.name = name
		Self.value = value
	End
End

Class XMLElement
Private
	Field openTagStart:String = "<"
	Field openTagEnd:String = ">"
	Field selfCloseEnd:String = " />"
	Field closeTagStart:String = "</"
	Field closeTagEnd:String = ">"

	Field name:String
	Field attributes:ArrayList<XMLAttribute> = New ArrayList<XMLAttribute>
	Field children:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field value:String
	Field parent:XMLElement

	Field pi:Bool
	Field prolog:Bool
	Field cdata:Bool

Public
' Constructors
	Method New()
	End

	Method New(name:String, parent:XMLElement = Null)
		Self.parent = parent
		Self.name = name
		If parent <> Null Then parent.children.Add(Self)
	End

' Methods
	Method IsProcessingInstruction:Bool()
		Return pi
	End

	Method IsProlog:Bool()
		Return prolog
	End

	Method IsCharacterData:Bool()
		Return cdata
	End

	' avoid using this method if you can, because you should try not to have "floating" elements
	Method AddChild:Void(child:XMLElement)
		If children.Contains(child) Return
		children.Add(child)
		child.parent = Self
	End

	Method HasAttribute:Bool(name:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then Return True
		Next
		Return False
	End

	Method GetAttribute:String(name:String, defaultValue:String = "")
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then Return att.value
		Next
		Return defaultValue
	End

	Method SetAttribute:String(name:String, value:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				Local old:String = att.value
				att.value = value
				Return old
			End
		Next
		attributes.Add(New XMLAttribute(name, value))
		Return ""
	End

	Method ClearAttribute:String(name:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				attributes.Remove(att)
				Return att.value
			End
		Next
		Return ""
	End

	Method Dispose:Void(removeSelf:Bool = True)
		' dispose children
		Local en:IEnumerator<XMLElement> = children.Enumerator()
		While en.HasNext()
			Local element:XMLElement = en.NextObject()
			element.Dispose(False)
			en.Remove()
		End
		' remove self from parent if this is not recursing
		If removeSelf And parent <> Null Then parent.children.Remove(Self)
		' clear out the parent
		parent = Null
	End

	Method ToString:String(formatXML:Bool=True, indentation:Int=0)
		Local rv:String = ""
		If formatXML Then
			For Local i% = 0 Until indentation
				rv += "  "
			Next
		End
		rv += openTagStart + name
		If Not attributes.IsEmpty() Then
			For Local i% = 0 Until attributes.Size
				Local att:XMLAttribute = attributes.Get(i)
				rv += " " + att.name + "=~q" + EscapeXMLString(att.value) + "~q"
			Next
		End
		If children.IsEmpty() Then
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() = 0
				rv += selfCloseEnd
			Else
				rv += openTagEnd + value.Trim() + closeTagStart + name + closeTagEnd
			End
		Else
			rv += openTagEnd
			If formatXML Then
				rv += "~n"
			End
			For Local i% = 0 Until children.Size
				rv += children.Get(i).ToString(formatXML, indentation + 1)
			End
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() > 0 Then
				If Not formatXML Then
					rv += esc
				Else
					rv += "~n" + esc + "~n"
				End
			End
			If formatXML Then
				For Local i% = 0 Until indentation
					rv += "  "
				Next
			End
			rv += closeTagStart + name + closeTagEnd
		End
		If formatXML Then
			rv += "~n"
		End
		Return rv
	End

	Method GetChildrenByName:ArrayList<XMLElement>(findName:String)
		Local rv:ArrayList<XMLElement> = New ArrayList<XMLElement>
		For Local element:XMLElement = Eachin children
			If element.name = findName Then rv.Add(element)
		Next
		Return rv
	End

	Method GetFirstChildByName:XMLElement(findName:String)
		For Local element:XMLElement = Eachin children
			If element.name = findName Then Return element
		Next
		Return Null
	End

' Properties
	Method Children:ArrayList<XMLElement>() Property
		Return children
	End

	Method Parent:XMLElement() Property
		Return parent
	End

	Method Name:String() Property
		Return name
	End

	Method Value:String() Property
		Return value
	End
End

' TODO: disabled escaping for performance, will fix these in the next commit

Function EscapeXMLString:String(str:String)
	'str = str.Replace("&", "&")
	'str = str.Replace("<", "<")
	'str = str.Replace(">", ">")
	'str = str.Replace("'", "'")
	'str = str.Replace("~q", """)
	Return str
End

Function UnescapeXMLString:String(str:String)
	'str = str.Replace(""", "~q")
	'str = str.Replace("'", "'")
	'str = str.Replace(">", ">")
	'str = str.Replace("<", "<")
	'str = str.Replace("&", "&")
	Return str
End


Added SetMouse for GLFW

Added SetMouse for GLFW

' force strict coding standard
Strict

' import the diddy module
Import diddy

' the starting point for a Monkey app
Function Main:Int()
	' create the "game"
	New MyGame
	Return 0
End

' MyGame
Class MyGame Extends DiddyApp
	' save the starting seed
	Field startingSeed:Int
	Field mouseOn:Bool = true

	Method OnCreate:Int()
		Super.OnCreate()
		' set the seed to use the system time
		startingSeed = RealMillisecs()
		Seed = startingSeed
		ShowMouse()

		Return 0
	End

	Method OnUpdate:Int()
		If KeyHit(KEY_ENTER)
			If mouseOn
				HideMouse()
				mouseOn = False
			Else
				ShowMouse()
				mouseOn = True
			End
		End

		If KeyHit(KEY_ESCAPE)
			ExitApp()
		End

		' this will print "No FlushKeys 3 times
		For Local i:Int = 1 To 3
			If KeyHit(KEY_SPACE)
				Print "No FlushKeys"
			End
		Next
		' this will print "FlushKeys once
		For Local i:Int = 1 To 3
			If KeyHit(KEY_SPACE)
				Print "FlushKeys"
				' force the seed to change
				Rnd(0,1000)
				SetMouse(100, 100)
			End
			' clear the key hits
			FlushKeys()
		Next
		If Not mouseOn
			if (MouseX() < 0 or MouseX() > DEVICE_WIDTH or MouseY()< 0 or MouseY() > DEVICE_HEIGHT)
				ShowMouse()
				SetMouse(MouseX(), MouseY())
			Else
				HideMouse()
			End
		End
		Return 0
	End

	Method OnRender:Int()
		Cls
		DrawText "Starting Seed = "+startingSeed, 10, 10
		DrawText "Seed          = "+Seed, 10, 20
		DrawText "RealMillisecs = "+RealMillisecs(), 10, 30
		DrawText "Millisecs     = "+Millisecs(), 10, 40
		If mouseOn
			DrawText "Mouse On      = true (press enter to toggle)", 10, 50
		Else
			DrawText "Mouse On      = false (press enter to toggle)", 10, 50
		End
		DrawText "UpdateRate    = "+GetUpdateRate(), 10, 60
		DrawText "MouseX        = "+MouseX(), 10, 70
		DrawText "MouseY        = "+MouseY(), 10, 80
		DrawText "DEVICE_WIDTH  = "+DEVICE_WIDTH, 10, 90
		DrawText "DEVICE_HEIGHT = "+DEVICE_HEIGHT, 10, 100

		Return 0
	End

End

' force strict coding standard
Strict

' import the diddy module
Import diddy

' the starting point for a Monkey app
Function Main:Int()
	' create the "game"
	New MyGame
	Return 0
End

' MyGame
Class MyGame Extends DiddyApp
	' save the starting seed
	Field startingSeed:Int
	Field mouseOn:Bool = false

	Method OnCreate:Int()
		' set the seed to use the system time
		startingSeed = RealMillisecs()
		Seed = startingSeed
		HideMouse()

		' 60 FPS please
		SetUpdateRate 60

		Return 0
	End

	Method OnUpdate:Int()
		If KeyHit(KEY_ENTER)
			If mouseOn
				HideMouse()
				mouseOn = False
			Else
				ShowMouse()
				mouseOn = True
			End
		End

		If KeyHit(KEY_ESCAPE)
			ExitApp()
		End

		' this will print "No FlushKeys 3 times
		For Local i:Int = 1 To 3
			If KeyHit(KEY_SPACE)
				Print "No FlushKeys"
			End
		Next
		' this will print "FlushKeys once
		For Local i:Int = 1 To 3
			If KeyHit(KEY_SPACE)
				Print "FlushKeys"
				' force the seed to change
				Rnd(0,1000)
			End
			' clear the key hits
			FlushKeys()
		Next

		Return 0
	End

	Method OnRender:Int()
		Cls
		DrawText "Starting Seed = "+startingSeed, 10, 10
		DrawText "Seed          = "+Seed, 10, 20
		DrawText "RealMillisecs = "+RealMillisecs(), 10, 30
		DrawText "Millisecs     = "+Millisecs(), 10, 40
		If mouseOn
			DrawText "Mouse On      = true (press enter to toggle)", 10, 50
		Else
			DrawText "Mouse On      = false (press enter to toggle)", 10, 50
		End
		DrawText "UpdateRate    = "+GetUpdateRate(), 10, 60
		Return 0
	End

End


base exception class improved

base exception class improved

* detect the current class name (with reflection!)
* improved report via ToString()
* properties for message, className and classNameFile

Strict

Private

#REFLECTION_FILTER="*exception"
Import reflection

Public

Class Exception Extends Throwable
    Private

    Field _message:String
    Field _classNameFull:String
    Field _className:String

    Public

    Method New(message:String)
        _message = message

        Local ci:ClassInfo = GetClass(Object(Self))
        If ci
            _classNameFull = ci.Name
            If _classNameFull.Contains(".")
                _className = _classNameFull[_classNameFull.FindLast(".") + 1..]
            End
        Else
            _classNameFull = "--UNKNOWN--"
            _className = _classNameFull
        End
    End

    Method ToString:String()
        Return "Uncaught exception '" + _className + "' with message:~n" +
            _message + "~n" +
            "~nStack trace:"
    End

    Method className:String() Property
        Return _className
    End

    Method classNameFull:String() Property
        Return _classNameFull
    End

    Method message:String() Property
        Return _message
    End
End

Strict

Public

Class Exception Extends Throwable
    Private

    Field message:String

    Public

    Method New(message:String)
        Self.message = message
    End

    Method ToString:String()
        Return message
    End
End


Adding detail error messages for HTML5 and Flash

#Print "Target: ${TARGET} Lang: ${LANG}"

#If (LANG="cpp" And TARGET<>"win8") Or LANG="java" Or TARGET="xna"

	Import brl.stream
	Import tcpsocket
	#If LANG="cpp" Or LANG="java"
		Import "native/tcpserver.${LANG}"
	#Else
		Import "native/tcpserver.${TARGET}.${LANG}"
	#End

	Extern

	Class BBTcpServer

		Method SetupListen:Bool(port:Int)
		Method CheckConnection:Bool()
		Method SetupSocket:Bool(bbTcpSocket:BBTcpSocket)

	End

	Public

	Class TcpServer

		Method New()
			_server=New BBTcpServer
		End

		Method SetupListen:Bool(port:Int)
			Return _server.SetupListen(port)
		End

		Method CheckConnection:TcpSocket()

			If _server.CheckConnection() = True Then

				Local tcpSocket:TcpSocket = New TcpSocket()

				_server.SetupSocket(tcpSocket.GetBBTcpSocket())

				Return tcpSocket

			End If

			Return Null

		End

		'***** INTERNAL *****
		Method GetBBTcpServer:BBTcpServer()
			Return _server
		End

		Private

		Field _server:BBTcpServer

	End

#Else If TARGET="html5"

	#Error "PushButton Networking server is not available on HTML5, because HTML5 can not listen for connection."

#Else If TARGET="flash"

	#Error "PushButton Networking server is not available on Flash, because Flash can not listen for connection."

#Else

	#Error "PushButton Networking is not available on this target."

#End

#Print "Target: ${TARGET} Lang: ${LANG}"

#If (LANG="cpp" And TARGET<>"win8") Or LANG="java" Or TARGET="xna"

	Import brl.stream
	Import tcpsocket
	#If LANG="cpp" Or LANG="java"
		Import "native/tcpserver.${LANG}"
	#Else
		Import "native/tcpserver.${TARGET}.${LANG}"
	#End

	Extern

	Class BBTcpServer

		Method SetupListen:Bool(port:Int)
		Method CheckConnection:Bool()
		Method SetupSocket:Bool(bbTcpSocket:BBTcpSocket)

	End

	Public

	Class TcpServer

		Method New()
			_server=New BBTcpServer
		End

		Method SetupListen:Bool(port:Int)
			Return _server.SetupListen(port)
		End

		Method CheckConnection:TcpSocket()

			If _server.CheckConnection() = True Then

				Local tcpSocket:TcpSocket = New TcpSocket()

				_server.SetupSocket(tcpSocket.GetBBTcpSocket())

				Return tcpSocket

			End If

			Return Null

		End

		'***** INTERNAL *****
		Method GetBBTcpServer:BBTcpServer()
			Return _server
		End

		Private

		Field _server:BBTcpServer

	End

#Else

	#Error "PushButton Networking is not available on this target."

#End


Added FlxObject separate invoker

Added FlxObject separate invoker

Strict

#rem
	header:This module contains the FlxObject class.
#end
Import flxbasic
Import flxpoint
Import flxpath
Import flxgroup
Import flxcamera
Import flxtilemap
Import flxg
Import flxu

Import plugin.monkey.flxcolor

#Rem
summary:This is the base class for most of the display objects (FlxSprite, FlxText, etc).
It includes some basic attributes about game objects, including retro-style flickering,
basic state information, sizes, scrolling, and basic physics and motion.
#End
Class FlxObject Extends FlxBasic

	Global CLASS_OBJECT:FlxClass = new FlxObjectClass()

	Global X_COMPARATOR:FlxBasicComparator = new FlxObjectXComparator()

	Global Y_COMPARATOR:FlxBasicComparator = new FlxObjectYComparator()

	'summary:Generic value for "left" Used by facing, allowCollisions, and touching.
	Const LEFT:Int = 1

	'summary:Generic value for "right" Used by facing, allowCollisions, and touching.
	Const RIGHT:Int = 2

	'summary:Generic value for "up" Used by facing, allowCollisions, and touching.
	Const UP:Int = 4

	'summary:Generic value for "down" Used by facing, allowCollisions, and touching.
	Const DOWN:Int = 8

	'summary:Special-case constant meaning no collisions, used mainly by allowCollisions and touching.
	Const NONE:Int = 0

	'summary:Special-case constant meaning up, used mainly by allowCollisions and touching.
	Const CEILING:Int = UP

	'summary:Special-case constant meaning down, used mainly by allowCollisions and touching.
	Const FLOOR:Int = DOWN

	'summary:Special-case constant meaning only the left and right sides, used mainly by allowCollisions and touching.
	Const WALL:Int = LEFT | RIGHT

	'summary:Special-case constant meaning any direction, used mainly by allowCollisions and touching.
	Const ANY:Int = LEFT | RIGHT | UP | DOWN

	'summary:Handy constant used during collision resolution (see separateX() and separateY()).
	Const OVERLAP_BIAS:Int = 4

	'summary:Path behavior controls: move from the start of the path to the end then stop.
	Const PATH_FORWARD:Int = 0

	'summary:Path behavior controls: move from the end of the path to the start then stop.
	Const PATH_BACKWARD:Int = 1

	'summary:Path behavior controls: move from the start of the path to the end then directly back to the start, and start over.
	Const PATH_LOOP_FORWARD:Int = 2

	'summary:Path behavior controls: move from the end of the path to the start then directly back to the end, and start over.
	Const PATH_LOOP_BACKWARD:Int = 4

	'summary:Path behavior controls: move from the start of the path to the end then turn around and go back to the start, over and over.
	Const PATH_YOYO:Int = 8

	'summary:Path behavior controls: ignores any vertical component to the path data, only follows side to side.
	Const PATH_HORIZONTAL_ONLY:Int = 16

	'summary:Path behavior controls: ignores any horizontal component to the path data, only follows up and down.
	Const PATH_VERTICAL_ONLY:Int = 32

	'summary:X position of the upper left corner of this object in world space.
	Field x:Float

	'summary:Y position of the upper left corner of this object in world space.
	Field y:Float

	'summary:The width of this object.
	Field width:Float

	'summary:The height of this object.
	Field height:Float

	'summary:Whether an object will move/alter position after a collision.
	Field immovable:Bool

	Field velocity:FlxPoint

	Field mass:Float

	Field elasticity:Float

	Field acceleration:FlxPoint

	Field drag:FlxPoint

	Field maxVelocity:FlxPoint

	Field angle:Float

	Field angularVelocity:Float

	Field angularAcceleration:Float

	Field angularDrag:Float

	Field maxAngular:Float

	Field scrollFactor:FlxPoint

	Field health:Float

	Field moves:Bool

	Field touching:Int

	Field wasTouching:Int

	Field allowCollisions:Int

	Field last:FlxPoint

	Field path:FlxPath

	Field pathSpeed:Float

	Field pathAngle:Float

Private
	Global _pZero:FlxPoint = New FlxPoint()

	Field _flicker:Bool

	Field _flickerTimer:Float

	Field _point:FlxPoint

	Field _rect:FlxRect

	Field _pathNodeIndex:Int

	Field _pathMode:Int

	Field _pathInc:Int

	Field _pathRotate:Bool

	Field _debugBoundingBoxColor:FlxColor

Public
	Method New(x:Float, y:Float, width:Float = 0, height:Float = 0)
		Self.x = x
		Self.y = y
		last = New FlxPoint(x, y)
		Self.width = width
		Self.height = height
		mass = 1.0
		elasticity = 0.0

		health = 1

		immovable = False
		moves = True

		touching = NONE
		wasTouching = NONE
		allowCollisions = ANY

		velocity = New FlxPoint()
		acceleration = New FlxPoint()
		drag = New FlxPoint()
		maxVelocity = New FlxPoint(10000, 10000)

		angle = 0
		angularVelocity = 0
		angularAcceleration = 0
		angularDrag = 0
		maxAngular = 10000

		scrollFactor = New FlxPoint(1.0, 1.0)
		_flicker = False
		_flickerTimer = 0

		_point = New FlxPoint()
		_rect = New	FlxRect()

		path = Null
		pathSpeed = 0
		pathAngle = 0
	End Method

	Method Destroy:Void()
		velocity = Null
		acceleration = Null
		drag = Null
		maxVelocity = Null
		scrollFactor = Null
		_point = Null
		_rect = Null
		last = Null
		cameras = Null
		If (path <> Null) path.Destroy()
		path = Null
		_debugBoundingBoxColor = Null
	End Method

	Method PreUpdate:Void()
		_ACTIVECOUNT += 1

		If (_flickerTimer <> 0) Then
			If (_flickerTimer > 0) Then
				_flickerTimer -= FlxG.elapsed
				If (_flickerTimer <= 0) Then
					_flickerTimer = 0
					_flicker = False
				End If
			End If
		End If

		last.x = x
		last.y = y

		If (path <> Null And pathSpeed <> 0 And path.nodes.Get(_pathNodeIndex) <> Null) Then
			_UpdatePathMotion()
		End If
	End Method

	Method PostUpdate:Void()
		If (moves) _UpdateMotion()

		wasTouching = touching
		touching = NONE
	End Method

	Method Draw:Void()
		If (Not OnScreen(FlxG._currentCamera)) Return

		_VISIBLECOUNT += 1
		If (FlxG.visualDebug And Not ignoreDrawDebug) DrawDebug(FlxG._currentCamera)
	End Method

	Method DrawDebug:Void(camera:FlxCamera = Null)
		Local boundingBoxX:Float = x - Int(camera.scroll.x * scrollFactor.x)
		Local boundingBoxY:Float = y - Int(camera.scroll.y * scrollFactor.y)

		If (boundingBoxX > 0) Then
			boundingBoxX += 0.0000001
		Else
			boundingBoxX -= 0.0000001
		End If

		If (boundingBoxY > 0) Then
			boundingBoxY += 0.0000001
		Else
			boundingBoxY -= 0.0000001
		End If

		Local boundingBoxWidth:Int
		Local boundingBoxHeight:Int

		If (width <> Int(width)) Then
			boundingBoxWidth = width
		Else
			boundingBoxWidth = width - 1
		End If

		If (height <> Int(height)) Then
			boundingBoxHeight = height
		Else
			boundingBoxHeight = height - 1
		End If

		If (_debugBoundingBoxColor = Null) _debugBoundingBoxColor = New FlxColor()
		If (allowCollisions) Then
			If (allowCollisions <> ANY) Then
				_debugBoundingBoxColor.SetARGB(FlxG.PINK)
			ElseIf (immovable)
				_debugBoundingBoxColor.SetARGB(FlxG.GREEN)
			Else
				_debugBoundingBoxColor.SetARGB(FlxG.RED)
			End if
		Else
			_debugBoundingBoxColor.SetARGB(FlxG.BLUE)
		End If

		SetAlpha(.5)
		SetColor(_debugBoundingBoxColor.r, _debugBoundingBoxColor.g, _debugBoundingBoxColor.b)

		DrawLine(boundingBoxX, boundingBoxY, boundingBoxX + boundingBoxWidth, boundingBoxY)
		DrawLine(boundingBoxX + boundingBoxWidth, boundingBoxY, boundingBoxX + boundingBoxWidth ,boundingBoxY + boundingBoxHeight)
		DrawLine(boundingBoxX + boundingBoxWidth ,boundingBoxY + boundingBoxHeight, boundingBoxX, boundingBoxY + boundingBoxHeight)
		DrawLine(boundingBoxX, boundingBoxY + boundingBoxHeight, boundingBoxX, boundingBoxY)

		FlxG._lastDrawingColor = _debugBoundingBoxColor.argb
		SetAlpha(1)
	End Method

	Method FollowPath:Void(path:FlxPath, speed:Float = 100, mode:Int = PATH_FORWARD, autoRotate:Bool = False)
		If (path.nodes.Length() <= 0) Then
			FlxG.Log("WARNING: Paths need at least one node in them to be followed.")
			Return
		End If

		Self.path = path
		pathSpeed = Abs(speed)
		_pathMode = mode
		_pathRotate = autoRotate

		If (_pathMode = PATH_BACKWARD Or _pathMode = PATH_LOOP_BACKWARD) Then
			_pathNodeIndex = Self.path.nodes.Length() - 1
			_pathInc = -1
		Else
			_pathNodeIndex = 0
			_pathInc = 1
		End If
	End Method

	Method StopFollowingPath:Void(destroyPath:Bool = False)
		pathSpeed = 0
		If (destroyPath And path <> Null) Then
			path.Destroy()
			path = Null
		End If
	End Method

	Method Overlaps:Bool(objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (objectOrGroup = Null) Return False

		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local i:Int = 0
			Local l:Int = members.Length()

			While(i < l)
				If (Overlaps(members[i], inScreenSpace, camera)) Then
					results = True
					Exit
				End if
				i += 1
			Wend

			Return results
		End If

		If (FlxTilemap(objectOrGroup) <> Null) Then
			Return FlxTilemap(objectOrGroup).overlaps(Self, inScreenSpace, camera)
		End If

		Local object:FlxObject = FlxObject(objectOrGroup)

		If (Not inScreenSpace) Then
			Return (object.x + object.width > x) And (object.x < x + width) And
				(object.y + object.height > y) And (object.y < y + height)
		End If

		If (camera = Null) camera = FlxG.camera
		Local objectScreenPos:FlxPoint = object.GetScreenXY(Null, camera)
		GetScreenXY(_point, camera)

		Return (objectScreenPos.x + object.width > _point.x) And (objectScreenPos.x < _point.x + width) And
			(objectScreenPos.y + object.height > _point.y) And (objectScreenPos.y < _point.y + height)
	End Method

	Method OverlapsAt:Bool(x:Float, y:Float, objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (objectOrGroup = Null) Return False

		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local i:Int = 0
			Local l:Int = members.Length()

			While(i < l)
				If (overlapsAt(x, y, members[i], inScreenSpace, camera)) Then
					results = True
					Exit
				End if
				i += 1
			Wend

			Return results
		End If

		If (FlxTilemap(objectOrGroup) <> Null) Then
			Local tilemap:FlxTilemap = FlxTilemap(objectOrGroup)
			Return tilemap.OverlapsAt(tilemap.x - (x - Self.x), tilemap.y - (y - Self.y), Self ,inScreenSpace, camera)
		End If

		Local object:FlxObject = FlxObject(objectOrGroup)

		If (Not inScreenSpace) Then
			Return (object.x + object.width > x) And (object.x < x + width) And
				(object.y + object.height > y) And (object.y < y + height)
		End If

		If (camera = Null) camera = FlxG.camera
		Local objectScreenPos:FlxPoint = object.GetScreenXY(Null, camera)

		point.x = x - Int(camera.scroll.x * scrollFactor.x)
		point.y = y - Int(camera.scroll.y * scrollFactor.y)

		If (point.x > 0) Then
			point.x += 0.0000001
		Else
			point.x -= 0.0000001
		End If

		If (point.y > 0) Then
			point.y += 0.0000001
		Else
			point.y -= 0.0000001
		End If

		Return (objectScreenPos.x + object.width > _point.x) And (objectScreenPos.x < _point.x + width) And
			(objectScreenPos.y + object.height > _point.y) And (objectScreenPos.y < _point.y + height)
	End Method

	Method OverlapsPoint:Bool(point:FlxPoint, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (Not inScreenSpace) Then
			Return (point.x > x) And (point.x < x + width) And (point.y > y) And (point.y < y + height)
		End If

		If (camera = Null) camera = FlxG.camera

		Local x:Float = point.x - camera.scroll.x
		Local y:Float = point.y - camera.scroll.y
		GetScreenXY(_point, camera)

		Return (x > _point.x) And (x < _point.x + width) And (y > _point.y) And (y < _point.y + height)
	End Method

	Method OnScreen:Bool(camera:FlxCamera = Null)
		If (camera = Null) camera = FlxG.camera

		GetScreenXY(_point, camera)
		Return (_point.x + width > 0) And (_point.x < camera.Width) And (_point.y + height > 0) And (_point.y < camera.Height)
	End Method

	Method GetScreenXY:FlxPoint(point:FlxPoint = Null, camera:FlxCamera = Null)
		If (point = Null) point = New FlxPoint()
		If (camera = Null) camera = FlxG.camera

		point.x = x - Int(camera.scroll.x * scrollFactor.x)
		point.y = y - Int(camera.scroll.y * scrollFactor.y)

		If (point.x > 0) Then
			point.x += 0.0000001
		Else
			point.x -= 0.0000001
		End If

		If (point.y > 0) Then
			point.y += 0.0000001
		Else
			point.y -= 0.0000001
		End If

		Return point
	End Method

	Method Flicker:Void(duration:Float = 1)
		_flickerTimer = duration
		If (_flickerTimer = 0) _flicker = False
	End Method

	Method Flickering:Bool() Property
		Return _flickerTimer <> 0
	End Method

	Method Solid:Bool() Property
		Return (allowCollisions & ANY) > NONE
	End Method

	Method Solid:Void(solid:Bool) Property
		If (solid) Then
			allowCollisions = ANY
		Else
			allowCollisions = NONE
		End If
	End Method

	Method GetMidpoint:FlxPoint(point:FlxPoint)
		If (point = Null) point = New FlxPoint()
		point.x = x + width * .5
		point.y = y + height * .5
		Return point
	End Method

	Method Reset:Void(x:Float, y:Float)
		Revive()
		touching = NONE
		wasTouching = NONE
		Self.x = x
		Self.y = y
		last.x = x
		last.y = y
		velocity.x = 0
		velocity.y = 0
	End Method

	Method IsTouching:Bool(direction:Int)
		Return (touching & direction) > NONE
	End Method

	Method JustTouched:Bool(direction:Int)
		Return (touching & direction) > NONE And (wasTouching & direction) <= NONE
	End Method

	Method Hurt:Void(damage:Float)
		health -= damage
		If (health <= 0) Kill()
	End Method

	Method SeparateX:Bool(object1:FlxObject, object2:FlxObject)
		Local obj1immovable:Bool = object1.immovable
		Local obj2immovable:Bool = object2.immovable

		If (obj1immovable And obj2immovable) Return False

		'(FlxTilemap(object1) <> Null) Return FlxTilemap(object1).
		'TODO
	End Method

	Method SeparateY:Bool(object1:FlxObject, object2:FlxObject)

	End Method

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

Private
	Method _UpdateMotion:Void()
		Local delta:Float
		Local velocityDelta:Float

		velocityDelta = (FlxU.ComputeVelocity(angularVelocity, angularAcceleration, angularDrag, maxAngular) - angularVelocity) / 2
		angularVelocity += velocityDelta
		angle += angularVelocity * FlxG.elapsed
		angularVelocity += velocityDelta

		velocityDelta = (FlxU.ComputeVelocity(velocity.x, acceleration.x, drag.x, maxVelocity.x) - velocity.x) / 2
		velocity.x += velocityDelta
		delta = velocity.x * FlxG.elapsed
		velocity.x += velocityDelta
		x += delta

		velocityDelta = (FlxU.ComputeVelocity(velocity.y, acceleration.y, drag.y, maxVelocity.y) - velocity.y) / 2
		velocity.y += velocityDelta
		delta = velocity.y * FlxG.elapsed
		velocity.y += velocityDelta
		y += delta
	End Method

	Method _AdvancePath:FlxPoint(snap:Bool = True)
		If (snap) Then
			Local oldNode:FlxPoint = path.nodes.Get(_pathNodeIndex)
			If (oldNode <> Null) Then
				If ((_pathMode & PATH_VERTICAL_ONLY) = 0) Then
					x = oldNode.x - width * .5
				End If

				If ((_pathMode & PATH_HORIZONTAL_ONLY) = 0) Then
					y = oldNode.y - height * .5
				End If
			End If
		End If

		_pathNodeIndex += _pathInc

		If ((_pathMode & PATH_BACKWARD) > 0) Then
			If (_pathNodeIndex < 0) Then
				_pathNodeIndex = 0
				pathSpeed = 0
			End If

		ElseIf ((_pathMode & PATH_LOOP_FORWARD) > 0) Then
			If (_pathNodeIndex >= path.nodes.Length()) Then
				_pathNodeIndex = 0
			End If

		ElseIf ((_pathMode & PATH_LOOP_BACKWARD) > 0) Then
			If (_pathNodeIndex < 0) Then
				_pathNodeIndex = path.nodes.Length() - 1
				If (_pathNodeIndex < 0) _pathNodeIndex = 0
			End If

		ElseIf ((_pathMode & PATH_YOYO) > 0)
			If (_pathInc > 0) Then
				If (_pathNodeIndex >= path.nodes.Length()) Then
					_pathNodeIndex = path.nodes.Length() - 2

					If (_pathNodeIndex < 0) _pathNodeIndex = 0
					_pathInc = -_pathInc

				ElseIf (_pathNodeIndex < 0) Then
					_pathNodeIndex = 1
					If (_pathNodeIndex >= path.nodes.Length()) Then
						_pathNodeIndex = path.nodes.Length() -1
					End If

					If (_pathNodeIndex < 0) _pathNodeIndex = 0
					_pathInc = -_pathInc
				End If
			End If

		Else
			If (_pathNodeIndex >= path.nodes.Length()) Then
				_pathNodeIndex = path.nodes.Length() - 1
				pathSpeed = 0
			End If
		End If

		Return path.nodes.Get(_pathNodeIndex)
	End Method

	Method _UpdatePathMotion:Void()
		_point.x = x + width * .5
		_point.y = y + height * .5

		Local node:FlxPoint = path.nodes.Get(_pathNodeIndex)
		Local deltaX:Float = node.x - _point.x
		Local deltaY:Float = node.y - _point.y

		Local horizontalOnly:Bool = (_pathMode & PATH_HORIZONTAL_ONLY) > 0
		Local verticalOnly:Bool = (_pathMode & PATH_VERTICAL_ONLY) > 0

		If (horizontalOnly) Then
			If (Abs(deltaX) < pathSpeed * FlxG.elapsed) node = _AdvancePath()

		ElseIf (verticalOnly) Then
			If (Abs(deltaY) < pathSpeed * FlxG.elapsed) node = _AdvancePath()

		Else
			If (Sqrt(deltaX * deltaX + deltaY * deltaY) < pathSpeed * FlxG.elapsed) Then
				node = _AdvancePath()
			End If
		End If

		If (pathSpeed <> 0) Then
			_point.x = x + width * .5
			_point.y = y + height * .5
			If (horizontalOnly Or _point.y = node.y) Then
				If (_point.x < node.x) Then
					velocity.x = pathSpeed
				Else
					velocity.x = -pathSpeed
				End if

				If (velocity.x < 0) Then
					pathAngle = -90
				Else
					pathAngle = 90
				End If

				If (Not horizontalOnly) velocity.y = 0

			ElseIf (verticalOnly Or _point.x = node.x) Then
				If (_point.y < node.y) Then
					velocity.y = pathSpeed
				Else
					velocity.y = -pathSpeed
				End If

				If (velocity.y < 0) Then
					pathAngle = 0
				Else
					pathAngle = 180
				End If

				If (Not verticalOnly) velocity.x = 0

			Else
				pathAngle = FlxU.GetAngle(_point, node)
				FlxU.RotatePoint(0, pathSpeed, 0, 0, pathAngle, velocity)
			End If

			If (_pathRotate) Then
				angularVelocity = 0
				angularAcceleration = 0
				angle = pathAngle
			End If
		End If
	End Method

End Class

Private
Class FlxObjectClass Implements FlxClass

	Method CreateInstance:FlxBasic()
		Return New FlxObject()
	End Method

	Method InstanceOf:Bool(object:FlxBasic)
		Return (FlxObject(object) <> Null)
	End Method

End Class

Class FlxObjectYComparator Implements FlxBasicComparator

	Method Compare:Int(lhs:FlxBasic, rhs:FlxBasic)
		Return FlxObject(lhs).y - FlxObject(rhs).y
	End Method

End Class

Class FlxObjectXComparator Implements FlxBasicComparator

	Method Compare:Int(lhs:FlxBasic, rhs:FlxBasic)
		Return FlxObject(lhs).x - FlxObject(rhs).x
	End Method

End Class

Class FlxObjectSeparateInvoker

	Const SEAPARATE_X:Int = 0
	Const SEPARATE_Y:Int = 1

	Field target:FlxObject
	Field methodToInvoke:Int

	Method Invoke:Bool(object1:FlxObject, object2:FlxObject)
		If (methodToInvoke = SEAPARATE_X) Then
			target.SeparateX(object1, object2)
		Else
			target.SeparateY(object1, object2)
		End If
	End Method

End Class

Private
	Global _separateInvoker:FlxObjectSeparateInvoker = New FlxObjectSeparateInvoker()

#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

Strict

#rem
	header:This module contains the FlxObject class.
#end
Import flxbasic
Import flxpoint
Import flxpath
Import flxgroup
Import flxcamera
Import flxtilemap
Import flxg
Import flxu

Import plugin.monkey.flxcolor

#Rem
summary:This is the base class for most of the display objects (FlxSprite, FlxText, etc).
It includes some basic attributes about game objects, including retro-style flickering,
basic state information, sizes, scrolling, and basic physics and motion.
#End
Class FlxObject Extends FlxBasic

	Global CLASS_OBJECT:FlxClass = new FlxObjectClass()

	Global X_COMPARATOR:FlxBasicComparator = new FlxObjectXComparator()

	Global Y_COMPARATOR:FlxBasicComparator = new FlxObjectYComparator()

	'summary:Generic value for "left" Used by facing, allowCollisions, and touching.
	Const LEFT:Int = 1

	'summary:Generic value for "right" Used by facing, allowCollisions, and touching.
	Const RIGHT:Int = 2

	'summary:Generic value for "up" Used by facing, allowCollisions, and touching.
	Const UP:Int = 4

	'summary:Generic value for "down" Used by facing, allowCollisions, and touching.
	Const DOWN:Int = 8

	'summary:Special-case constant meaning no collisions, used mainly by allowCollisions and touching.
	Const NONE:Int = 0

	'summary:Special-case constant meaning up, used mainly by allowCollisions and touching.
	Const CEILING:Int = UP

	'summary:Special-case constant meaning down, used mainly by allowCollisions and touching.
	Const FLOOR:Int = DOWN

	'summary:Special-case constant meaning only the left and right sides, used mainly by allowCollisions and touching.
	Const WALL:Int = LEFT | RIGHT

	'summary:Special-case constant meaning any direction, used mainly by allowCollisions and touching.
	Const ANY:Int = LEFT | RIGHT | UP | DOWN

	'summary:Handy constant used during collision resolution (see separateX() and separateY()).
	Const OVERLAP_BIAS:Int = 4

	'summary:Path behavior controls: move from the start of the path to the end then stop.
	Const PATH_FORWARD:Int = 0

	'summary:Path behavior controls: move from the end of the path to the start then stop.
	Const PATH_BACKWARD:Int = 1

	'summary:Path behavior controls: move from the start of the path to the end then directly back to the start, and start over.
	Const PATH_LOOP_FORWARD:Int = 2

	'summary:Path behavior controls: move from the end of the path to the start then directly back to the end, and start over.
	Const PATH_LOOP_BACKWARD:Int = 4

	'summary:Path behavior controls: move from the start of the path to the end then turn around and go back to the start, over and over.
	Const PATH_YOYO:Int = 8

	'summary:Path behavior controls: ignores any vertical component to the path data, only follows side to side.
	Const PATH_HORIZONTAL_ONLY:Int = 16

	'summary:Path behavior controls: ignores any horizontal component to the path data, only follows up and down.
	Const PATH_VERTICAL_ONLY:Int = 32

	'summary:X position of the upper left corner of this object in world space.
	Field x:Float

	'summary:Y position of the upper left corner of this object in world space.
	Field y:Float

	'summary:The width of this object.
	Field width:Float

	'summary:The height of this object.
	Field height:Float

	'summary:Whether an object will move/alter position after a collision.
	Field immovable:Bool

	Field velocity:FlxPoint

	Field mass:Float

	Field elasticity:Float

	Field acceleration:FlxPoint

	Field drag:FlxPoint

	Field maxVelocity:FlxPoint

	Field angle:Float

	Field angularVelocity:Float

	Field angularAcceleration:Float

	Field angularDrag:Float

	Field maxAngular:Float

	Field scrollFactor:FlxPoint

	Field health:Float

	Field moves:Bool

	Field touching:Int

	Field wasTouching:Int

	Field allowCollisions:Int

	Field last:FlxPoint

	Field path:FlxPath

	Field pathSpeed:Float

	Field pathAngle:Float

Private
	Global _pZero:FlxPoint = New FlxPoint()

	Field _flicker:Bool

	Field _flickerTimer:Float

	Field _point:FlxPoint

	Field _rect:FlxRect

	Field _pathNodeIndex:Int

	Field _pathMode:Int

	Field _pathInc:Int

	Field _pathRotate:Bool

	Field _debugBoundingBoxColor:FlxColor

Public
	Method New(x:Float, y:Float, width:Float = 0, height:Float = 0)
		Self.x = x
		Self.y = y
		last = New FlxPoint(x, y)
		Self.width = width
		Self.height = height
		mass = 1.0
		elasticity = 0.0

		health = 1

		immovable = False
		moves = True

		touching = NONE
		wasTouching = NONE
		allowCollisions = ANY

		velocity = New FlxPoint()
		acceleration = New FlxPoint()
		drag = New FlxPoint()
		maxVelocity = New FlxPoint(10000, 10000)

		angle = 0
		angularVelocity = 0
		angularAcceleration = 0
		angularDrag = 0
		maxAngular = 10000

		scrollFactor = New FlxPoint(1.0, 1.0)
		_flicker = False
		_flickerTimer = 0

		_point = New FlxPoint()
		_rect = New	FlxRect()

		path = Null
		pathSpeed = 0
		pathAngle = 0
	End Method

	Method Destroy:Void()
		velocity = Null
		acceleration = Null
		drag = Null
		maxVelocity = Null
		scrollFactor = Null
		_point = Null
		_rect = Null
		last = Null
		cameras = Null
		If (path <> Null) path.Destroy()
		path = Null
		_debugBoundingBoxColor = Null
	End Method

	Method PreUpdate:Void()
		_ACTIVECOUNT += 1

		If (_flickerTimer <> 0) Then
			If (_flickerTimer > 0) Then
				_flickerTimer -= FlxG.elapsed
				If (_flickerTimer <= 0) Then
					_flickerTimer = 0
					_flicker = False
				End If
			End If
		End If

		last.x = x
		last.y = y

		If (path <> Null And pathSpeed <> 0 And path.nodes.Get(_pathNodeIndex) <> Null) Then
			_UpdatePathMotion()
		End If
	End Method

	Method PostUpdate:Void()
		If (moves) _UpdateMotion()

		wasTouching = touching
		touching = NONE
	End Method

	Method Draw:Void()
		If (Not OnScreen(FlxG._currentCamera)) Return

		_VISIBLECOUNT += 1
		If (FlxG.visualDebug And Not ignoreDrawDebug) DrawDebug(FlxG._currentCamera)
	End Method

	Method DrawDebug:Void(camera:FlxCamera = Null)
		Local boundingBoxX:Float = x - Int(camera.scroll.x * scrollFactor.x)
		Local boundingBoxY:Float = y - Int(camera.scroll.y * scrollFactor.y)

		If (boundingBoxX > 0) Then
			boundingBoxX += 0.0000001
		Else
			boundingBoxX -= 0.0000001
		End If

		If (boundingBoxY > 0) Then
			boundingBoxY += 0.0000001
		Else
			boundingBoxY -= 0.0000001
		End If

		Local boundingBoxWidth:Int
		Local boundingBoxHeight:Int

		If (width <> Int(width)) Then
			boundingBoxWidth = width
		Else
			boundingBoxWidth = width - 1
		End If

		If (height <> Int(height)) Then
			boundingBoxHeight = height
		Else
			boundingBoxHeight = height - 1
		End If

		If (_debugBoundingBoxColor = Null) _debugBoundingBoxColor = New FlxColor()
		If (allowCollisions) Then
			If (allowCollisions <> ANY) Then
				_debugBoundingBoxColor.SetARGB(FlxG.PINK)
			ElseIf (immovable)
				_debugBoundingBoxColor.SetARGB(FlxG.GREEN)
			Else
				_debugBoundingBoxColor.SetARGB(FlxG.RED)
			End if
		Else
			_debugBoundingBoxColor.SetARGB(FlxG.BLUE)
		End If

		SetAlpha(.5)
		SetColor(_debugBoundingBoxColor.r, _debugBoundingBoxColor.g, _debugBoundingBoxColor.b)

		DrawLine(boundingBoxX, boundingBoxY, boundingBoxX + boundingBoxWidth, boundingBoxY)
		DrawLine(boundingBoxX + boundingBoxWidth, boundingBoxY, boundingBoxX + boundingBoxWidth ,boundingBoxY + boundingBoxHeight)
		DrawLine(boundingBoxX + boundingBoxWidth ,boundingBoxY + boundingBoxHeight, boundingBoxX, boundingBoxY + boundingBoxHeight)
		DrawLine(boundingBoxX, boundingBoxY + boundingBoxHeight, boundingBoxX, boundingBoxY)

		FlxG._lastDrawingColor = _debugBoundingBoxColor.argb
		SetAlpha(1)
	End Method

	Method FollowPath:Void(path:FlxPath, speed:Float = 100, mode:Int = PATH_FORWARD, autoRotate:Bool = False)
		If (path.nodes.Length() <= 0) Then
			FlxG.Log("WARNING: Paths need at least one node in them to be followed.")
			Return
		End If

		Self.path = path
		pathSpeed = Abs(speed)
		_pathMode = mode
		_pathRotate = autoRotate

		If (_pathMode = PATH_BACKWARD Or _pathMode = PATH_LOOP_BACKWARD) Then
			_pathNodeIndex = Self.path.nodes.Length() - 1
			_pathInc = -1
		Else
			_pathNodeIndex = 0
			_pathInc = 1
		End If
	End Method

	Method StopFollowingPath:Void(destroyPath:Bool = False)
		pathSpeed = 0
		If (destroyPath And path <> Null) Then
			path.Destroy()
			path = Null
		End If
	End Method

	Method Overlaps:Bool(objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (objectOrGroup = Null) Return False

		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local i:Int = 0
			Local l:Int = members.Length()

			While(i < l)
				If (Overlaps(members[i], inScreenSpace, camera)) Then
					results = True
					Exit
				End if
				i += 1
			Wend

			Return results
		End If

		If (FlxTilemap(objectOrGroup) <> Null) Then
			Return FlxTilemap(objectOrGroup).overlaps(Self, inScreenSpace, camera)
		End If

		Local object:FlxObject = FlxObject(objectOrGroup)

		If (Not inScreenSpace) Then
			Return (object.x + object.width > x) And (object.x < x + width) And
				(object.y + object.height > y) And (object.y < y + height)
		End If

		If (camera = Null) camera = FlxG.camera
		Local objectScreenPos:FlxPoint = object.GetScreenXY(Null, camera)
		GetScreenXY(_point, camera)

		Return (objectScreenPos.x + object.width > _point.x) And (objectScreenPos.x < _point.x + width) And
			(objectScreenPos.y + object.height > _point.y) And (objectScreenPos.y < _point.y + height)
	End Method

	Method OverlapsAt:Bool(x:Float, y:Float, objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (objectOrGroup = Null) Return False

		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local i:Int = 0
			Local l:Int = members.Length()

			While(i < l)
				If (overlapsAt(x, y, members[i], inScreenSpace, camera)) Then
					results = True
					Exit
				End if
				i += 1
			Wend

			Return results
		End If

		If (FlxTilemap(objectOrGroup) <> Null) Then
			Local tilemap:FlxTilemap = FlxTilemap(objectOrGroup)
			Return tilemap.OverlapsAt(tilemap.x - (x - Self.x), tilemap.y - (y - Self.y), Self ,inScreenSpace, camera)
		End If

		Local object:FlxObject = FlxObject(objectOrGroup)

		If (Not inScreenSpace) Then
			Return (object.x + object.width > x) And (object.x < x + width) And
				(object.y + object.height > y) And (object.y < y + height)
		End If

		If (camera = Null) camera = FlxG.camera
		Local objectScreenPos:FlxPoint = object.GetScreenXY(Null, camera)

		point.x = x - Int(camera.scroll.x * scrollFactor.x)
		point.y = y - Int(camera.scroll.y * scrollFactor.y)

		If (point.x > 0) Then
			point.x += 0.0000001
		Else
			point.x -= 0.0000001
		End If

		If (point.y > 0) Then
			point.y += 0.0000001
		Else
			point.y -= 0.0000001
		End If

		Return (objectScreenPos.x + object.width > _point.x) And (objectScreenPos.x < _point.x + width) And
			(objectScreenPos.y + object.height > _point.y) And (objectScreenPos.y < _point.y + height)
	End Method

	Method OverlapsPoint:Bool(point:FlxPoint, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (Not inScreenSpace) Then
			Return (point.x > x) And (point.x < x + width) And (point.y > y) And (point.y < y + height)
		End If

		If (camera = Null) camera = FlxG.camera

		Local x:Float = point.x - camera.scroll.x
		Local y:Float = point.y - camera.scroll.y
		GetScreenXY(_point, camera)

		Return (x > _point.x) And (x < _point.x + width) And (y > _point.y) And (y < _point.y + height)
	End Method

	Method OnScreen:Bool(camera:FlxCamera = Null)
		If (camera = Null) camera = FlxG.camera

		GetScreenXY(_point, camera)
		Return (_point.x + width > 0) And (_point.x < camera.Width) And (_point.y + height > 0) And (_point.y < camera.Height)
	End Method

	Method GetScreenXY:FlxPoint(point:FlxPoint = Null, camera:FlxCamera = Null)
		If (point = Null) point = New FlxPoint()
		If (camera = Null) camera = FlxG.camera

		point.x = x - Int(camera.scroll.x * scrollFactor.x)
		point.y = y - Int(camera.scroll.y * scrollFactor.y)

		If (point.x > 0) Then
			point.x += 0.0000001
		Else
			point.x -= 0.0000001
		End If

		If (point.y > 0) Then
			point.y += 0.0000001
		Else
			point.y -= 0.0000001
		End If

		Return point
	End Method

	Method Flicker:Void(duration:Float = 1)
		_flickerTimer = duration
		If (_flickerTimer = 0) _flicker = False
	End Method

	Method Flickering:Bool() Property
		Return _flickerTimer <> 0
	End Method

	Method Solid:Bool() Property
		Return (allowCollisions & ANY) > NONE
	End Method

	Method Solid:Void(solid:Bool) Property
		If (solid) Then
			allowCollisions = ANY
		Else
			allowCollisions = NONE
		End If
	End Method

	Method GetMidpoint:FlxPoint(point:FlxPoint)
		If (point = Null) point = New FlxPoint()
		point.x = x + width * .5
		point.y = y + height * .5
		Return point
	End Method

	Method Reset:Void(x:Float, y:Float)
		Revive()
		touching = NONE
		wasTouching = NONE
		Self.x = x
		Self.y = y
		last.x = x
		last.y = y
		velocity.x = 0
		velocity.y = 0
	End Method

	Method IsTouching:Bool(direction:Int)
		Return (touching & direction) > NONE
	End Method

	Method JustTouched:Bool(direction:Int)
		Return (touching & direction) > NONE And (wasTouching & direction) <= NONE
	End Method

	Method Hurt:Void(damage:Float)
		health -= damage
		If (health <= 0) Kill()
	End Method

	Method SeparateX:Bool(object1:FlxObject, object2:FlxObject)
		Local obj1immovable:Bool = object1.immovable
		Local obj2immovable:Bool = object2.immovable

		If (obj1immovable And obj2immovable) Return False

		'(FlxTilemap(object1) <> Null) Return FlxTilemap(object1).
		'TODO
	End Method

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

Private
	Method _UpdateMotion:Void()
		Local delta:Float
		Local velocityDelta:Float

		velocityDelta = (FlxU.ComputeVelocity(angularVelocity, angularAcceleration, angularDrag, maxAngular) - angularVelocity) / 2
		angularVelocity += velocityDelta
		angle += angularVelocity * FlxG.elapsed
		angularVelocity += velocityDelta

		velocityDelta = (FlxU.ComputeVelocity(velocity.x, acceleration.x, drag.x, maxVelocity.x) - velocity.x) / 2
		velocity.x += velocityDelta
		delta = velocity.x * FlxG.elapsed
		velocity.x += velocityDelta
		x += delta

		velocityDelta = (FlxU.ComputeVelocity(velocity.y, acceleration.y, drag.y, maxVelocity.y) - velocity.y) / 2
		velocity.y += velocityDelta
		delta = velocity.y * FlxG.elapsed
		velocity.y += velocityDelta
		y += delta
	End Method

	Method _AdvancePath:FlxPoint(snap:Bool = True)
		If (snap) Then
			Local oldNode:FlxPoint = path.nodes.Get(_pathNodeIndex)
			If (oldNode <> Null) Then
				If ((_pathMode & PATH_VERTICAL_ONLY) = 0) Then
					x = oldNode.x - width * .5
				End If

				If ((_pathMode & PATH_HORIZONTAL_ONLY) = 0) Then
					y = oldNode.y - height * .5
				End If
			End If
		End If

		_pathNodeIndex += _pathInc

		If ((_pathMode & PATH_BACKWARD) > 0) Then
			If (_pathNodeIndex < 0) Then
				_pathNodeIndex = 0
				pathSpeed = 0
			End If

		ElseIf ((_pathMode & PATH_LOOP_FORWARD) > 0) Then
			If (_pathNodeIndex >= path.nodes.Length()) Then
				_pathNodeIndex = 0
			End If

		ElseIf ((_pathMode & PATH_LOOP_BACKWARD) > 0) Then
			If (_pathNodeIndex < 0) Then
				_pathNodeIndex = path.nodes.Length() - 1
				If (_pathNodeIndex < 0) _pathNodeIndex = 0
			End If

		ElseIf ((_pathMode & PATH_YOYO) > 0)
			If (_pathInc > 0) Then
				If (_pathNodeIndex >= path.nodes.Length()) Then
					_pathNodeIndex = path.nodes.Length() - 2

					If (_pathNodeIndex < 0) _pathNodeIndex = 0
					_pathInc = -_pathInc

				ElseIf (_pathNodeIndex < 0) Then
					_pathNodeIndex = 1
					If (_pathNodeIndex >= path.nodes.Length()) Then
						_pathNodeIndex = path.nodes.Length() -1
					End If

					If (_pathNodeIndex < 0) _pathNodeIndex = 0
					_pathInc = -_pathInc
				End If
			End If

		Else
			If (_pathNodeIndex >= path.nodes.Length()) Then
				_pathNodeIndex = path.nodes.Length() - 1
				pathSpeed = 0
			End If
		End If

		Return path.nodes.Get(_pathNodeIndex)
	End Method

	Method _UpdatePathMotion:Void()
		_point.x = x + width * .5
		_point.y = y + height * .5

		Local node:FlxPoint = path.nodes.Get(_pathNodeIndex)
		Local deltaX:Float = node.x - _point.x
		Local deltaY:Float = node.y - _point.y

		Local horizontalOnly:Bool = (_pathMode & PATH_HORIZONTAL_ONLY) > 0
		Local verticalOnly:Bool = (_pathMode & PATH_VERTICAL_ONLY) > 0

		If (horizontalOnly) Then
			If (Abs(deltaX) < pathSpeed * FlxG.elapsed) node = _AdvancePath()

		ElseIf (verticalOnly) Then
			If (Abs(deltaY) < pathSpeed * FlxG.elapsed) node = _AdvancePath()

		Else
			If (Sqrt(deltaX * deltaX + deltaY * deltaY) < pathSpeed * FlxG.elapsed) Then
				node = _AdvancePath()
			End If
		End If

		If (pathSpeed <> 0) Then
			_point.x = x + width * .5
			_point.y = y + height * .5
			If (horizontalOnly Or _point.y = node.y) Then
				If (_point.x < node.x) Then
					velocity.x = pathSpeed
				Else
					velocity.x = -pathSpeed
				End if

				If (velocity.x < 0) Then
					pathAngle = -90
				Else
					pathAngle = 90
				End If

				If (Not horizontalOnly) velocity.y = 0

			ElseIf (verticalOnly Or _point.x = node.x) Then
				If (_point.y < node.y) Then
					velocity.y = pathSpeed
				Else
					velocity.y = -pathSpeed
				End If

				If (velocity.y < 0) Then
					pathAngle = 0
				Else
					pathAngle = 180
				End If

				If (Not verticalOnly) velocity.x = 0

			Else
				pathAngle = FlxU.GetAngle(_point, node)
				FlxU.RotatePoint(0, pathSpeed, 0, 0, pathAngle, velocity)
			End If

			If (_pathRotate) Then
				angularVelocity = 0
				angularAcceleration = 0
				angle = pathAngle
			End If
		End If
	End Method

End Class

Private
Class FlxObjectClass Implements FlxClass

	Method CreateInstance:FlxBasic()
		Return New FlxObject()
	End Method

	Method InstanceOf:Bool(object:FlxBasic)
		Return (FlxObject(object) <> Null)
	End Method

End Class

Class FlxObjectYComparator Implements FlxBasicComparator

	Method Compare:Int(lhs:FlxBasic, rhs:FlxBasic)
		Return FlxObject(lhs).y - FlxObject(rhs).y
	End Method

End Class

Class FlxObjectXComparator Implements FlxBasicComparator

	Method Compare:Int(lhs:FlxBasic, rhs:FlxBasic)
		Return FlxObject(lhs).x - FlxObject(rhs).x
	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