Fixed bug with Base64 not correctly taking into account padding characters.

#if HOST="macos" And TARGET="glfw"
	Import "native/diddy.${TARGET}.mac.${LANG}"
#else
	Import "native/diddy.${TARGET}.${LANG}"
#end
Import mojo
Import framework
Import assert

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy::setMouse"
		Function ShowKeyboard:Void() = "diddy::showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy::launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy::launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy::setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy::realMod"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy.setMouse"
		Function ShowKeyboard:Void() = "diddy.showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy.launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy.launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy.setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy.realMod"
	#End

	#If LANG="java" Then
		Function BuildString:String(arr:Int[], offset:Int, length:Int) = "diddy.buildString"
	#End
Public

#If LANG <> "java" Then
	Function BuildString:String(arr:Int[], offset:Int, length:Int)
		If offset<0 Or length<=0 Or offset+length > arr.Length Then Return ""
		Local rv:String = String.FromChar(arr[offset])
		For Local i:Int = offset+1 Until offset+length
			rv += String.FromChar(arr[i])
		Next
		Return rv
	End
#End

Function SetGraphics:Void(w:Int, h:Int)
	SetNativeGraphicsSize(w, h)
	DEVICE_WIDTH = w
	DEVICE_HEIGHT = h
	SCREEN_HEIGHT = h
	SCREEN_WIDTH = w
	SCREEN_WIDTH2 = SCREEN_WIDTH / 2
	SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2
End

Function ExitApp:Void()
	Error ""
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x,y,x+w,y)
	DrawLine(x+w,y,x+w,y+h)
	DrawLine(x+w,y+h,x,y+h)
	DrawLine(x,y+h,x,y)
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

' TODO: allow encoding of Int[]
' TODO: enabling lineWrap will wrap the string every lineWrapWidth characters (defaults to 80)
' enabling padOutput will pad the ending of the output with equals characters (=) to next 4-byte boundary
Function EncodeBase64:String(src:String, padOutput:Bool=False, lineWrap:Bool=False, lineWrapWidth:Int=80)
	Local rv:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	If src.Length = 0 Then Return ""
	Repeat
		s1 = src[i]
		If i+1 < src.Length Then s2 = src[i+1] Else s2 = 0
		If i+2 < src.Length Then s3 = src[i+2] Else s3 = 0
		a = s1 Shr 2
		b = ((s1 & 3) Shl 4) | (s2 Shr 4)
		c = ((s2 & 15) Shl 2) | (s3 Shr 6)
		d = s3 & 63
		If i+1 >= src.Length Then c = 64
		If i+2 >= src.Length Then d = 64
		rv += BASE64_CHARS[a..a+1] + BASE64_CHARS[b..b+1]
		If c < 64 Or padOutput Then rv += BASE64_CHARS[c..c+1]
		If d < 64 Or padOutput Then rv += BASE64_CHARS[d..d+1]
		i += 3
	Until i >= src.Length
	Return rv
End

' TODO: convert this to use BASE64_ARRAY
' I'll do this once Monkey has a decent string builder class.
Function DecodeBase64:String(src:String)
	Local rv:String = "", src2:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	' remove any non-base64 characters
	For i = 0 Until src.Length
		If BASE64_CHARS.Find(src[i..i+1]) >= 0 Then src2 += src[i..i+1]
	Next
	If src2.Length = 0 Then Return ""
	i = 0
	Repeat
		a = BASE64_CHARS.Find(src2[i..i+1])
		If i+1 > src2.Length Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_CHARS.Find(src2[i+1..i+2])
		If i+2 < src2.Length Then c = BASE64_CHARS.Find(src2[i+2..i+3]) Else c = 64
		If i+3 < src2.Length Then d = BASE64_CHARS.Find(src2[i+3..i+4]) Else d = 64
		s1 = (a Shl 2) | (b Shr 4)
		s2 = ((b & 15) Shl 4) | (c Shr 2)
		s3 = ((c & 3) Shl 6) | d
		rv += String.FromChar(s1)
		If c <> 64 Then rv += String.FromChar(s2)
		If d <> 64 Then rv += String.FromChar(s3)
		i += 4
	Until i >= src2.Length
	Return rv
End

Function DecodeBase64Bytes:Int[](src:String)
	InitBase64()
	Local a:Int, b:Int, c:Int, d:Int, i:Int, j:Int
	Local src2:Int[] = New Int[src.Length]
	Local padding:Int = 0

	' find out how many base64 characters
	Local srclen:Int = 0
	For i = 0 Until src.Length
		If BASE64_ARRAY[src[i]] >= 0 Then
			src2[srclen] = src[i]
			srclen += 1
			' check if it's a padding character and increment the count
			If BASE64_ARRAY[src[i]] = 64 Then padding += 1
		End
	Next

	' die if there are no base64 chars
	If srclen = 0 Then Return []

	' get the target length and create the array
	Local len:Int = 3*(srclen/4)
	If srclen Mod 4 = 0 Then
		len -= padding
	ElseIf padding = 0 Then
		If srclen Mod 4 >= 2 Then len += 1
		If srclen Mod 4 = 3 Then len += 1
	End
	Local rv:Int[] = New Int[len]

	i = 0
	j = 0
	Repeat
		a = BASE64_ARRAY[src2[i]]
		If i+1 > srclen Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_ARRAY[src2[i+1]]
		If i+2 < srclen Then c = BASE64_ARRAY[src2[i+2]] Else c = 64
		If i+3 < srclen Then d = BASE64_ARRAY[src2[i+3]] Else d = 64
		rv[j] = (a Shl 2) | (b Shr 4)
		If j+1 < len Then rv[j+1] = ((b & 15) Shl 4) | (c Shr 2)
		If j+2 < len Then rv[j+2] = ((c & 3) Shl 6) | d
		i += 4
		j += 3
	Until i >= srclen
	Return rv
End

Function InitBase64:Void()
	If BASE64_ARRAY.Length = 0 Then
		BASE64_ARRAY = New Int[256]
		Local i% = 0
		For i = 0 Until BASE64_ARRAY.Length
			BASE64_ARRAY[i] = -1
		Next
		For i = 0 Until BASE64_CHARS.Length
			BASE64_ARRAY[BASE64_CHARS[i]] = i
		Next
	End
End

' constants

Const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
Global BASE64_ARRAY:Int[] = []

' control characters
Const ASC_NUL:Int = 0       ' Null character
Const ASC_SOH:Int = 1       ' Start of Heading
Const ASC_STX:Int = 2       ' Start of Text
Const ASC_ETX:Int = 3       ' End of Text
Const ASC_EOT:Int = 4       ' End of Transmission
Const ASC_ENQ:Int = 5       ' Enquiry
Const ASC_ACK:Int = 6       ' Acknowledgment
Const ASC_BEL:Int = 7       ' Bell
Const ASC_BACKSPACE:Int = 8 ' Backspace
Const ASC_TAB:Int = 9       ' Horizontal tab
Const ASC_LF:Int = 10       ' Linefeed
Const ASC_VTAB:Int = 11     ' Vertical tab
Const ASC_FF:Int = 12       ' Form feed
Const ASC_CR:Int = 13       ' Carriage return
Const ASC_SO:Int = 14       ' Shift Out
Const ASC_SI:Int = 15       ' Shift In
Const ASC_DLE:Int = 16      ' Data Line Escape
Const ASC_DC1:Int = 17      ' Device Control 1
Const ASC_DC2:Int = 18      ' Device Control 2
Const ASC_DC3:Int = 19      ' Device Control 3
Const ASC_DC4:Int = 20      ' Device Control 4
Const ASC_NAK:Int = 21      ' Negative Acknowledgment
Const ASC_SYN:Int = 22      ' Synchronous Idle
Const ASC_ETB:Int = 23      ' End of Transmit Block
Const ASC_CAN:Int = 24      ' Cancel
Const ASC_EM:Int = 25       ' End of Medium
Const ASC_SUB:Int = 26      ' Substitute
Const ASC_ESCAPE:Int = 27   ' Escape
Const ASC_FS:Int = 28       ' File separator
Const ASC_GS:Int = 29       ' Group separator
Const ASC_RS:Int = 30       ' Record separator
Const ASC_US:Int = 31       ' Unit separator

' visible characters
Const ASC_SPACE:Int = 32                ' '
Const ASC_EXCLAMATION:Int = 33          '!'
Const ASC_DOUBLE_QUOTE:Int = 34         '"'
Const ASC_HASH:Int = 35                 '#'
Const ASC_DOLLAR:Int = 36               '$'
Const ASC_PERCENT:Int = 37              '%'
Const ASC_AMPERSAND:Int = 38            '&'
Const ASC_SINGLE_QUOTE:Int = 39         '''
Const ASC_OPEN_PARENTHESIS:Int = 40     '('
Const ASC_CLOSE_PARENTHESIS:Int = 41    ')'
Const ASC_ASTERISK:Int = 42             '*'
Const ASC_PLUS:Int = 43                 '+'
Const ASC_COMMA:Int = 44                ','
Const ASC_HYPHEN:Int = 45               '-'
Const ASC_PERIOD:Int = 46               '.'
Const ASC_SLASH:Int = 47                '/'
Const ASC_0:Int = 48
Const ASC_1:Int = 49
Const ASC_2:Int = 50
Const ASC_3:Int = 51
Const ASC_4:Int = 52
Const ASC_5:Int = 53
Const ASC_6:Int = 54
Const ASC_7:Int = 55
Const ASC_8:Int = 56
Const ASC_9:Int = 57
Const ASC_COLON:Int = 58        ':'
Const ASC_SEMICOLON:Int = 59    ';'
Const ASC_LESS_THAN:Int = 60    '<'
Const ASC_EQUALS:Int = 61       '='
Const ASC_GREATER_THAN:Int = 62 '>'
Const ASC_QUESTION:Int = 63     '?'
Const ASC_AT:Int = 64           '@'
Const ASC_UPPER_A:Int = 65
Const ASC_UPPER_B:Int = 66
Const ASC_UPPER_C:Int = 67
Const ASC_UPPER_D:Int = 68
Const ASC_UPPER_E:Int = 69
Const ASC_UPPER_F:Int = 70
Const ASC_UPPER_G:Int = 71
Const ASC_UPPER_H:Int = 72
Const ASC_UPPER_I:Int = 73
Const ASC_UPPER_J:Int = 74
Const ASC_UPPER_K:Int = 75
Const ASC_UPPER_L:Int = 76
Const ASC_UPPER_M:Int = 77
Const ASC_UPPER_N:Int = 78
Const ASC_UPPER_O:Int = 79
Const ASC_UPPER_P:Int = 80
Const ASC_UPPER_Q:Int = 81
Const ASC_UPPER_R:Int = 82
Const ASC_UPPER_S:Int = 83
Const ASC_UPPER_T:Int = 84
Const ASC_UPPER_U:Int = 85
Const ASC_UPPER_V:Int = 86
Const ASC_UPPER_W:Int = 87
Const ASC_UPPER_X:Int = 88
Const ASC_UPPER_Y:Int = 89
Const ASC_UPPER_Z:Int = 90
Const ASC_OPEN_BRACKET:Int = 91     '['
Const ASC_BACKSLASH:Int = 92        '\'
Const ASC_CLOSE_BRACKET:Int = 93    ']'
Const ASC_CIRCUMFLEX:Int = 94       '^'
Const ASC_UNDERSCORE:Int = 95       '_'
Const ASC_BACKTICK:Int = 96         '`'
Const ASC_LOWER_A:Int = 97
Const ASC_LOWER_B:Int = 98
Const ASC_LOWER_C:Int = 99
Const ASC_LOWER_D:Int = 100
Const ASC_LOWER_E:Int = 101
Const ASC_LOWER_F:Int = 102
Const ASC_LOWER_G:Int = 103
Const ASC_LOWER_H:Int = 104
Const ASC_LOWER_I:Int = 105
Const ASC_LOWER_J:Int = 106
Const ASC_LOWER_K:Int = 107
Const ASC_LOWER_L:Int = 108
Const ASC_LOWER_M:Int = 109
Const ASC_LOWER_N:Int = 110
Const ASC_LOWER_O:Int = 111
Const ASC_LOWER_P:Int = 112
Const ASC_LOWER_Q:Int = 113
Const ASC_LOWER_R:Int = 114
Const ASC_LOWER_S:Int = 115
Const ASC_LOWER_T:Int = 116
Const ASC_LOWER_U:Int = 117
Const ASC_LOWER_V:Int = 118
Const ASC_LOWER_W:Int = 119
Const ASC_LOWER_X:Int = 120
Const ASC_LOWER_Y:Int = 121
Const ASC_LOWER_Z:Int = 122
Const ASC_OPEN_BRACE:Int = 123  '{'
Const ASC_PIPE:Int = 124        '|'
Const ASC_CLOSE_BRACE:Int = 125 '}'
Const ASC_TILDE:Int = 126       '~'
Const ASC_DELETE:Int = 127

#if HOST="macos" And TARGET="glfw"
	Import "native/diddy.${TARGET}.mac.${LANG}"
#else
	Import "native/diddy.${TARGET}.${LANG}"
#end
Import mojo
Import framework
Import assert

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy::setMouse"
		Function ShowKeyboard:Void() = "diddy::showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy::launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy::launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy::setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy::realMod"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy.setMouse"
		Function ShowKeyboard:Void() = "diddy.showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy.launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy.launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy.setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy.realMod"
	#End

	#If LANG="java" Then
		Function BuildString:String(arr:Int[], offset:Int, length:Int) = "diddy.buildString"
	#End
Public

#If LANG <> "java" Then
	Function BuildString:String(arr:Int[], offset:Int, length:Int)
		If offset<0 Or length<=0 Or offset+length > arr.Length Then Return ""
		Local rv:String = String.FromChar(arr[offset])
		For Local i:Int = offset+1 Until offset+length
			rv += String.FromChar(arr[i])
		Next
		Return rv
	End
#End

Function SetGraphics:Void(w:Int, h:Int)
	SetNativeGraphicsSize(w, h)
	DEVICE_WIDTH = w
	DEVICE_HEIGHT = h
	SCREEN_HEIGHT = h
	SCREEN_WIDTH = w
	SCREEN_WIDTH2 = SCREEN_WIDTH / 2
	SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2
End

Function ExitApp:Void()
	Error ""
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x,y,x+w,y)
	DrawLine(x+w,y,x+w,y+h)
	DrawLine(x+w,y+h,x,y+h)
	DrawLine(x,y+h,x,y)
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

' TODO: allow encoding of Int[]
' TODO: enabling lineWrap will wrap the string every lineWrapWidth characters (defaults to 80)
' enabling padOutput will pad the ending of the output with equals characters (=) to next 4-byte boundary
Function EncodeBase64:String(src:String, padOutput:Bool=False, lineWrap:Bool=False, lineWrapWidth:Int=80)
	Local rv:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	If src.Length = 0 Then Return ""
	Repeat
		s1 = src[i]
		If i+1 < src.Length Then s2 = src[i+1] Else s2 = 0
		If i+2 < src.Length Then s3 = src[i+2] Else s3 = 0
		a = s1 Shr 2
		b = ((s1 & 3) Shl 4) | (s2 Shr 4)
		c = ((s2 & 15) Shl 2) | (s3 Shr 6)
		d = s3 & 63
		If i+1 >= src.Length Then c = 64
		If i+2 >= src.Length Then d = 64
		rv += BASE64_CHARS[a..a+1] + BASE64_CHARS[b..b+1]
		If c < 64 Or padOutput Then rv += BASE64_CHARS[c..c+1]
		If d < 64 Or padOutput Then rv += BASE64_CHARS[d..d+1]
		i += 3
	Until i >= src.Length
	Return rv
End

' TODO: convert this to use BASE64_ARRAY
' I'll do this once Monkey has a decent string builder class.
Function DecodeBase64:String(src:String)
	Local rv:String = "", src2:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	' remove any non-base64 characters
	For i = 0 Until src.Length
		If BASE64_CHARS.Find(src[i..i+1]) >= 0 Then src2 += src[i..i+1]
	Next
	If src2.Length = 0 Then Return ""
	i = 0
	Repeat
		a = BASE64_CHARS.Find(src2[i..i+1])
		If i+1 > src2.Length Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_CHARS.Find(src2[i+1..i+2])
		If i+2 < src2.Length Then c = BASE64_CHARS.Find(src2[i+2..i+3]) Else c = 64
		If i+3 < src2.Length Then d = BASE64_CHARS.Find(src2[i+3..i+4]) Else d = 64
		s1 = (a Shl 2) | (b Shr 4)
		s2 = ((b & 15) Shl 4) | (c Shr 2)
		s3 = ((c & 3) Shl 6) | d
		rv += String.FromChar(s1)
		If c <> 64 Then rv += String.FromChar(s2)
		If d <> 64 Then rv += String.FromChar(s3)
		i += 4
	Until i >= src2.Length
	Return rv
End

Function DecodeBase64Bytes:Int[](src:String)
	InitBase64()
	Local a:Int, b:Int, c:Int, d:Int, i:Int, j:Int
	Local src2:Int[] = New Int[src.Length]

	' find out how many base64 characters
	Local srclen:Int = 0
	For i = 0 Until src.Length
		If BASE64_ARRAY[src[i]] >= 0 Then
			src2[srclen] = src[i]
			srclen += 1
		End
	Next

	' die if there are no base64 chars
	If srclen = 0 Then Return []

	' get the target length and create the array
	Local len:Int = 3*srclen/4
	If srclen Mod 4 = 1 Or srclen Mod 4 = 2 Then
		len += 1
	ElseIf srclen Mod 4 = 3 Then
		len += 2
	End
	Local rv:Int[] = New Int[len]

	i = 0
	j = 0
	Repeat
		a = BASE64_ARRAY[src2[i]]
		If i+1 > srclen Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_ARRAY[src2[i+1]]
		If i+2 < srclen Then c = BASE64_ARRAY[src2[i+2]] Else c = 64
		If i+3 < srclen Then d = BASE64_ARRAY[src2[i+3]] Else d = 64
		rv[j] = (a Shl 2) | (b Shr 4)
		If j+1 < len Then rv[j+1] = ((b & 15) Shl 4) | (c Shr 2)
		If j+2 < len Then rv[j+2] = ((c & 3) Shl 6) | d
		i += 4
		j += 3
	Until i >= srclen
	Return rv
End

Function InitBase64:Void()
	If BASE64_ARRAY.Length = 0 Then
		BASE64_ARRAY = New Int[256]
		Local i% = 0
		For i = 0 Until BASE64_ARRAY.Length
			BASE64_ARRAY[i] = -1
		Next
		For i = 0 Until BASE64_CHARS.Length
			BASE64_ARRAY[BASE64_CHARS[i]] = i
		Next
	End
End

' constants

Const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
Global BASE64_ARRAY:Int[] = []

' control characters
Const ASC_NUL:Int = 0       ' Null character
Const ASC_SOH:Int = 1       ' Start of Heading
Const ASC_STX:Int = 2       ' Start of Text
Const ASC_ETX:Int = 3       ' End of Text
Const ASC_EOT:Int = 4       ' End of Transmission
Const ASC_ENQ:Int = 5       ' Enquiry
Const ASC_ACK:Int = 6       ' Acknowledgment
Const ASC_BEL:Int = 7       ' Bell
Const ASC_BACKSPACE:Int = 8 ' Backspace
Const ASC_TAB:Int = 9       ' Horizontal tab
Const ASC_LF:Int = 10       ' Linefeed
Const ASC_VTAB:Int = 11     ' Vertical tab
Const ASC_FF:Int = 12       ' Form feed
Const ASC_CR:Int = 13       ' Carriage return
Const ASC_SO:Int = 14       ' Shift Out
Const ASC_SI:Int = 15       ' Shift In
Const ASC_DLE:Int = 16      ' Data Line Escape
Const ASC_DC1:Int = 17      ' Device Control 1
Const ASC_DC2:Int = 18      ' Device Control 2
Const ASC_DC3:Int = 19      ' Device Control 3
Const ASC_DC4:Int = 20      ' Device Control 4
Const ASC_NAK:Int = 21      ' Negative Acknowledgment
Const ASC_SYN:Int = 22      ' Synchronous Idle
Const ASC_ETB:Int = 23      ' End of Transmit Block
Const ASC_CAN:Int = 24      ' Cancel
Const ASC_EM:Int = 25       ' End of Medium
Const ASC_SUB:Int = 26      ' Substitute
Const ASC_ESCAPE:Int = 27   ' Escape
Const ASC_FS:Int = 28       ' File separator
Const ASC_GS:Int = 29       ' Group separator
Const ASC_RS:Int = 30       ' Record separator
Const ASC_US:Int = 31       ' Unit separator

' visible characters
Const ASC_SPACE:Int = 32                ' '
Const ASC_EXCLAMATION:Int = 33          '!'
Const ASC_DOUBLE_QUOTE:Int = 34         '"'
Const ASC_HASH:Int = 35                 '#'
Const ASC_DOLLAR:Int = 36               '$'
Const ASC_PERCENT:Int = 37              '%'
Const ASC_AMPERSAND:Int = 38            '&'
Const ASC_SINGLE_QUOTE:Int = 39         '''
Const ASC_OPEN_PARENTHESIS:Int = 40     '('
Const ASC_CLOSE_PARENTHESIS:Int = 41    ')'
Const ASC_ASTERISK:Int = 42             '*'
Const ASC_PLUS:Int = 43                 '+'
Const ASC_COMMA:Int = 44                ','
Const ASC_HYPHEN:Int = 45               '-'
Const ASC_PERIOD:Int = 46               '.'
Const ASC_SLASH:Int = 47                '/'
Const ASC_0:Int = 48
Const ASC_1:Int = 49
Const ASC_2:Int = 50
Const ASC_3:Int = 51
Const ASC_4:Int = 52
Const ASC_5:Int = 53
Const ASC_6:Int = 54
Const ASC_7:Int = 55
Const ASC_8:Int = 56
Const ASC_9:Int = 57
Const ASC_COLON:Int = 58        ':'
Const ASC_SEMICOLON:Int = 59    ';'
Const ASC_LESS_THAN:Int = 60    '<'
Const ASC_EQUALS:Int = 61       '='
Const ASC_GREATER_THAN:Int = 62 '>'
Const ASC_QUESTION:Int = 63     '?'
Const ASC_AT:Int = 64           '@'
Const ASC_UPPER_A:Int = 65
Const ASC_UPPER_B:Int = 66
Const ASC_UPPER_C:Int = 67
Const ASC_UPPER_D:Int = 68
Const ASC_UPPER_E:Int = 69
Const ASC_UPPER_F:Int = 70
Const ASC_UPPER_G:Int = 71
Const ASC_UPPER_H:Int = 72
Const ASC_UPPER_I:Int = 73
Const ASC_UPPER_J:Int = 74
Const ASC_UPPER_K:Int = 75
Const ASC_UPPER_L:Int = 76
Const ASC_UPPER_M:Int = 77
Const ASC_UPPER_N:Int = 78
Const ASC_UPPER_O:Int = 79
Const ASC_UPPER_P:Int = 80
Const ASC_UPPER_Q:Int = 81
Const ASC_UPPER_R:Int = 82
Const ASC_UPPER_S:Int = 83
Const ASC_UPPER_T:Int = 84
Const ASC_UPPER_U:Int = 85
Const ASC_UPPER_V:Int = 86
Const ASC_UPPER_W:Int = 87
Const ASC_UPPER_X:Int = 88
Const ASC_UPPER_Y:Int = 89
Const ASC_UPPER_Z:Int = 90
Const ASC_OPEN_BRACKET:Int = 91     '['
Const ASC_BACKSLASH:Int = 92        '\'
Const ASC_CLOSE_BRACKET:Int = 93    ']'
Const ASC_CIRCUMFLEX:Int = 94       '^'
Const ASC_UNDERSCORE:Int = 95       '_'
Const ASC_BACKTICK:Int = 96         '`'
Const ASC_LOWER_A:Int = 97
Const ASC_LOWER_B:Int = 98
Const ASC_LOWER_C:Int = 99
Const ASC_LOWER_D:Int = 100
Const ASC_LOWER_E:Int = 101
Const ASC_LOWER_F:Int = 102
Const ASC_LOWER_G:Int = 103
Const ASC_LOWER_H:Int = 104
Const ASC_LOWER_I:Int = 105
Const ASC_LOWER_J:Int = 106
Const ASC_LOWER_K:Int = 107
Const ASC_LOWER_L:Int = 108
Const ASC_LOWER_M:Int = 109
Const ASC_LOWER_N:Int = 110
Const ASC_LOWER_O:Int = 111
Const ASC_LOWER_P:Int = 112
Const ASC_LOWER_Q:Int = 113
Const ASC_LOWER_R:Int = 114
Const ASC_LOWER_S:Int = 115
Const ASC_LOWER_T:Int = 116
Const ASC_LOWER_U:Int = 117
Const ASC_LOWER_V:Int = 118
Const ASC_LOWER_W:Int = 119
Const ASC_LOWER_X:Int = 120
Const ASC_LOWER_Y:Int = 121
Const ASC_LOWER_Z:Int = 122
Const ASC_OPEN_BRACE:Int = 123  '{'
Const ASC_PIPE:Int = 124        '|'
Const ASC_CLOSE_BRACE:Int = 125 '}'
Const ASC_TILDE:Int = 126       '~'
Const ASC_DELETE:Int = 127


Removed a test usage of 'Connect' in the cursor demo.

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

Strict

Public

' Preprocessor related:

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

' Imports:
Import mojo

Import brl.stream

Import networking

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

	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)

		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()
		Cls(205.0, 205.0, 205.0)

		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

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

Strict

Public

' Preprocessor related:

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

' Imports:
Import mojo

Import brl.stream

Import networking

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

	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)

		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()
		Cls(205.0, 205.0, 205.0)

		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")
			Connect("192.168.1.32")

			' 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


Added import text driver module

Added import text driver module

Strict

Import "native/flixel.${TARGET}.${LANG}"

Import "vendor/${FLX_TEXT_DRIVER}/${FLX_TEXT_DRIVER}.monkey"

#If TARGET = "html5"
	#If FLX_WEBGL_ENABLED = "1"
		Import "native/webgl/mojo.${TARGET}.gl.min.${LANG}"
	#End
#End

Extern

#If LANG="cpp"
	Function IsMobile:Bool() = "flixel::isMobile"
#ElseIf TARGET = "xna" Or TARGET = "psm"
	Function IsMobile:Bool() = "flixel.functions.isMobile"
#Else
	Function IsMobile:Bool() = "flixel.isMobile"
#End

#If TARGET = "xna" Or TARGET = "psm"
	Function FlxOpenURL:Void(url:String) = "flixel.functions.openURL"
#End

#If TARGET = "html5"
	Function IsIE:Bool() = "flixel.isIE"
#End

Strict

Import "native/flixel.${TARGET}.${LANG}"

#If TARGET = "html5"
	#If FLX_WEBGL_ENABLED = "1"
		Import "native/webgl/mojo.${TARGET}.gl.min.${LANG}"
	#End
#End

Extern

#If LANG="cpp"
	Function IsMobile:Bool() = "flixel::isMobile"
#ElseIf TARGET = "xna" Or TARGET = "psm"
	Function IsMobile:Bool() = "flixel.functions.isMobile"
#Else
	Function IsMobile:Bool() = "flixel.isMobile"
#End

#If TARGET = "xna" Or TARGET = "psm"
	Function FlxOpenURL:Void(url:String) = "flixel.functions.openURL"
#End

#If TARGET = "html5"
	Function IsIE:Bool() = "flixel.isIE"
#End


Oops... Image loader fix returned

Oops... Image loader fix returned

Strict

Import mojo

Import flxextern
Import flxpoint
Import flxobject
Import flxcamera
Import flxg

Import system.flxanim
Import system.flxcolor

Import "data/flx_default.png"

Class FlxSprite Extends FlxObject

	Global ClassObject:FlxClass = new FlxSpriteClass()

	Field origin:FlxPoint

	Field offset:FlxPoint

	Field scale:FlxPoint

	Field blend:Int

	Field finished:Bool

	Field frameWidth:Int

	Field frameHeight:Int

	Field frames:Int

	Field dirty:Bool

	Field _color:FlxColor

	Field _mixedColor:FlxColor

	Field _camera:FlxCamera

Private
	Global _GraphicLoader:FlxGraphicLoader = New FlxGraphicLoader()

	Field _animations:StringMap<FlxAnim>

	Field _flipped:Bool

	Field _flipNeeded:Bool

	Field _curAnim:FlxAnim

	Field _curFrame:Int

	Field _curIndex:Int

	Field _frameTimer:Float

	Field _callback:FlxAnimationListener

	Field _facing:Int

	Field _alpha:Float

	Field _bakedRotation:Float

	Field _pixels:Image

	Field _matrix:Float[6]

	Field _surfaceColor:FlxColor

	Field _halfWidth:Float

	Field _halfHeight:Float

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

		offset = New FlxPoint()
		origin = New FlxPoint()

		scale = New FlxPoint(1.0, 1.0)
		_alpha = 1
		_color = New FlxColor(FlxG.WHITE)
		blend = AlphaBlend

		finished = False
		_facing = RIGHT
		_animations = New StringMap<FlxAnim>
		_flipped = False
		_curAnim = Null
		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0

		_callback = Null

		_surfaceColor = New FlxColor(FlxG.WHITE)
		_mixedColor = New FlxColor(FlxG.WHITE)

		If (simpleGraphic.Length() = 0)
			simpleGraphic = FlxG.DATA_PREFIX + "default"
		End If

		LoadGraphic(simpleGraphic)
	End Method

	Method Destroy:Void()
		If (_animations <> Null) Then
			_animations.Clear()
			_animations = Null
		End If

		offset = Null
		origin = Null
		scale = Null
		_curAnim = Null
		_callback = Null
		_color = Null
		_surfaceColor = Null
		_mixedColor = Null
		_camera = Null
	End Method

	Method LoadGraphic:FlxSprite(graphic:String, animated:Bool = False, reverse:Bool = False, width:Int = 0, height:Int = 0, unique:Bool = False)
		_bakedRotation = 0

		_GraphicLoader.name = graphic
		_GraphicLoader.animated = animated
		_GraphicLoader.width = width
		_GraphicLoader.height = height

		_pixels = FlxG.AddBitmap(graphic, _GraphicLoader, unique)

		If (reverse) Then
			_flipped = True
		Else
			_flipped = True
		End if

		Self.width = _pixels.Width()
		frameWidth = Self.width

		Self.height = _pixels.Height()
		frameHeight = Self.height

		_ResetHelpers()

		Return Self
	End Method

	Method LoadRotatedGraphic:FlxSprite(graphic:String, rotations:Int = 16, frame:Int = -1)
		'TODO
		Return Null
	End Method

	Method MakeGraphic:FlxSprite(width:Int, height:Int, color:Int = FlxG.WHITE)
		_pixels = Null
		_bakedRotation = 0
		_surfaceColor.SetARGB(color)

		Self.width = width
		frameWidth = width
		Self.height = height
		frameHeight = height

		_ResetHelpers()

		Return Self
	End Method

	Method PostUpdate:Void()
		Super.PostUpdate()
		_UpdateAnimation()
	End Method

	Method Draw:Void()
		If (_flickerTimer <> 0) Then
			_flicker = Not _flicker
			If (_flicker) Return
		End If

		If (Not OnScreen(FlxG._CurrentCamera)) Return

		If (dirty) _CalcFrame()

		_camera = FlxG._CurrentCamera

		If (FlxG._LastDrawingBlend <> blend) Then
			SetBlend(blend)
			FlxG._LastDrawingBlend = blend
		End If

		_point.x = x - int(_camera.scroll.x * scrollFactor.x) - offset.x
		_point.y = y - int(_camera.scroll.y * scrollFactor.y) - offset.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

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

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

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

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
					FlxG._LastDrawingAlpha = _mixedAlpha
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _alpha) Then
					SetAlpha(_alpha)
					FlxG._LastDrawingAlpha = _alpha
				End If
			End If

			If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
				If (Not _flipNeeded) Then
					DrawImage(_pixels, _point.x, _point.y, _curIndex)
				Else
					PushMatrix()
						Transform(-1, 0, 0, 1, _point.x + _halfWidth, _point.y + _halfHeight)
						DrawImage(_pixels, -_halfWidth, -_halfHeight, _curIndex)
					PopMatrix()
				End If
			Else
				PushMatrix()
					'Translate
					_matrix[4] = _point.x + origin.x
					_matrix[5] = _point.y + origin.y

					'Scale
					_matrix[0] = scale.x
					_matrix[3] = scale.y

					'Rotate
					If (angle <> 0 And _bakedRotation = 0) Then
						Local sin:Float = -Sin(angle)
						Local cos:Float = Cos(angle)

						_matrix[1] = -sin * _matrix[3]
						_matrix[2] = _matrix[0] * sin
						_matrix[0] *= cos
						_matrix[3] = cos * _matrix[3]
					End If

					Transform(_matrix[0], _matrix[1], _matrix[2], _matrix[3], _matrix[4], _matrix[5])

					If (_flipNeeded) Then
						Transform(-1, 0, 0, 1, _halfWidth - origin.x, _halfHeight - origin.y)
					End If

					DrawImage(_pixels, -origin.x, -origin.y, _curIndex)
				PopMatrix()
			End If
		Else
			If (_camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_surfaceColor, _camera._color)

				If (_color.argb <> FlxG.WHITE) Then
					_mixedColor.MixRGB(_color)
				End If

				If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
					SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
					FlxG._LastDrawingColor = _mixedColor.argb
				End If
			Else
				If (_color.argb <> FlxG.WHITE) Then
					_mixedColor.MixRGB(_surfaceColor, _color)

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

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

				If (_surfaceColor.a < 1) Then
					_mixedAlpha *= _surfaceColor.a
				End If

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
					FlxG._LastDrawingAlpha = _mixedAlpha
				End If
			Else
				If (_surfaceColor.a < 1) Then
					Local _mixedAlpha:Float = _surfaceColor.a * _alpha

					If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
						SetAlpha(_mixedAlpha)
						FlxG._LastDrawingAlpha = _mixedAlpha
					End If
				Else
					If (FlxG._LastDrawingAlpha <> _alpha) Then
						SetAlpha(_alpha)
						FlxG._LastDrawingAlpha = _alpha
					End If
				End If
			End If

			If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
				_DrawSurface(_point.x, _point.y)
			Else
				PushMatrix()
					'Translate
					_matrix[4] = _point.x + origin.x
					_matrix[5] = _point.y + origin.y

					'Scale
					_matrix[0] = scale.x
					_matrix[3] = scale.y

					'Rotate
					If (angle <> 0 And _bakedRotation = 0) Then
						Local sin:Float = -Sin(angle)
						Local cos:Float = Cos(angle)

						_matrix[1] = -sin * _matrix[3]
						_matrix[2] = _matrix[0] * sin
						_matrix[0] *= cos
						_matrix[3] = cos * _matrix[3]
					End If

					Transform(_matrix[0], _matrix[1], _matrix[2], _matrix[3], _matrix[4], _matrix[5])
					_DrawSurface(-origin.x, -origin.y)
				PopMatrix()
			End If
		End If

		_VisibleCount += 1;
		If(FlxG.VisualDebug And Not ignoreDrawDebug) DrawDebug(_camera);
	End Method

	Method DrawFrame:Void(force:Bool = False)
		If (force Or dirty) _CalcFrame()
	End Method

	Method AddAnimation:Void(name:String, frames:Int[], frameRate:Float = 0, looped:Bool = True)
		_animations.Set(name, New FlxAnim(name, frames, frameRate, looped))
	End Method

	Method AddAnimationCallback:Void(animationCallback:FlxAnimationCallback)
		_callback = animationCallback
	End Method

	Method Play:Void(animName:String, force:Bool = False)
		If (Not force And _curAnim <> Null And animName = _curAnim.name And( _curAnim.looped Or Not finished )) Return

		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0

		Local anim:FlxAnim = _animations.Get(animName)

		If (anim <> Null) Then
			_curAnim = anim

			If (_curAnim.delay <= 0) Then
				finished = True
			Else
				finished = False
			End If

			_curIndex = _curAnim.frames[_curFrame]
			dirty = True
			Return
		End If

		FlxG.Log("WARNING: No animation called ~q" + animName + "~q")
	End Method

	Method RandomFrame:Void()
		_curAnim = Null

		If (_pixels <> Null) Then
			_curIndex = Int(FlxG.Random() * (_pixels.Frames() - 1))
		Else
			_curIndex = 0
		End If

		dirty = True
	End Method

	Method SetOriginToCorner:Void()
		origin.x = 0
		origin.y = 0
	End Method

	Method CenterOffsets:Void(adjustPosition:Bool = False)
		offset.x = (frameWidth - width) * .5
		offset.y = (frameHeight - height) * .5

		If (adjustPosition) Then
			x += offset.x
			y += offset.y
		End If
	End Method

	Method Pixels:Image() Property
		Return _pixels
	End Method

	Method Pixels:Void(pixels:Image) Property
		_pixels = pixels

		If (_pixels <> Null) Then
			width = pixels.Width()
			height = pixels.Height()
		Else
			width = 0
			height = 0
		End If

		frameWidth = width
		frameHeight = height
		_ResetHelpers()
	End Method

	Method Facing:Int() Property
		Return _facing
	End Method

	Method Facing:Void(direction:Int) Property
		If (_facing <> direction) dirty = True
		_facing = direction

		_flipNeeded = (_flipped And _facing = LEFT)
	End Method

	Method Alpha:Float() Property
		Return _alpha
	End Method

	Method Alpha:Void(alpha:Float) Property
		If (alpha < 0) alpha = 0
		_alpha = alpha
	End Method

	Method Color:Int() Property
		Return _color.argb
	End Method

	Method Color:Void(color:Int) Property
		_color.SetRGB(color)
	End Method

	Method Frame:Int() Property
		Return _curIndex
	End Method

	Method Frame:Void(frame:Int) Property
		_curAnim = Null
		_curIndex = frame
		dirty = True
	End Method

	Method OnScreen:Bool(camera:FlxCamera)
		If (camera = Null) camera = FlxG.Camera
		GetScreenXY(_point, camera)

		_point.x = _point.x - offset.x
		_point.y = _point.y - offset.y

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			Return _point.x + frameWidth > 0 And _point.x < camera.Width And _point.y + frameHeight > 0 And _point.y < camera.Height
		End If

		Local radius:Float = Sqrt(_halfWidth * _halfWidth + _halfHeight * _halfHeight) * Max(Abs(scale.x), Abs(scale.y))
		_point.x += _halfWidth
		_point.y += _halfHeight

		Return _point.x + radius > 0 And _point.x - radius < camera.Width And _point.y + radius > 0 And _point.y - radius < camera.Height
	End Method

	Method _DrawSurface:Void(x:Float, y:Float)
		DrawRect(x, y, frameWidth, frameHeight)
	End Method

	Method _ResetHelpers:Void()
		_halfWidth = frameWidth * .5
		_halfHeight = frameHeight * .5
		origin.Make(_halfWidth, _halfHeight)

		If (_pixels <> Null) Then
			frames = _pixels.Frames()
		Else
			frames = 1
		End If

		_curIndex = 0
		_mixedColor.SetARGB(FlxG.WHITE)
	End Method

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

Private
	Method _UpdateAnimation:Void()
		If (_bakedRotation > 0) Then
			Local oldIndex:Int = _curIndex
			Local angleHelper:Int = angle Mod 360

			If (angleHelper < 0) angleHelper += 360
			_curIndex = angleHelper / _bakedRotation + 0.5

			If (oldIndex <> _curIndex) dirty = True

		ElseIf (_curAnim <> Null And _curAnim.delay > 0 And (_curAnim.looped Or Not finished))
			_frameTimer += FlxG.Elapsed
			While (_frameTimer > _curAnim.delay)
				_frameTimer -= _curAnim.delay

				If (_curFrame = _curAnim.frames.Length() - 1) Then
					If (_curAnim.looped) _curFrame = 0
					finished = True
				Else
					_curFrame += 1
				End If

				_curIndex = _curAnim.frames[_curFrame]
				dirty = True
			Wend
		End If

		If (dirty) _CalcFrame()
	End Method

	Method _CalcFrame:Void()
		If (_callback <> Null) Then
			If (_curAnim <> Null) Then
				_callback.OnAnimationFrame(_curAnim.name, _curFrame, _curIndex)
			Else
				_callback.OnAnimationFrame("", _curFrame, _curIndex)
			End If
		End If
		dirty = False
	End Method

End Class

Private
Class FlxSpriteClass Implements FlxClass

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

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

End Class

Class FlxGraphicLoader Extends FlxResourceLoader<Image>

	Field name:String
	Field animated:Bool
	Field width:Float
	Field height:Float

	Method Load:Image(name:String)
		Local image:Image = LoadImage(FlxAssetsManager.GetImagePath(Self.name))

		If (Not animated) Then
			Return image
		Else
			Local frames:Int

			If (width = 0 And height = 0) Then
				width = image.Height()
			ElseIf (width = 0) Then
				width = height
			ElseIf (height = 0) Then
				height = width
			End If

			If (height = 0) Then
				frames = Ceil(image.Width() / width)
			Else
				frames = Ceil((image.Width() * image.Height()) / (width * height))
			End If

			Return image.GrabImage(0, 0, width, height, frames)
		End If
	End Method

End Class

Strict

Import mojo

Import flxextern
Import flxpoint
Import flxobject
Import flxcamera
Import flxg

Import system.flxanim
Import system.flxcolor

Import "data/flx_default.png"

Class FlxSprite Extends FlxObject

	Global ClassObject:FlxClass = new FlxSpriteClass()

	Field origin:FlxPoint

	Field offset:FlxPoint

	Field scale:FlxPoint

	Field blend:Int

	Field finished:Bool

	Field frameWidth:Int

	Field frameHeight:Int

	Field frames:Int

	Field dirty:Bool

	Field _color:FlxColor

	Field _mixedColor:FlxColor

	Field _camera:FlxCamera

Private
	Global _GraphicLoader:FlxGraphicLoader = New FlxGraphicLoader()

	Field _animations:StringMap<FlxAnim>

	Field _flipped:Bool

	Field _flipNeeded:Bool

	Field _curAnim:FlxAnim

	Field _curFrame:Int

	Field _curIndex:Int

	Field _frameTimer:Float

	Field _callback:FlxAnimationListener

	Field _facing:Int

	Field _alpha:Float

	Field _bakedRotation:Float

	Field _pixels:Image

	Field _matrix:Float[6]

	Field _surfaceColor:FlxColor

	Field _halfWidth:Float

	Field _halfHeight:Float

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

		offset = New FlxPoint()
		origin = New FlxPoint()

		scale = New FlxPoint(1.0, 1.0)
		_alpha = 1
		_color = New FlxColor(FlxG.WHITE)
		blend = AlphaBlend

		finished = False
		_facing = RIGHT
		_animations = New StringMap<FlxAnim>
		_flipped = False
		_curAnim = Null
		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0

		_callback = Null

		_surfaceColor = New FlxColor(FlxG.WHITE)
		_mixedColor = New FlxColor(FlxG.WHITE)

		If (simpleGraphic.Length() = 0)
			simpleGraphic = FlxG.DATA_PREFIX + "default"
		End If

		LoadGraphic(simpleGraphic)
	End Method

	Method Destroy:Void()
		If (_animations <> Null) Then
			_animations.Clear()
			_animations = Null
		End If

		offset = Null
		origin = Null
		scale = Null
		_curAnim = Null
		_callback = Null
		_color = Null
		_surfaceColor = Null
		_mixedColor = Null
		_camera = Null
	End Method

	Method LoadGraphic:FlxSprite(graphic:String, animated:Bool = False, reverse:Bool = False, width:Int = 0, height:Int = 0, unique:Bool = False)
		_bakedRotation = 0

		_GraphicLoader.name = graphic
		_GraphicLoader.animated = animated
		_GraphicLoader.width = width
		_GraphicLoader.height = height

		_pixels = FlxG.AddBitmap(graphic, _GraphicLoader, unique)

		If (reverse) Then
			_flipped = True
		Else
			_flipped = True
		End if

		Self.width = _pixels.Width()
		frameWidth = Self.width

		Self.height = _pixels.Height()
		frameHeight = Self.height

		_ResetHelpers()

		Return Self
	End Method

	Method LoadRotatedGraphic:FlxSprite(graphic:String, rotations:Int = 16, frame:Int = -1)
		'TODO
		Return Null
	End Method

	Method MakeGraphic:FlxSprite(width:Int, height:Int, color:Int = FlxG.WHITE)
		_pixels = Null
		_bakedRotation = 0
		_surfaceColor.SetARGB(color)

		Self.width = width
		frameWidth = width
		Self.height = height
		frameHeight = height

		_ResetHelpers()

		Return Self
	End Method

	Method PostUpdate:Void()
		Super.PostUpdate()
		_UpdateAnimation()
	End Method

	Method Draw:Void()
		If (_flickerTimer <> 0) Then
			_flicker = Not _flicker
			If (_flicker) Return
		End If

		If (Not OnScreen(FlxG._CurrentCamera)) Return

		If (dirty) _CalcFrame()

		_camera = FlxG._CurrentCamera

		If (FlxG._LastDrawingBlend <> blend) Then
			SetBlend(blend)
			FlxG._LastDrawingBlend = blend
		End If

		_point.x = x - int(_camera.scroll.x * scrollFactor.x) - offset.x
		_point.y = y - int(_camera.scroll.y * scrollFactor.y) - offset.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

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

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

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

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
					FlxG._LastDrawingAlpha = _mixedAlpha
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _alpha) Then
					SetAlpha(_alpha)
					FlxG._LastDrawingAlpha = _alpha
				End If
			End If

			If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
				If (Not _flipNeeded) Then
					DrawImage(_pixels, _point.x, _point.y, _curIndex)
				Else
					PushMatrix()
						Transform(-1, 0, 0, 1, _point.x + _halfWidth, _point.y + _halfHeight)
						DrawImage(_pixels, -_halfWidth, -_halfHeight, _curIndex)
					PopMatrix()
				End If
			Else
				PushMatrix()
					'Translate
					_matrix[4] = _point.x + origin.x
					_matrix[5] = _point.y + origin.y

					'Scale
					_matrix[0] = scale.x
					_matrix[3] = scale.y

					'Rotate
					If (angle <> 0 And _bakedRotation = 0) Then
						Local sin:Float = -Sin(angle)
						Local cos:Float = Cos(angle)

						_matrix[1] = -sin * _matrix[3]
						_matrix[2] = _matrix[0] * sin
						_matrix[0] *= cos
						_matrix[3] = cos * _matrix[3]
					End If

					Transform(_matrix[0], _matrix[1], _matrix[2], _matrix[3], _matrix[4], _matrix[5])

					If (_flipNeeded) Then
						Transform(-1, 0, 0, 1, _halfWidth - origin.x, _halfHeight - origin.y)
					End If

					DrawImage(_pixels, -origin.x, -origin.y, _curIndex)
				PopMatrix()
			End If
		Else
			If (_camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_surfaceColor, _camera._color)

				If (_color.argb <> FlxG.WHITE) Then
					_mixedColor.MixRGB(_color)
				End If

				If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
					SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
					FlxG._LastDrawingColor = _mixedColor.argb
				End If
			Else
				If (_color.argb <> FlxG.WHITE) Then
					_mixedColor.MixRGB(_surfaceColor, _color)

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

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

				If (_surfaceColor.a < 1) Then
					_mixedAlpha *= _surfaceColor.a
				End If

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
					FlxG._LastDrawingAlpha = _mixedAlpha
				End If
			Else
				If (_surfaceColor.a < 1) Then
					Local _mixedAlpha:Float = _surfaceColor.a * _alpha

					If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
						SetAlpha(_mixedAlpha)
						FlxG._LastDrawingAlpha = _mixedAlpha
					End If
				Else
					If (FlxG._LastDrawingAlpha <> _alpha) Then
						SetAlpha(_alpha)
						FlxG._LastDrawingAlpha = _alpha
					End If
				End If
			End If

			If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
				_DrawSurface(_point.x, _point.y)
			Else
				PushMatrix()
					'Translate
					_matrix[4] = _point.x + origin.x
					_matrix[5] = _point.y + origin.y

					'Scale
					_matrix[0] = scale.x
					_matrix[3] = scale.y

					'Rotate
					If (angle <> 0 And _bakedRotation = 0) Then
						Local sin:Float = -Sin(angle)
						Local cos:Float = Cos(angle)

						_matrix[1] = -sin * _matrix[3]
						_matrix[2] = _matrix[0] * sin
						_matrix[0] *= cos
						_matrix[3] = cos * _matrix[3]
					End If

					Transform(_matrix[0], _matrix[1], _matrix[2], _matrix[3], _matrix[4], _matrix[5])
					_DrawSurface(-origin.x, -origin.y)
				PopMatrix()
			End If
		End If

		_VisibleCount += 1;
		If(FlxG.VisualDebug And Not ignoreDrawDebug) DrawDebug(_camera);
	End Method

	Method DrawFrame:Void(force:Bool = False)
		If (force Or dirty) _CalcFrame()
	End Method

	Method AddAnimation:Void(name:String, frames:Int[], frameRate:Float = 0, looped:Bool = True)
		_animations.Set(name, New FlxAnim(name, frames, frameRate, looped))
	End Method

	Method AddAnimationCallback:Void(animationCallback:FlxAnimationCallback)
		_callback = animationCallback
	End Method

	Method Play:Void(animName:String, force:Bool = False)
		If (Not force And _curAnim <> Null And animName = _curAnim.name And( _curAnim.looped Or Not finished )) Return

		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0

		Local anim:FlxAnim = _animations.Get(animName)

		If (anim <> Null) Then
			_curAnim = anim

			If (_curAnim.delay <= 0) Then
				finished = True
			Else
				finished = False
			End If

			_curIndex = _curAnim.frames[_curFrame]
			dirty = True
			Return
		End If

		FlxG.Log("WARNING: No animation called ~q" + animName + "~q")
	End Method

	Method RandomFrame:Void()
		_curAnim = Null

		If (_pixels <> Null) Then
			_curIndex = Int(FlxG.Random() * (_pixels.Frames() - 1))
		Else
			_curIndex = 0
		End If

		dirty = True
	End Method

	Method SetOriginToCorner:Void()
		origin.x = 0
		origin.y = 0
	End Method

	Method CenterOffsets:Void(adjustPosition:Bool = False)
		offset.x = (frameWidth - width) * .5
		offset.y = (frameHeight - height) * .5

		If (adjustPosition) Then
			x += offset.x
			y += offset.y
		End If
	End Method

	Method Pixels:Image() Property
		Return _pixels
	End Method

	Method Pixels:Void(pixels:Image) Property
		_pixels = pixels

		If (_pixels <> Null) Then
			width = pixels.Width()
			height = pixels.Height()
		Else
			width = 0
			height = 0
		End If

		frameWidth = width
		frameHeight = height
		_ResetHelpers()
	End Method

	Method Facing:Int() Property
		Return _facing
	End Method

	Method Facing:Void(direction:Int) Property
		If (_facing <> direction) dirty = True
		_facing = direction

		_flipNeeded = (_flipped And _facing = LEFT)
	End Method

	Method Alpha:Float() Property
		Return _alpha
	End Method

	Method Alpha:Void(alpha:Float) Property
		If (alpha < 0) alpha = 0
		_alpha = alpha
	End Method

	Method Color:Int() Property
		Return _color.argb
	End Method

	Method Color:Void(color:Int) Property
		_color.SetRGB(color)
	End Method

	Method Frame:Int() Property
		Return _curIndex
	End Method

	Method Frame:Void(frame:Int) Property
		_curAnim = Null
		_curIndex = frame
		dirty = True
	End Method

	Method OnScreen:Bool(camera:FlxCamera)
		If (camera = Null) camera = FlxG.Camera
		GetScreenXY(_point, camera)

		_point.x = _point.x - offset.x
		_point.y = _point.y - offset.y

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			Return _point.x + frameWidth > 0 And _point.x < camera.Width And _point.y + frameHeight > 0 And _point.y < camera.Height
		End If

		Local radius:Float = Sqrt(_halfWidth * _halfWidth + _halfHeight * _halfHeight) * Max(Abs(scale.x), Abs(scale.y))
		_point.x += _halfWidth
		_point.y += _halfHeight

		Return _point.x + radius > 0 And _point.x - radius < camera.Width And _point.y + radius > 0 And _point.y - radius < camera.Height
	End Method

	Method _DrawSurface:Void(x:Float, y:Float)
		DrawRect(x, y, frameWidth, frameHeight)
	End Method

	Method _ResetHelpers:Void()
		_halfWidth = frameWidth * .5
		_halfHeight = frameHeight * .5
		origin.Make(_halfWidth, _halfHeight)

		If (_pixels <> Null) Then
			frames = _pixels.Frames()
		Else
			frames = 1
		End If

		_curIndex = 0
		_mixedColor.SetARGB(FlxG.WHITE)
	End Method

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

Private
	Method _UpdateAnimation:Void()
		If (_bakedRotation > 0) Then
			Local oldIndex:Int = _curIndex
			Local angleHelper:Int = angle Mod 360

			If (angleHelper < 0) angleHelper += 360
			_curIndex = angleHelper / _bakedRotation + 0.5

			If (oldIndex <> _curIndex) dirty = True

		ElseIf (_curAnim <> Null And _curAnim.delay > 0 And (_curAnim.looped Or Not finished))
			_frameTimer += FlxG.Elapsed
			While (_frameTimer > _curAnim.delay)
				_frameTimer -= _curAnim.delay

				If (_curFrame = _curAnim.frames.Length() - 1) Then
					If (_curAnim.looped) _curFrame = 0
					finished = True
				Else
					_curFrame += 1
				End If

				_curIndex = _curAnim.frames[_curFrame]
				dirty = True
			Wend
		End If

		If (dirty) _CalcFrame()
	End Method

	Method _CalcFrame:Void()
		If (_callback <> Null) Then
			If (_curAnim <> Null) Then
				_callback.OnAnimationFrame(_curAnim.name, _curFrame, _curIndex)
			Else
				_callback.OnAnimationFrame("", _curFrame, _curIndex)
			End If
		End If
		dirty = False
	End Method

End Class

Private
Class FlxSpriteClass Implements FlxClass

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

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

End Class

Class FlxGraphicLoader Extends FlxResourceLoader<Image>

	Field name:String
	Field animated:Bool
	Field width:Float
	Field height:Float

	Method Load:Image(name:String)
		Local image:Image = LoadImage(FlxAssetsManager.GetImagePath(Self.name))

		If (Not animated) Then
			Return image
		Else
			Local frames:Int

			If (width = 0 And height = 0) Then
				width = image.Height()
			ElseIf (width = 0) Then
				width = height
			Else
				height = width
			End If

			If (height = 0) Then
				frames = Ceil(image.Width() / width)
			Else
				frames = Ceil((image.Width() * image.Height()) / (width * height))
			End If

			Return image.GrabImage(0, 0, width, height, frames)
		End If
	End Method

End Class


TOANDFRO tween type added #46

TOANDFRO tween type added #46

Strict

Import flixel.flxbasic
Import flixel.flxg
Import util.flxease

Class FlxTween

	Const PERSIST:Int = 0

	Const LOOPING:Int = 1

	Const ONESHOT:Int = 2

	Const TOANDFRO:Int = 3

	Field active:Bool

	Field complete:FlxTweenListener

	Field _finish:Bool

	Field _parent:FlxBasic

	Field _prev:FlxTween

	Field _next:FlxTween

	Field _target:Float

	Field _ease:FlxEaseFunction

	Field _type:Int

	Field _t:Float

	Field _time:Float

	Field _backward:Bool

	Field _times:Int

Public
	Method New(duration:Float, type:Int = 0, complete:FlxTweenListener = Null, ease:FlxEaseFunction = Null)
		_target = duration
		_type = type
		Self.complete = complete
		_ease = ease
		_t = 0
		_backward = False
	End Method

	Method Update:Void()
		_time += FlxG.Elapsed
		_t = _time / _target

		If (_ease <> Null) _t = _ease.Ease(_t)
		If (_backward) _t = 1 - _t

		If (_time >= _target) Then
			If ( Not _backward) Then
				_t = 1
			Else
				_t = 0
			End If
			_finish = True
		End If
	End Method

	Method Start:Void()
		_time = 0

		If (_target = 0) Then
			active = False
			Return
		End If

		active = True
	End Method

	Method Finish:Void()
		Select(_type)
			Case PERSIST
				_time = _target
				active = False

			Case LOOPING
				_time Mod = _target
				_t = _time / _target

				If (_ease <> Null And _t > 0 And _t < 1) Then
					_t = _ease.Ease(_t)
				End If

				Start()

			Case ONESHOT
				_time = _target
				active = False
				_parent.RemoveTween(Self)

			Case TOANDFRO
				_time Mod = _target
				_t = _time / _target

				If (_ease <> Null And _t > 0 And _t < 1) Then
					_t = _ease.Ease(_t)
				End If

				If (_backward) _t = 1 - _t
				_backward = Not _backward

				Start()
		End Select

		_finish = False
		If (complete <> Null) complete.OnTweenComplete()
	End Method

	Method Percent:Float() Property
		Return _time / _target
	End Method

	Method Percent:Void(value:Float) Property
		_time = _target * value
	End Method

	Method Scale:Float() Property
		Return _t
	End Method

End Class

Interface FlxTweenListener

	Method OnTweenComplete:Void()

End Interface

Strict

Import flixel.flxbasic
Import flixel.flxg
Import util.flxease

Class FlxTween

	Const PERSIST:Int = 0

	Const LOOPING:Int = 1

	Const ONESHOT:Int = 2

	Field active:Bool

	Field complete:FlxTweenListener

	Field _finish:Bool

	Field _parent:FlxBasic

	Field _prev:FlxTween

	Field _next:FlxTween

	Field _target:Float

	Field _ease:FlxEaseFunction

	Field _type:Int

	Field _t:Float

	Field _time:Float

Public
	Method New(duration:Float, type:Int = 0, complete:FlxTweenListener = Null, ease:FlxEaseFunction = Null)
		_target = duration
		_type = type
		Self.complete = complete
		_ease = ease
		_t = 0
	End Method

	Method Update:Void()
		_time += FlxG.Elapsed
		_t = _time / _target

		If (_ease <> Null) _t = _ease.Ease(_t)

		If (_time >= _target) Then
			_t = 1
			_finish = True
		End If
	End Method

	Method Start:Void()
		_time = 0

		If (_target = 0) Then
			active = False
			Return
		End If

		active = True
	End Method

	Method Finish:Void()
		Select(_type)
			Case PERSIST
				_time = _target
				active = False

			Case LOOPING
				_time Mod = _target
				_t = _time / _target

				If (_ease <> Null And _t > 0 And _t < 1) Then
					_t = _ease.Ease(_t)
				End If

				Start()

			Case ONESHOT
				_time = _target
				active = False
				_parent.RemoveTween(Self)
		End Select

		_finish = False
		If (complete <> Null) complete.OnTweenComplete()
	End Method

	Method Percent:Float() Property
		Return _time / _target
	End Method

	Method Percent:Void(value:Float) Property
		_time = _target * value
	End Method

	Method Scale:Float() Property
		Return _t
	End Method

End Class

Interface FlxTweenListener

	Method OnTweenComplete:Void()

End Interface


Added simple camera test

Added simple camera test

Strict

Import flixel

Class CamerasTest Extends FlxGame

	Method New()
		Super.New(640, 480, CamerasTestState.CREATOR)
	End Method

End Class

Class CamerasTestStateCreator Implements FlxClassCreator

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

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

End Class

Class CamerasTestState Extends FlxState

	Global CREATOR:FlxClassCreator = new CamerasTestStateCreator()

	Method Create:Void()
		Local camera:FlxCamera = new FlxCamera(10, 10, 100, 100)
		camera.bgColor = FlxG.RED
		FlxG.AddCamera(camera)
	End Method

End Class

#Rem
Code on ActionScript for for comparison:

import org.flixel.*
[SWF(width="640", height="480", backgroundColor="#000000")]

public class CamerasTest extends FlxGame {

	public function CamerasTest() {
		super(640, 480, CamerasTestState);
	}

}

public class CamerasTestState extends FlxState{

	override public function create():void {
		var camera:FlxCamera = new FlxCamera(10, 10, 100, 100);
		camera.bgColor = FlxG.RED;
		FlxG.addCamera(camera);
	}
}
#End

Strict

Import flixel

Class CamerasTestStateCreator Implements FlxClassCreator

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

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

End Class

Class CamerasTestState Extends FlxState

	Global CREATOR:FlxClassCreator = new CamerasTestStateCreator()

	Method Create:Void()
		Local camera:FlxCamera = new FlxCamera(10, 10, 100, 100)
		camera.bgColor = FlxG.RED
		FlxG.AddCamera(camera)
	End Method

End Class


install docs added to google payment provider

Strict

Private

' 1) Ensure this two lines exists inside <manifest> of your AndroidManifest.xml
'
'     <uses-permission android:name="android.permission.INTERNET"></uses-permission>
'     <uses-permission android:name="com.android.vending.BILLING"></uses-permission>
'
' 2) Add this reciever inside <application> of your AndroidManifest.xml
'
'    <service android:name="net.robotmedia.billing.BillingService" />
'    <receiver android:name="net.robotmedia.billing.BillingReceiver">
'        <intent-filter>
'            <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
'            <action android:name="com.android.vending.billing.RESPONSE_CODE" />
'            <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
'        </intent-filter>
'    </receiver>
'
' 3) Copy the billing library from the following link into src/
'
'    https://github.com/robotmedia/AndroidBillingLibrary
'
' 4) Patch AndroidBillingLibrary/src/net/robotmedia/billing/BillingRequest.java
'    around line 220 to look like:
'
'    ## BEFORE
'
'    @Override
'    public void onResponseCode(ResponseCode response) {
'        super.onResponseCode(response);
'        if (response == ResponseCode.RESULT_OK) {
'            BillingController.onTransactionsRestored();
'        }
'    }
'
'    ## AFTER
'
'    @Override
'    public void onResponseCode(ResponseCode response) {
'        super.onResponseCode(response);
'        BillingController.onTransactionsRestored();
'    }

Import bono
Import "native/paymentwrapper-google.android.java"

Private Extern

Class PaymentWrapper
    Method Init:Void(publicKey:String)
    Method Purchase:Void(productId:String)
    Method IsBought:Bool(id:String)
    Method IsPurchaseInProgress:Bool()
End

Public

Class PaymentProviderAndroidGoogle Implements PaymentProvider
    Private

    Field wrapper:PaymentWrapper

    Public

    Field publicKey:String

    Method IsProcessing:Bool()
        Initialize()
        Return wrapper.IsPurchaseInProgress()
    End

    Method Purchase:Void(id:String)
        Initialize()
        wrapper.Purchase(id)
    End

    Method IsPurchased:Bool(id:String)
        Initialize()
        Return wrapper.IsBought(id)
    End

    Private

    Method Initialize:Void()
        If wrapper Then Return

        If Not publicKey
            Throw New RuntimeException("Please configure " +
                "PaymentProviderAndroidGoogle.publicKey with your " +
                "public key first.")
        End

        wrapper = New PaymentWrapper()
        wrapper.Init(publicKey)
    End
End

Strict

Private

Import bono
Import "native/paymentwrapper-google.android.java"

Private Extern

Class PaymentWrapper
    Method Init:Void(publicKey:String)
    Method Purchase:Void(productId:String)
    Method IsBought:Bool(id:String)
    Method IsPurchaseInProgress:Bool()
End

Public

Class PaymentProviderAndroidGoogle Implements PaymentProvider
    Private

    Field wrapper:PaymentWrapper

    Public

    Field publicKey:String

    Method IsProcessing:Bool()
        Initialize()
        Return wrapper.IsPurchaseInProgress()
    End

    Method Purchase:Void(id:String)
        Initialize()
        wrapper.Purchase(id)
    End

    Method IsPurchased:Bool(id:String)
        Initialize()
        Return wrapper.IsBought(id)
    End

    Private

    Method Initialize:Void()
        If wrapper Then Return

        If Not publicKey
            Throw New RuntimeException("Please configure " +
                "PaymentProviderAndroidGoogle.publicKey with your " +
                "public key first.")
        End

        wrapper = New PaymentWrapper()
        wrapper.Init(publicKey)
    End
End