Added a few generic-utility commands, etc.

Strict

Public

' Preprocessor related:
#Rem
#If TARGET = "glfw" Or TARGET = "sexy"
	#GLFW_TARGET = True
#End
#End

#If CONFIG = "debug"
	#DEBUG_PRINT = True
#Else
	#DEBUG_PRINT = False
#End

#DEBUG_PRINT_QUOTES = False

' Imports (Public):
Import byteorder
Import imagedimensions
Import retrostrings
Import stringutil
Import vector

#If CONSOLE_IMPLEMENTED
	Import console
#End

Import sizeof
Import time

#If Not BRL_GAMETARGET_IMPLEMENTED
	Import mojoemulator
#End

' Imports (Private):
Private

' Official:

' BRL:
Import brl.databuffer
Import brl.stream
Import brl.filepath

' Unofficial:

' ImmutableOctet:
Import autostream

Public

' Constant variable(s):

' Used mainly for external code where '-9999...' isn't ever going to be valid.
' You should stick to 'Null' in most cases; try not to use this.
Const NOVAR:Int = -999999

Const ErrorTemplate:String = "[ERROR] {Debug}: " ' + Space
Const LogTemplate:String = "[Info] {Debug}: " ' + Space

' Global variable(s) (Public):
#If CONSOLE_IMPLEMENTED
	Global DebugConsole:Console = Null
#End

' A global stack logging randomization seeds.
' This is commonly accessed by the 'PushSeed' and 'PopSeed' functions.
Global SeedStack:Stack<Int> = Null

' Global variable(s) (Private):
Private

' Nothing so far.

Public

' Functions:
Function FLAG:Int(ID:Int)
	Return Pow(2, ID)
End

' This command is little-endian only:
Function AsByte:Int(I:Int)
	Return ((I Shl 24) Shr 24)
End

' This command treats data as 8-bit (Turns something like $F7, into $FFFFFFF7)
' This is only applied on little-endian systems,
' please use this command for hex values (Monkey converts hex on compile-time).
Function PaddedHex:Int(I:Int)
	If (Not BigEndian()) Then
		Return AsByte(I)
	Endif

	Return I
End

Function Transfer:Bool(InputStream:Stream, OutputStream:Stream, DataSize:Int)
	' Check for errors:
	If (DataSize = 0) Then Return False
	If (InputStream = Null Or InputStream.Eof()) Then Return False
	If (OutputStream = Null) Then Return False

	' Local variable(s):
	#If FLAG_TEMP_BUFFERS
		Local Data:DataBuffer = New DataBuffer(DataSize)
		Local DataLength:= DataSize ' Data.Length()

		' Read from the input-stream, then write to the output-stream:
		InputStream.Read(Data, 0, DataLength)
		OutputStream.Write(Data, 0, DataLength)

		Data.Discard()
	#Else
		'Local Data:Int[DataSize]

		For Local I:Int = 1 To DataSize ' Data.Length()
			OutputStream.WriteByte(InputStream.ReadByte())
		Next
	#End

	' Return the default response.
	Return True
End

Function Sq:Float(Input:Float)
	Return Pow(Input, 2.0) ' Input * Input
End

Function Sq:Int(Input:Int)
	Return Pow(Input, 2)
End

Function WrapAngle:Float(A:Float)
	While (A < 0.0)
		A += 360.0
	Wend

	While (A > 360.0)
		A -= 360.0
	Wend

	Return A
End

Function WrapColor:Float(C:Float)
	#Rem
	While (C < 0.0)
		C += 255.0
	Wend

	While (C > 255.0)
		C -= 255.0
	Wend
	#End

	Return C Mod 256.0
End

' This is just a wrapper for the main implementation:
Function PushSeed:Bool()
	Return PushSeed(Seed)
End

' The return value of this function specifies if the seed-stack already existed.
Function PushSeed:Bool(Seed:Int)
	' Local variable(s):
	Local Response:Bool = (SeedStack <> Null)

	If (Not Response) Then
		SeedStack = New IntStack()
	Endif

	SeedStack.Push(Seed)

	' Return the calculated response.
	Return Response
End

' This command retrieves a seed from the seed-stack,
' if no seed exists, the current randomization seed is returned.
Function PopSeed:Int()
	If (SeedStack <> Null) Then
		If (Not SeedStack.IsEmpty()) Then
			Return SeedStack.Pop()
		Endif
	Endif

	' Return the default response.
	Return Seed
End

Function ResizeBuffer:DataBuffer(Buffer:DataBuffer, Size:Int, CopyData:Bool=True, DiscardOldBuffer:Bool=False)
	' Allocate a new data-buffer.
	Local B:= New DataBuffer(Size)

	' Copy the buffer's bytes over to 'B'.
	If (CopyData) Then
		Buffer.CopyBytes(0, B, 0, Buffer.Length())
	Endif

	If (DiscardOldBuffer) Then
		Buffer.Discard()
	Endif

	' Return the new buffer ('B').
	Return B
End

#If CONSOLE_IMPLEMENTED
	Function DebugBind:Bool(C:Console)
		If (C = Null) Then Return False

		DebugConsole = C

		' Return the default response.
		Return True
	End
#End

Function DebugError:Void(E:Throwable, StopExecution:Bool=True)
	#If CONFIG = "debug"
		DebugStop()
	#Else
		DebugPrint("Unknown exception has been thrown; continuing anyway.")
	#End

	Return
End

Function DebugError:Void(Msg:String, StopExecution:Bool=True)
	#If DEBUG_PRINT
		' Local variable(s):
		Local TempMsg:String

		If (StopExecution) Then
			TempMsg = ErrorTemplate
		Else
			TempMsg = LogTemplate
		Endif

		#If DEBUG_PRINT_QUOTES
			Msg = Tempmsg + Quote + Msg + Quote
		#Else
			Msg = TempMsg + Msg
		#End

		#If CONSOLE_IMPLEMENTED
			If (DebugConsole <> Null) Then
				DebugConsole.WriteLine(Msg, True)
			Else
		#End

		Print(Msg)

		#If CONSOLE_IMPLEMENTED
			Endif
		#End
	#Else
		' This may change later:
		If (StopExecution) Then
			Msg = ErrorTemplate + Msg
		Endif
	#End

	If (StopExecution) Then
		#If CONFIG = "debug"
			#If DEBUG_PRINT
				DebugStop()
			#Else
				Error(Msg)
			#End
		#Else
			#If DEBUG_PRINT
				Local FinalStr:String = (ErrorTemplate + Quote + "Attempt to stop execution failed. (Reason: Release-mode)" + Quote)

				#If CONSOLE_IMPLEMENTED
					If (DebugConsole <> Null) Then
						DebugConsole.WriteLine(FinalStr, True)
					Else
				#End

				Print(FinalStr)

				#If CONSOLE_IMPLEMENTED
					Endif
				#End
			#Else
				Error(Msg)
			#End
		#End
	Endif

	#If CONFIG = "debug"
		' Nothing so far.
	#Else
		' Nothing so far.
	#End

	Return
End

Function DebugPrint:Void(Str:String, StopExecution:Bool=False)
	DebugError(Str, StopExecution)

	Return
End

' Classes:
Class GenericUtilities<T>
	' Constant variable(s):

	' Comparison response codes (Positive numbers are for error-reporting):
	Const COMPARE_RESPONSE_IDENTICAL:Int			= -1
	Const COMPARE_RESPONSE_WRONG_LENGTH:Int			= -2

	' Other:
	Const AUTO:Int = -1

	' Global variable(s):

	' For internal and extern use. That being said, please do not modify this value.
	Global NIL:T

	' Functions:
	Function AsString:String(Input:Bool)
		Return BoolToString(Input)
	End

	Function AsString:String(S:Stream, Length:Int=AUTO, Encoding:String="utf8")
		If (Length = AUTO) Then
			Length = S.Length()
		Endif

		Return S.ReadString(Length, Encoding)
	End

	Function AsString:String(Input:T[], Offset:Int=0, Length:Int=AUTO, AddSpaces:Bool=True)
		' Local variable(s):
		Local Output:String = LeftBracket

		' If no length was specified, use the array's length.
		If (Length = AUTO) Then
			Length = Input.Length()
		Endif

		Local VirtualLength:= Length+Offset

		For Local Index:Int = Offset Until VirtualLength
			Output += Input[Index]

			If (Index+1 < VirtualLength) Then
				Output += Comma

				If (AddSpaces) Then
					Output += Space
				Endif
			Endif
		Next

		Return Output + RightBracket
	End

	Function CopyStringToArray:T[](S:String, Input:T[], Offset:Int=0, Length:Int=AUTO)
		If (Length = AUTO) Then
			Length = Min(S.Length(), Input.Length())
		Endif

		For Local I:Int = Offset Until Length
			Input[I] = S[I]
		Next

		' Just for the sake of convenience, return the 'Input' array.
		Return Input
	End

	Function IndexOfList:T(L:List<T>, Index:Int=0)
		' Local variable(s):
		Local Data:list.Node<T> = L.FirstNode()

		For Local I:= 0 Until Index
			Data = Data.NextNode()

			' Not my favorite method, but it works:
			If (Data = Null) Then
				Return NIL
			Endif
		Next

		' Return the value of the assessed node.
		Return Data.Value()
	End

	Function Zero:T[](Input:T[])
		Return Nil(Input)
	End

	Function Nil:T[](Input:T[])
		For Local Index:= 0 Until Input.Length()
			Input[Index] = NIL
		Next

		Return Input
	End

	Function PrintArray:Void(Input:T[])
		Print(AsString(Input))

		Return
	End

	Function CopyArray:T[](Source:T[], Destination:T[])
		Local MinLength:Int = Min(Source.Length(), Destination.Length())

		For Local Index:Int = 0 Until MinLength
			Destination[Index] = Source[Index]
		Next

		Return Destination
	End

	Function CloneArray:T[](Source:T[], Destination:T[]=[])
		If (Destination.Length() = 0) Then
			Destination = New T[Source.Length()]
		Endif

		Return CopyArray(Source, Destination)
	End

	' This command returns a positive number upon an error,
	' otherwise a 'response code' will be given (See the "Contant variable(s)" section for details).
	Function Compare:Int(A1:T[], A2:T[], CheckLength:Bool=True)
		' Local variable(s):
		Local A1_Length:= A1.Length
		Local A2_Length:= A2.Length

		' Check for errors:
		If (CheckLength) Then
			' Make sure they're both the same length.
			If (A1_Length <> A2_Length) Then
				Return COMPARE_RESPONSE_WRONG_LENGTH
			Endif
		Endif

		For Local I:= 0 Until Min(A1_Length, A2_Length)
			If (A1[I] <> A2[I]) Then
				' Not the best of escape methods, but it works.
				Return I
			Endif
		Next

		' Return the default response.
		Return COMPARE_RESPONSE_IDENTICAL
	End

	' This function returns positive if both arrays are identical.
	Function SimpleCompare:Bool(A1:T[], A2:T[], CheckLength:Bool=True)
		Return (Compare(A1, A2, CheckLength) = COMPARE_RESPONSE_IDENTICAL)
	End

	Function Read:T(S:Stream)
		Return Read(S, NIL)
	End

	Function Read:T[](S:Stream, DataArray:T[], Size:Int=AUTO)
		If (Size = AUTO) Then Size = S.ReadInt()

		If (Size > 0) Then
			If (DataArray.Length() = 0) Then
				DataArray = New T[Size]
			Endif
		Endif

		' Local variable(s):
		Local DLength:Int = Min(DataArray.Length, Size)

		For Local Index:= 0 Until DLength
			DataArray[Index] = Read(S, NIL)
		Next

		Return DataArray
	End

	Function Write:Void(S:Stream, DataArray:T[], WriteLength:Bool=True)
		' Local variable(s):
		Local DLength:= DataArray.Length()

		If (WriteLength) Then S.WriteInt(DLength)

		For Local Index:= 0 Until DLength
			Write(S, DataArray[Index])
		Next

		Return
	End

	Function Read:Int(S:Stream, Indentifier:Int, Size:Int=4)
		' Local variable(s):
		Local Data:Int = 0

		If (Size > 1) Then
			If (Size < 4) Then
				Data = S.ReadShort()
			Else
				If (Size > 4) Then
					S.ReadInt()
				Endif

				Data = S.ReadInt()
			Endif
		Else
			Data = S.ReadByte()
		Endif

		Return Data
	End

	Function Write:Void(S:Stream, Data:Int, Size:Int=4)
		Size = IntSize(Size)

		If (Size > 1) Then
			If (Size < 4) Then
				S.WriteShort(Data)
			Else
				If (Size > 4) Then
					S.WriteInt(0)
				Endif

				S.WriteInt(Data)
			Endif
		Else
			S.WriteByte(Data)
		Endif

		Return
	End

	#Rem
	Function Read:Bool(S:Stream, Identifier:Bool)
		Return Read(S, Int(Identifier), 1)
	End

	Function Write:Void(S:Stream, B:Bool)
		Write(S, Int(B), 1)

		Return
	End
	#End

	Function Read:Float(S:Stream, Identifier:Float)
		Return S.ReadFloat()
	End

	Function Write:Void(S:Stream, Data:Float)
		S.WriteFloat(Data)

		Return
	End

	Function Read:String(S:Stream, Data:String, Encoding:String="utf8", Size:Int=AUTO)
		If (Size = AUTO) Then Size = S.ReadInt()

		Data = S.ReadString(Size, Encoding)

		' Return the newly read string.
		Return Data
	End

	Function Write:Void(S:Stream, Data:String, Encoding:String="utf8", WriteSize:Bool=True)
		If (WriteSize) Then S.WriteInt(Data.Length())
		S.WriteString(Data, Encoding)

		Return
	End
End

Strict

Public

' Preprocessor related:
#Rem
#If TARGET = "glfw" Or TARGET = "sexy"
	#GLFW_TARGET = True
#End
#End

#If CONFIG = "debug"
	#DEBUG_PRINT = True
#Else
	#DEBUG_PRINT = False
#End

#DEBUG_PRINT_QUOTES = False

' Imports (Public):
Import byteorder
Import imagedimensions
Import retrostrings
Import stringutil
Import vector

#If CONSOLE_IMPLEMENTED
	Import console
#End

Import sizeof
Import time

#If Not BRL_GAMETARGET_IMPLEMENTED
	Import mojoemulator
#End

Import publicdatastream

' Imports (Private):
Private

' Official:

' BRL:
Import brl.databuffer
Import brl.stream
Import brl.filepath

' Unofficial:

' ImmutableOctet:
Import autostream

Public

' Constant variable(s):

' Used mainly for external code where '-9999...' isn't ever going to be valid.
' You should stick to 'Null' in most cases; try not to use this.
Const NOVAR:Int = -999999

Const ErrorTemplate:String = "[ERROR] {Debug}: " ' + Space
Const LogTemplate:String = "[Info] {Debug}: " ' + Space

' Global variable(s) (Public):
#If CONSOLE_IMPLEMENTED
	Global DebugConsole:Console = Null
#End

' Global variable(s) (Private):
Private

' Nothing so far.

Public

' Functions:
Function FLAG:Int(ID:Int)
	Return Pow(2, ID)
End

' This command is little-endian only:
Function AsByte:Int(I:Int)
	Return ((I Shl 24) Shr 24)
End

' This command treats data as 8-bit (Turns something like $F7, into $FFFFFFF7)
' This is only applied on little-endian systems,
' please use this command for hex values (Monkey converts hex on compile-time).
Function PaddedHex:Int(I:Int)
	If (Not BigEndian()) Then
		Return AsByte(I)
	Endif

	Return I
End

Function Transfer:Bool(InputStream:Stream, OutputStream:Stream, DataSize:Int)
	' Check for errors:
	If (DataSize = 0) Then Return False
	If (InputStream = Null Or InputStream.Eof()) Then Return False
	If (OutputStream = Null) Then Return False

	' Local variable(s):
	#If FLAG_TEMP_BUFFERS
		Local Data:DataBuffer = New DataBuffer(DataSize)
		Local DataLength:= DataSize ' Data.Length()

		' Read from the input-stream, then write to the output-stream:
		InputStream.Read(Data, 0, DataLength)
		OutputStream.Write(Data, 0, DataLength)

		Data.Discard()
	#Else
		'Local Data:Int[DataSize]

		For Local I:Int = 1 To DataSize ' Data.Length()
			OutputStream.WriteByte(InputStream.ReadByte())
		Next
	#End

	' Return the default response.
	Return True
End

Function Sq:Float(Input:Float)
	Return Pow(Input, 2.0) ' Input * Input
End

Function Sq:Int(Input:Int)
	Return Pow(Input, 2)
End

Function WrapAngle:Float(A:Float)
	While (A < 0.0)
		A += 360.0
	Wend

	While (A > 360.0)
		A -= 360.0
	Wend

	Return A
End

Function WrapColor:Float(C:Float)
	#Rem
	While (C < 0.0)
		C += 255.0
	Wend

	While (C > 255.0)
		C -= 255.0
	Wend
	#End

	Return C Mod 256.0
End

#If CONSOLE_IMPLEMENTED
	Function DebugBind:Bool(C:Console)
		If (C = Null) Then Return False

		DebugConsole = C

		' Return the default response.
		Return True
	End
#End

Function DebugError:Void(Msg:String, StopExecution:Bool=True)
	#If DEBUG_PRINT
		' Local variable(s):
		Local TempMsg:String

		If (StopExecution) Then
			TempMsg = ErrorTemplate
		Else
			TempMsg = LogTemplate
		Endif

		#If DEBUG_PRINT_QUOTES
			Msg = Tempmsg + Quote + Msg + Quote
		#Else
			Msg = TempMsg + Msg
		#End

		#If CONSOLE_IMPLEMENTED
			If (DebugConsole <> Null) Then
				DebugConsole.WriteLine(Msg, True)
			Else
		#End

		Print(Msg)

		#If CONSOLE_IMPLEMENTED
			Endif
		#End
	#Else
		' This may change later:
		If (StopExecution) Then
			Msg = ErrorTemplate + Msg
		Endif
	#End

	If (StopExecution) Then
		#If CONFIG = "debug"
			#If DEBUG_PRINT
				DebugStop()
			#Else
				Error(Msg)
			#End
		#Else
			#If DEBUG_PRINT
				Local FinalStr:String = (ErrorTemplate + Quote + "Attempt to stop execution failed. (Reason: Release-mode)" + Quote)

				#If CONSOLE_IMPLEMENTED
					If (DebugConsole <> Null) Then
						DebugConsole.WriteLine(FinalStr, True)
					Else
				#End

				Print(FinalStr)

				#If CONSOLE_IMPLEMENTED
					Endif
				#End
			#Else
				Error(Msg)
			#End
		#End
	Endif

	#If CONFIG = "debug"
		' Nothing so far.
	#Else
		' Nothing so far.
	#End

	Return
End

Function DebugPrint:Void(Str:String, StopExecution:Bool=False)
	DebugError(Str, StopExecution)

	Return
End

' Classes:
Class GenericUtilities<T>
	' Global variable(s):
	Global NIL:T

	' Functions:
	Function IndexOfList:T(L:List<T>, Index:Int=0)
		' Local variable(s):
		Local Response:T
		Local Data:list.Node<T> = L.FirstNode()

		For Local I:= 0 Until Index
			Data = Data.NextNode()

			' Not my favorite method, but it works:
			If (Data = Null) Then
				Return Response
			Endif
		Next

		Response = Data.Value()

		' Return the value of the assessed node.
		Return Response
	End

	Function CopyArray:T[](Source:T[], Destination:T[]=[], SourceOffset:Int=0, DestinationOffset:Int=0)
		If (Destination.Length() = 0) Then
			Destination = New T[Source.Length()]
		Endif

		Local MinLength:Int = Min(Source.Length(), Destination.Length())

		For Local Index:Int = 0 Until MinLength
			' Local variable(s):
			Local DestinationIndex:= Index+DestinationOffset
			Local SourceIndex:= Index+SourceOffset

			' Check if we can perform the current operation:
			If (DestinationIndex >= Destination.Length() Or SourceIndex >= Source.Length()) Then
				Exit
			Endif

			Destination[DestinationIndex] = Source[SourceIndex]
		Next

		Return Destination
	End

	Function CloneArray:T[](Source:T[], Destination:T[]=[])
		Return CopyArray(Source, Destination)
	End

	Function Read:T(S:Stream)
		Return Read(S, NIL)
	End

	Function Read:T[](S:Stream, DataArray:T[], Size:Int=-1)
		If (Size = -1) Then Size = S.ReadInt()

		If (Size > 0) Then
			If (DataArray.Length() = 0) Then
				DataArray = New T[Size]
			Endif
		Endif

		' Local variable(s):
		Local DLength:Int = Min(DataArray.Length, Size)

		For Local Index:= 0 Until DLength
			DataArray[Index] = Read(S, NIL)
		Next

		Return DataArray
	End

	Function Write:Void(S:Stream, DataArray:T[], WriteLength:Bool=True)
		' Local variable(s):
		Local DLength:= DataArray.Length()

		If (WriteLength) Then S.WriteInt(DLength)

		For Local Index:= 0 Until DLength
			Write(S, DataArray[Index])
		Next

		Return
	End

	Function Read:Int(S:Stream, Indentifier:Int, Size:Int=4)
		' Local variable(s):
		Local Data:Int = 0

		If (Size > 1) Then
			If (Size < 4) Then
				Data = S.ReadShort()
			Else
				If (Size > 4) Then
					S.ReadInt()
				Endif

				Data = S.ReadInt()
			Endif
		Else
			Data = S.ReadByte()
		Endif

		Return Data
	End

	Function Write:Void(S:Stream, Data:Int, Size:Int=4)
		Size = IntSize(Size)

		If (Size > 1) Then
			If (Size < 4) Then
				S.WriteShort(Data)
			Else
				If (Size > 4) Then
					S.WriteInt(0)
				Endif

				S.WriteInt(Data)
			Endif
		Else
			S.WriteByte(Data)
		Endif

		Return
	End

	#Rem
	Function Read:Bool(S:Stream, Identifier:Bool)
		Return Read(S, Int(Identifier), 1)
	End

	Function Write:Void(S:Stream, B:Bool)
		Write(S, Int(B), 1)

		Return
	End
	#End

	Function Read:Float(S:Stream, Identifier:Float)
		Return S.ReadFloat()
	End

	Function Write:Void(S:Stream, Data:Float)
		S.WriteFloat(Data)

		Return
	End

	Function Read:String(S:Stream, Data:String, Encoding:String="utf8", Size:Int=-1)
		If (Size = -1) Then Size = S.ReadInt()

		Data = S.ReadString(Size, Encoding)

		' Return the newly read string.
		Return Data
	End

	Function Write:Void(S:Stream, Data:String, Encoding:String="utf8", WriteSize:Bool=True)
		If (WriteSize) Then S.WriteInt(Data.Length())
		S.WriteString(Data, Encoding)

		Return
	End
End


Whoops... that'll teach me to commit without compiling.

--HG--
branch : development

Strict

Private
Import mojo
Import functions
Import collections
Import xml

Public
Import exception

Const TRANSFORM_POSITION:Int = 0
Const TRANSFORM_ALPHA:Int = 1
Const TRANSFORM_SCALE:Int = 2
Const TRANSFORM_SCALE_VECTOR:Int = 3
Const TRANSFORM_ROTATION:Int = 4
Const TRANSFORM_COLOR:Int = 5
Const TRANSFORM_HANDLE:Int = 6
Const TRANSFORM_SHAKE:Int = 7
Const TRANSFORM_COUNT:Int = 8

Const EASE_NONE:Int = 0
Const EASE_IN_DOUBLE:Int = 1
Const EASE_IN:Int = 2
Const EASE_IN_HALF:Int = 3
Const EASE_OUT:Int = 4
Const EASE_OUT_HALF:Int = 5
Const EASE_OUT_DOUBLE:Int = 6
Const EASE_IN_OUT:Int = 7

Class Storyboard
Private
	Field sprites:ArrayList<StoryboardSprite> = New ArrayList<StoryboardSprite>
	Field sounds:ArrayList<StoryboardSound> = New ArrayList<StoryboardSound>

Public
	Function LoadXML:Storyboard(filename:String)
		Local parser:XMLParser = New XMLParser
		Local doc:XMLDocument = parser.ParseFile("storyboard.xml")
		Local root:XMLElement = doc.Root
		Local sb:Storyboard = New Storyboard
		For Local node:XMLElement = EachIn root.Children
			If node.Name = "layer" Then
				Local index:Int = Int(node.GetAttribute("index","0"))
				For Local spriteNode:XMLElement = EachIn node.Children
					If spriteNode.Name = "sprite" Then
						sb.sprites.Add(StoryboardSprite.CreateFromXML(spriteNode, index))
					End
				Next
			End
		Next
		sb.sprites.Sort()
		Return sb
	End

	#Rem
	Function LoadOSB:Storyboard(filename:String)
		Local osb:String = LoadString(filename)
		Local lines:List<String> = New List<String>
		Local s:Int = 0, e:Int = 0
		Local prev:Int = 0
		For Local e:Int = 0 Until osb.Length
			Local newline:Bool = False
			If osb[e] = 13 Then
				newline = True
			ElseIf osb[e] = 10 Then
				If prev <> 13 Then
					newline = True
				Else
					s += 1
				End
			End
			If newline Then
				lines.AddLast(osb[s..e])
				s = e+1
			End
			prev = osb[e]
		Next
		If s < e Then lines.AddLast(osb[s..e])
		For Local i:Int = 0 Until lines.Count
			Local str:String = lines.Get(i)
			If str.StartsWith("Sprite") Then
				Local tokens:String[] = str.Split(",")
				Local layerStr:String = tokens[1]
				Local imageName:String = tokens[3]
		Next
	End
	#End

	Method Update:Void(currentTime:Int)
		For Local i:Int = 0 Until sprites.Size
			Local sprite:StoryboardSprite = sprites.Get(i)
			sprite.Update(currentTime)
		Next
		'For Local i:Int = 0 Until sounds.Size
		'	Local sound:StoryboardSound = sounds.Get(i)
		'	sound.Update(currentTime)
		'Next
	End

	Method Render:Void()
		For Local i:Int = 0 Until sprites.Size
			Local sprite:StoryboardSprite = sprites.Get(i)
			sprite.Render()
		Next
	End
End

Class StoryboardElement Implements IComparable Abstract
Private
	Global nextId:Int = 0
	Field id:Int

Public
	Method New()
		id = nextId
		nextId += 1
	End

	Method Compare:Int(other:Object)
		Local o:StoryboardElement = StoryboardElement(other)
		If Not o Then Return -1
		If o = Self Then Return 0
		If id < o.id Then Return -1
		If id > o.id Then Return 1
		Return 0
	End

	Method Equals:Bool(other:Object)
		Return Compare(other)=0
	End
End

Class StoryboardSound Extends StoryboardElement
Private
	Field soundName:String
	Field sound:GameSound

Public
	Function CreateFromXML:StoryboardSound(node:XMLElement)
		Return Null
	End

	Method New(soundName:String)
		Super.New()
		Self.soundName = soundName
	End
End

Class StoryboardSprite Extends StoryboardElement
Private
	Field layer:Int
	Field imageName:String
	Field image:GameImage

	Field firstX:Float=0, firstY:Float=0
	field firstScaleX:Float=1, firstScaleY:Float=1
	Field firstScale:Float=1, firstRotation:Float=0
	Field firstRed:Float=255, firstGreen:Float=255, firstBlue:Float=255, firstAlpha:Float=1

	Field x:Float=0, y:Float=0
	field scaleX:Float=1, scaleY:Float=1
	Field scale:Float=1, rotation:Float=0
	Field red:Float=255, green:Float=255, blue:Float=255, alpha:Float=1

	Field transforms:ArrayList<StoryboardSpriteTransform> = New ArrayList<StoryboardSpriteTransform>
	Field currentTransforms:StoryboardSpriteTransform[] = New StoryboardSpriteTransform[TRANSFORM_COUNT]
	Field hasTransforms:Bool = False

Public
	Function CreateFromXML:StoryboardSprite(node:XMLElement, layer:Int)
		Local imageName:String = node.GetAttribute("image","")
		Local sprite:StoryboardSprite = New StoryboardSprite(imageName, layer)
		sprite.firstX = Float(node.GetAttribute("x","0"))
		sprite.firstY = Float(node.GetAttribute("y","0"))
		sprite.firstScaleX = Float(node.GetAttribute("scaleX","1"))
		sprite.firstScaleY = Float(node.GetAttribute("scaleY","1"))
		sprite.firstScale = Float(node.GetAttribute("scale","1"))
		sprite.firstRotation = Float(node.GetAttribute("rotation","0"))
		sprite.firstRed = Float(node.GetAttribute("red","255"))
		sprite.firstGreen = Float(node.GetAttribute("green","255"))
		sprite.firstBlue = Float(node.GetAttribute("blue","255"))
		sprite.firstAlpha = Float(node.GetAttribute("alpha","1"))
		CreateTransformsFromXML(sprite, node)
		Return sprite
	End

	Function CreateTransformsFromXML:Void(sprite:StoryboardSprite, node:XMLElement, timeOffset:Int=0)
		For Local childNode:XMLElement = EachIn node.Children
			Local name:String = childNode.Name
			If name = "group" Then
				Local loopCount:Int = Int(childNode.GetAttribute("loopCount","1"))
				Local startTime:Int = Int(childNode.GetAttribute("startTime","0"))
				Local endTime:Int = Int(childNode.GetAttribute("endTime","0"))
				Local myOffset:Int = timeOffset + startTime
				For Local i:Int = 0 Until loopCount
					CreateTransformsFromXML(sprite, childNode, myOffset)
					myOffset += endTime - startTime
				Next
			Else
				sprite.transforms.Add(StoryboardSpriteTransform.CreateFromXML(childNode, timeOffset))
			End
		Next
	End

	Method New(imageName:String, layer:Int)
		Super.New()
		Self.imageName = imageName
		Self.layer = layer
	End

	Method Compare:Int(other:Object)
		Local o:StoryboardSprite = StoryboardSprite(other)
		If Not o Then Return -1
		If o = Self Then Return 0
		If layer < o.layer Then Return -1
		If layer > o.layer Then Return 1
		Return Super.Compare(other)
	End

	Method Update:Void(currentTime:Int)
		x = firstX; y = firstY
		scaleX = firstScaleX; scaleY = firstScaleY
		scale = firstScale; rotation = firstRotation
		red = firstRed; green = firstGreen; blue = firstBlue; alpha = firstAlpha

		For Local i:Int = 0 Until transforms.Size
			transforms.Get(i).Update(currentTime)
		Next
		hasTransforms = False
		For Local i:Int = 0 Until currentTransforms.Length
			currentTransforms[i] = GetActiveTransform(i, currentTime)
			If currentTransforms[i] Then
				currentTransforms[i].Apply(Self)
				hasTransforms = True
			End
		Next
	End

	Method Render:Void()
		If Not image Then image = game.images.Find(imageName)
		If Not image Then
			Print "Couldn't load "+imageName+" for sprite."
			Return
		End
		If Not hasTransforms Or alpha = 0 Then Return

		' translation, scale, rotation, handle, other effects
		PushMatrix
		Translate x, y
		Scale scaleX, scaleY
		Scale scale, scale
		Rotate rotation
		SetColor red, green, blue
		SetAlpha alpha

		image.Draw(0, 0)
		PopMatrix
	End

	Method GetActiveTransform:StoryboardSpriteTransform(findType:Int, atTime:Int)
		' loop on all children
		For Local i:Int = 0 Until transforms.Size
			Local tr:StoryboardSpriteTransform = transforms.Get(i)
			If tr.transformType = findType Then
				' if we're past the start time for this transform
				If atTime >= tr.startTime And atTime <= tr.endTime Then
					Return tr
				End
			End
		Next
		Return Null
	End
End

Class StoryboardSpriteTransform Implements IComparable
Private
	Global hslArray:Float[] = New Float[3]
	Global rgbArray:Int[] = New Int[3]

	Field transformType:Int = 0

	Field currentTime:Int
	Field startTime:Int
	Field endTime:Int
	Field easeType:Int

	Field startValues:Float[]
	Field endValues:Float[]
	Field currentValues:Float[]

Public
	Function CreateFromXML:StoryboardSpriteTransform(node:XMLElement, timeOffset:Int=0)
		Local name:String = node.Name
		Local startTime:Int = Int(node.GetAttribute("startTime","0"))+timeOffset
		Local endTime:Int = Int(node.GetAttribute("endTime","0"))+timeOffset
		Local easeType:Int = Int(node.GetAttribute("easeType","0"))
		If name = "scale" Then
			Local startScale:Float = Float(node.GetAttribute("startScale","1"))
			Local endScale:Float = Float(node.GetAttribute("endScale","1"))
			Return CreateScale(startTime, endTime, easeType, startScale, endScale)
		ElseIf name = "scaleVector" Then
			Local startScaleX:Float = Float(node.GetAttribute("startScaleX","1"))
			Local endScaleX:Float = Float(node.GetAttribute("endScaleX","1"))
			Local startScaleY:Float = Float(node.GetAttribute("startScaleY","1"))
			Local endScaleY:Float = Float(node.GetAttribute("endScaleY","1"))
			Return CreateScaleVector(startTime, endTime, easeType, startScaleX, startScaleY, endScaleX, endScaleY)
		ElseIf name = "alpha" Then
			Local startAlpha:Float = Float(node.GetAttribute("startAlpha","1"))
			Local endAlpha:Float = Float(node.GetAttribute("endAlpha","1"))
			Return CreateAlpha(startTime, endTime, easeType, startAlpha, endAlpha)
		ElseIf name = "rotation" Then
			Local startRotation:Float = Float(node.GetAttribute("startRotation","0"))
			Local endRotation:Float = Float(node.GetAttribute("endRotation","0"))
			Return CreateRotation(startTime, endTime, easeType, startRotation, endRotation)
		ElseIf name = "position" Then
			Local startX:Float = Float(node.GetAttribute("startX","0"))
			Local endX:Float = Float(node.GetAttribute("endX","0"))
			Local startY:Float = Float(node.GetAttribute("startY","0"))
			Local endY:Float = Float(node.GetAttribute("endY","0"))
			Return CreatePosition(startTime, endTime, easeType, startX, startY, endX, endY)
		ElseIf name = "handle" Then
			Local startX:Float = Float(node.GetAttribute("startX","0"))
			Local endX:Float = Float(node.GetAttribute("endX","0"))
			Local startY:Float = Float(node.GetAttribute("startY","0"))
			Local endY:Float = Float(node.GetAttribute("endY","0"))
			Return CreateHandle(startTime, endTime, easeType, startX, startY, endX, endY)
		ElseIf name = "color" Then
			If node.HasAttribute("startRed") Or node.HasAttribute("startGreen") Or node.HasAttribute("startBlue") Then
				Local startRed:Float = Float(node.GetAttribute("startRed","255"))
				Local endRed:Float = Float(node.GetAttribute("endRed","255"))
				Local startGreen:Float = Float(node.GetAttribute("startGreen","255"))
				Local endGreen:Float = Float(node.GetAttribute("endGreen","255"))
				Local startBlue:Float = Float(node.GetAttribute("startBlue","255"))
				Local endBlue:Float = Float(node.GetAttribute("endBlue","255"))
				Return CreateColorRGB(startTime, endTime, easeType, startRed, startGreen, startBlue, endRed, endGreen, endBlue)
			Else
				Local startHue:Float = Float(node.GetAttribute("startHue","0"))
				Local endHue:Float = Float(node.GetAttribute("endHue","0"))
				Local startSaturation:Float = Float(node.GetAttribute("startSaturation","1"))
				Local endSaturation:Float = Float(node.GetAttribute("endSaturation","1"))
				Local startLuminance:Float = Float(node.GetAttribute("startLuminance","1"))
				Local endLuminance:Float = Float(node.GetAttribute("endLuminance","1"))
				Return CreateColorHSL(startTime, endTime, easeType, startHue, startSaturation, startLuminance, endHue, endSaturation, endLuminance)
			End
		End
		Return Null
	End

	Function CreateScale:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startScale#, endScale#)
		Return New StoryboardSpriteTransform(TRANSFORM_SCALE, startTime, endTime, easeType, startScale, endScale)
	End

	Function CreateScaleVector:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startScaleX#, startScaleY#, endScaleX#, endScaleY#)
		Return New StoryboardSpriteTransform(TRANSFORM_SCALE_VECTOR, startTime, endTime, easeType, startScaleX, endScaleX, startScaleY, endScaleY)
	End

	Function CreateAlpha:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startAlpha#, endAlpha#)
		startAlpha = Max(0.0,Min(1.0,startAlpha))
		endAlpha = Max(0.0,Min(1.0,endAlpha))
		Return New StoryboardSpriteTransform(TRANSFORM_ALPHA, startTime, endTime, easeType, startAlpha, endAlpha)
	End

	Function CreateRotation:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startRotation#, endRotation#)
		Return New StoryboardSpriteTransform(TRANSFORM_ROTATION, startTime, endTime, easeType, startRotation, endRotation)
	End

	Function CreatePosition:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startX#, startY#, endX#, endY#)
		Return New StoryboardSpriteTransform(TRANSFORM_POSITION, startTime, endTime, easeType, startX, endX, startY, endY)
	End

	Function CreateHandle:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startX#, startY#, endX#, endY#)
		Return New StoryboardSpriteTransform(TRANSFORM_HANDLE, startTime, endTime, easeType, startX, endX, startY, endY)
	End

	Function CreateColorRGB:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startRed#, startGreen#, startBlue#, endRed#, endGreen#, endBlue#)
		startRed = Max(0.0,Min(255.0,startRed))
		startGreen = Max(0.0,Min(255.0,startGreen))
		startBlue = Max(0.0,Min(255.0,startBlue))
		endRed = Max(0.0,Min(255.0,endRed))
		endGreen = Max(0.0,Min(255.0,endGreen))
		endBlue = Max(0.0,Min(255.0,endBlue))
		RGBtoHSL(startRed, startGreen, startBlue, hslArray)
		Local startHue:Float = hslArray[0], startSaturation:Float = hslArray[1], startLuminance:Float = hslArray[2]
		RGBtoHSL(endRed, endGreen, endBlue, hslArray)
		Local endHue:Float = hslArray[0], endSaturation:Float = hslArray[1], endLuminance:Float = hslArray[2]
		Return CreateColorHSL(startTime, endTime, easeType, startHue, startSaturation, startLuminance, endHue, endSaturation, endLuminance)
	End

	Function CreateColorHSL:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startHue#, startSaturation#, startLuminance#, endHue#, endSaturation#, endLuminance#)
		startHue = Max(0.0, Min(1.0, startHue))
		startSaturation = Max(0.0, Min(1.0, startSaturation))
		startLuminance = Max(0.0, Min(1.0, startLuminance))
		endHue = Max(0.0, Min(1.0, endHue))
		endSaturation = Max(0.0, Min(1.0, endSaturation))
		endLuminance = Max(0.0, Min(1.0, endLuminance))
		Return New StoryboardSpriteTransform(TRANSFORM_COLOR, startTime, endTime, easeType, startHue, endHue, startSaturation, endSaturation, startLuminance, endLuminance)
	End

	Method New(transformType:Int, startTime:Int, endTime:Int, easeType:Int,
							startValue1:Float=0, endValue1:Float=0,
							startValue2:Float=0, endValue2:Float=0,
							startValue3:Float=0, endValue3:Float=0,
							startValue4:Float=0, endValue4:Float=0,
							startValue5:Float=0, endValue5:Float=0)
		Self.transformType = transformType
		Self.startTime = startTime
		Self.endTime = endTime
		Self.easeType = easeType
		Select transformType
			Case TRANSFORM_ALPHA, TRANSFORM_SCALE, TRANSFORM_ROTATION
				Init(1, startValue1, endValue1)
			Case TRANSFORM_POSITION, TRANSFORM_SCALE_VECTOR, TRANSFORM_HANDLE
				Init(2, startValue1, endValue1, startValue2, endValue2)
			Case TRANSFORM_COLOR
				Init(3, startValue1, endValue1, startValue2, endValue2, startValue3, endValue3)
		End
	End

	Method Update:Void(currentTime:Int)
		Self.currentTime = currentTime
		For Local i:Int = 0 Until startValues.Length
			currentValues[i] = Calculate(startValues[i], endValues[i])
		Next
	End

	Method Apply:Void(sprite:StoryboardSprite)
		Select transformType
			Case TRANSFORM_ALPHA
				sprite.alpha = currentValues[0]
			Case TRANSFORM_SCALE
				sprite.scale = currentValues[0]
			Case TRANSFORM_ROTATION
				sprite.rotation = currentValues[0]
			Case TRANSFORM_POSITION
				sprite.x = currentValues[0]
				sprite.y = currentValues[1]
			Case TRANSFORM_SCALE_VECTOR
				sprite.scaleX = currentValues[0]
				sprite.scaleY = currentValues[1]
			Case TRANSFORM_COLOR
				HSLtoRGB(currentValues[0], currentValues[1], currentValues[2], rgbArray)
				sprite.red = rgbArray[0]
				sprite.green = rgbArray[1]
				sprite.blue = rgbArray[2]
			Case TRANSFORM_HANDLE
				If sprite And sprite.image And sprite.image.image Then
					sprite.image.image.SetHandle(currentValues[0], currentValues[1])
				End
		End
	End

	Method Compare:Int(other:Object)
		Local o:StoryboardSpriteTransform = StoryboardSpriteTransform(other)
		If Not o Then Return -1
		If o = Self Then Return 0
		If startTime > o.startTime Then Return 1
		If startTime < o.startTime Then Return -1
		If endTime > o.endTime Then Return 1
		If endTime < o.endTime Then Return -1
		If transformType > o.transformType Then Return 1
		If transformType < o.transformType Then Return -1
		Return 0
	End

	Method Equals:Bool(other:Object)
		Return Compare(other)=0
	End

Private
	Method Init:Void(elementCount:Int,
							startValue1:Float=0, endValue1:Float=0,
							startValue2:Float=0, endValue2:Float=0,
							startValue3:Float=0, endValue3:Float=0,
							startValue4:Float=0, endValue4:Float=0,
							startValue5:Float=0, endValue5:Float=0)
		startValues = New Float[elementCount]
		endValues = New Float[elementCount]
		currentValues = New Float[elementCount]
		If elementCount >= 1 Then
			startValues[0] = startValue1
			endValues[0] = endValue1
		End
		If elementCount >= 2 Then
			startValues[1] = startValue2
			endValues[1] = endValue2
		End
		If elementCount >= 3 Then
			startValues[2] = startValue3
			endValues[2] = endValue3
		End
		If elementCount >= 4 Then
			startValues[3] = startValue4
			endValues[3] = endValue4
		End
		If elementCount >= 5 Then
			startValues[4] = startValue5
			endValues[4] = endValue5
		End
	End

	Method Calculate:Float(startValue:Float, endValue:Float)
		' shortcut for start/end (also accounts for the case when startTime==endTime)
		' we check endTime first because if startTime==endTime==currentTime, we want to use endValue
		If currentTime >= endTime Then Return endValue
		If currentTime <= startTime Then Return startValue

		' how far through are we?
		Local progress:Float = Float(currentTime - startTime) / Float(endTime - startTime)

		Select easeType
			Case EASE_IN_DOUBLE
				Return Lerp(endValue, startValue, (1-progress)*(1-progress)*(1-progress)*(1-progress))
			Case EASE_IN
				Return Lerp(endValue, startValue, (1-progress)*(1-progress))
			Case EASE_IN_HALF
				Return Lerp(endValue, startValue, Pow(1-progress, 1.5))
			Case EASE_OUT
				Return Lerp(startValue, endValue, progress * progress)
			Case EASE_OUT_HALF
				Return Lerp(startValue, endValue, Pow(progress, 1.5))
			Case EASE_OUT_DOUBLE
				Return Lerp(startValue, endValue, progress*progress*progress*progress)
			Case EASE_IN_OUT
				Return startValue + (-2*(progress*progress*progress) + 3*(progress*progress)) * (endValue - startValue)
			Default
				Return Lerp(startValue, endValue, progress);
		End
	End

	Method Lerp:Float(startValue:Float, endValue:Float, progress:Float)
		Return startValue + (endValue-startValue) * progress
	End
End

Strict

Private
Import mojo
Import functions
Import collections
Import xml

Public
Import exception

Const TRANSFORM_POSITION:Int = 0
Const TRANSFORM_ALPHA:Int = 1
Const TRANSFORM_SCALE:Int = 2
Const TRANSFORM_SCALE_VECTOR:Int = 3
Const TRANSFORM_ROTATION:Int = 4
Const TRANSFORM_COLOR:Int = 5
Const TRANSFORM_HANDLE:Int = 6
Const TRANSFORM_SHAKE:Int = 7
Const TRANSFORM_COUNT:Int = 8

Const EASE_NONE:Int = 0
Const EASE_IN_DOUBLE:Int = 1
Const EASE_IN:Int = 2
Const EASE_IN_HALF:Int = 3
Const EASE_OUT:Int = 4
Const EASE_OUT_HALF:Int = 5
Const EASE_OUT_DOUBLE:Int = 6
Const EASE_IN_OUT:Int = 7

Class Storyboard
Private
	Field sprites:ArrayList<StoryboardSprite> = New ArrayList<StoryboardSprite>
	Field sounds:ArrayList<StoryboardSound> = New ArrayList<StoryboardSound>

Public
	Function LoadXML:Storyboard(filename:String)
		Local parser:XMLParser = New XMLParser
		Local doc:XMLDocument = parser.ParseFile("storyboard.xml")
		Local root:XMLElement = doc.Root
		Local sb:Storyboard = New Storyboard
		For Local node:XMLElement = EachIn root.Children
			If node.Name = "layer" Then
				Local index:Int = Int(node.GetAttribute("index","0"))
				For Local spriteNode:XMLElement = EachIn node.Children
					If spriteNode.Name = "sprite" Then
						sb.sprites.Add(StoryboardSprite.CreateFromXML(spriteNode, index))
					End
				Next
			End
		Next
		sb.sprites.Sort()
		Return sb
	End

	#Rem
	Function LoadOSB:Storyboard(filename:String)
		Local osb:String = LoadString(filename)
		Local lines:List<String> = New List<String>
		Local s:Int = 0, e:Int = 0
		Local prev:Int = 0
		For Local e:Int = 0 Until osb.Length
			Local newline:Bool = False
			If osb[e] = 13 Then
				newline = True
			ElseIf osb[e] = 10 Then
				If prev <> 13 Then
					newline = True
				Else
					s += 1
				End
			End
			If newline Then
				lines.AddLast(osb[s..e])
				s = e+1
			End
			prev = osb[e]
		Next
		If s < e Then lines.AddLast(osb[s..e])
		For Local i:Int = 0 Until lines.Count
			Local str:String = lines.Get(i)
			If str.StartsWith("Sprite") Then
				Local tokens:String[] = str.Split(",")
				Local layerStr:String = tokens[1]
				Local imageName:String = tokens[3]
		Next
	End
	#End

	Method Update:Void(currentTime:Int)
		For Local i:Int = 0 Until sprites.Size
			Local sprite:StoryboardSprite = sprites.Get(i)
			sprite.Update(currentTime)
		Next
		'For Local i:Int = 0 Until sounds.Size
		'	Local sound:StoryboardSound = sounds.Get(i)
		'	sound.Update(currentTime)
		'Next
	End

	Method Render:Void()
		For Local i:Int = 0 Until sprites.Size
			Local sprite:StoryboardSprite = sprites.Get(i)
			sprite.Render()
		Next
	End
End

Class StoryboardElement Implements IComparable Abstract
Private
	Global nextId:Int = 0
	Field id:Int

Public
	Method New()
		id = nextId
		nextId += 1
	End

	Method Compare:Int(other:Object)
		Local o:StoryboardElement = StoryboardElement(other)
		If Not o Then Return -1
		If o = Self Then Return 0
		If id < o.id Then Return -1
		If id > o.id Then Return 1
		Return 0
	End

	Method Equals:Bool(other:Object)
		Return Compare(other)=0
	End
End

Class StoryboardSound Extends StoryboardElement
Private
	Field soundName:String
	Field sound:GameSound

Public
	Function CreateFromXML:StoryboardSound(node:XMLElement)
		Return Null
	End

	Method New(soundName:String)
		Super.New()
		Self.soundName = soundName
	End
End

Class StoryboardSprite Extends StoryboardElement
Private
	Field layer:Int
	Field imageName:String
	Field image:GameImage

	Field firstX=0:Float, firstY:Float=0
	field firstScaleX:Float=1, firstScaleY:Float=1
	Field firstScale:Float=1, firstRotation:Float=0
	Field firstRed:Float=255, firstGreen:Float=255, firstBlue:Float=255, firstAlpha:Float=1

	Field x:Float=0, y:Float=0
	field scaleX:Float=1, scaleY:Float=1
	Field scale:Float=1, rotation:Float=0
	Field red:Float=255, green:Float=255, blue:Float=255, alpha:Float=1

	Field transforms:ArrayList<StoryboardSpriteTransform> = New ArrayList<StoryboardSpriteTransform>
	Field currentTransforms:StoryboardSpriteTransform[] = New StoryboardSpriteTransform[TRANSFORM_COUNT]
	Field hasTransforms:Bool = False

Public
	Function CreateFromXML:StoryboardSprite(node:XMLElement, layer:Int)
		Local imageName:String = node.GetAttribute("image","")
		Local sprite:StoryboardSprite = New StoryboardSprite(imageName, layer)
		sprite.firstX = Float(node.GetAttribute("x","0"))
		sprite.firstY = Float(node.GetAttribute("y","0"))
		sprite.firstScaleX = Float(node.GetAttribute("scaleX","1"))
		sprite.firstScaleY = Float(node.GetAttribute("scaleY","1"))
		sprite.firstScale = Float(node.GetAttribute("scale","1"))
		sprite.firstRotation = Float(node.GetAttribute("rotation","0"))
		sprite.firstRed = Float(node.GetAttribute("red","255"))
		sprite.firstGreen = Float(node.GetAttribute("green","255"))
		sprite.firstBlue = Float(node.GetAttribute("blue","255"))
		sprite.firstAlpha = Float(node.GetAttribute("alpha","1"))
		CreateTransformsFromXML(sprite, node)
		Return sprite
	End

	Function CreateTransformsFromXML:Void(sprite:StoryboardSprite, node:XMLElement, timeOffset:Int=0)
		For Local childNode:XMLElement = EachIn node.Children
			Local name:String = childNode.Name
			If name = "group" Then
				Local loopCount:Int = Int(childNode.GetAttribute("loopCount","1"))
				Local startTime:Int = Int(childNode.GetAttribute("startTime","0"))
				Local endTime:Int = Int(childNode.GetAttribute("endTime","0"))
				Local myOffset:Int = timeOffset + startTime
				For Local i:Int = 0 Until loopCount
					CreateTransformsFromXML(sprite, childNode, myOffset)
					myOffset += endTime - startTime
				Next
			Else
				sprite.transforms.Add(StoryboardSpriteTransform.CreateFromXML(childNode, timeOffset))
			End
		Next
	End

	Method New(imageName:String, layer:Int)
		Super.New()
		Self.imageName = imageName
		Self.layer = layer
	End

	Method Compare:Int(other:Object)
		Local o:StoryboardSprite = StoryboardSprite(other)
		If Not o Then Return -1
		If o = Self Then Return 0
		If layer < o.layer Then Return -1
		If layer > o.layer Then Return 1
		Return Super.Compare(other)
	End

	Method Update:Void(currentTime:Int)
		x = firstX; y = firstY
		scaleX = firstScaleX; scaleY = firstScaleY
		scale = firstScale; rotation = firstRotation
		red = firstRed; green = firstGreen; blue = firstBlue; alpha = firstAlpha

		For Local i:Int = 0 Until transforms.Size
			transforms.Get(i).Update(currentTime)
		Next
		hasTransforms = False
		For Local i:Int = 0 Until currentTransforms.Length
			currentTransforms[i] = GetActiveTransform(i, currentTime)
			If currentTransforms[i] Then
				currentTransforms[i].Apply(Self)
				hasTransforms = True
			End
		Next
	End

	Method Render:Void()
		If Not image Then image = game.images.Find(imageName)
		If Not image Then
			Print "Couldn't load "+imageName+" for sprite."
			Return
		End
		If Not hasTransforms Or alpha = 0 Then Return

		' translation, scale, rotation, handle, other effects
		PushMatrix
		Translate x, y
		Scale scaleX, scaleY
		Scale scale, scale
		Rotate rotation
		SetColor red, green, blue
		SetAlpha alpha

		image.Draw(0, 0)
		PopMatrix
	End

	Method GetActiveTransform:StoryboardSpriteTransform(findType:Int, atTime:Int)
		' loop on all children
		For Local i:Int = 0 Until transforms.Size
			Local tr:StoryboardSpriteTransform = transforms.Get(i)
			If tr.transformType = findType Then
				' if we're past the start time for this transform
				If atTime >= tr.startTime And atTime <= tr.endTime Then
					Return tr
				End
			End
		Next
		Return Null
	End
End

Class StoryboardSpriteTransform Implements IComparable
Private
	Global hslArray:Float[] = New Float[3]
	Global rgbArray:Int[] = New Int[3]

	Field transformType:Int = 0

	Field currentTime:Int
	Field startTime:Int
	Field endTime:Int
	Field easeType:Int

	Field startValues:Float[]
	Field endValues:Float[]
	Field currentValues:Float[]

Public
	Function CreateFromXML:StoryboardSpriteTransform(node:XMLElement, timeOffset:Int=0)
		Local name:String = node.Name
		Local startTime:Int = Int(node.GetAttribute("startTime","0"))+timeOffset
		Local endTime:Int = Int(node.GetAttribute("endTime","0"))+timeOffset
		Local easeType:Int = Int(node.GetAttribute("easeType","0"))
		If name = "scale" Then
			Local startScale:Float = Float(node.GetAttribute("startScale","1"))
			Local endScale:Float = Float(node.GetAttribute("endScale","1"))
			Return CreateScale(startTime, endTime, easeType, startScale, endScale)
		ElseIf name = "scaleVector" Then
			Local startScaleX:Float = Float(node.GetAttribute("startScaleX","1"))
			Local endScaleX:Float = Float(node.GetAttribute("endScaleX","1"))
			Local startScaleY:Float = Float(node.GetAttribute("startScaleY","1"))
			Local endScaleY:Float = Float(node.GetAttribute("endScaleY","1"))
			Return CreateScaleVector(startTime, endTime, easeType, startScaleX, startScaleY, endScaleX, endScaleY)
		ElseIf name = "alpha" Then
			Local startAlpha:Float = Float(node.GetAttribute("startAlpha","1"))
			Local endAlpha:Float = Float(node.GetAttribute("endAlpha","1"))
			Return CreateAlpha(startTime, endTime, easeType, startAlpha, endAlpha)
		ElseIf name = "rotation" Then
			Local startRotation:Float = Float(node.GetAttribute("startRotation","0"))
			Local endRotation:Float = Float(node.GetAttribute("endRotation","0"))
			Return CreateRotation(startTime, endTime, easeType, startRotation, endRotation)
		ElseIf name = "position" Then
			Local startX:Float = Float(node.GetAttribute("startX","0"))
			Local endX:Float = Float(node.GetAttribute("endX","0"))
			Local startY:Float = Float(node.GetAttribute("startY","0"))
			Local endY:Float = Float(node.GetAttribute("endY","0"))
			Return CreatePosition(startTime, endTime, easeType, startX, startY, endX, endY)
		ElseIf name = "handle" Then
			Local startX:Float = Float(node.GetAttribute("startX","0"))
			Local endX:Float = Float(node.GetAttribute("endX","0"))
			Local startY:Float = Float(node.GetAttribute("startY","0"))
			Local endY:Float = Float(node.GetAttribute("endY","0"))
			Return CreateHandle(startTime, endTime, easeType, startX, startY, endX, endY)
		ElseIf name = "color" Then
			If node.HasAttribute("startRed") Or node.HasAttribute("startGreen") Or node.HasAttribute("startBlue") Then
				Local startRed:Float = Float(node.GetAttribute("startRed","255"))
				Local endRed:Float = Float(node.GetAttribute("endRed","255"))
				Local startGreen:Float = Float(node.GetAttribute("startGreen","255"))
				Local endGreen:Float = Float(node.GetAttribute("endGreen","255"))
				Local startBlue:Float = Float(node.GetAttribute("startBlue","255"))
				Local endBlue:Float = Float(node.GetAttribute("endBlue","255"))
				Return CreateColorRGB(startTime, endTime, easeType, startRed, startGreen, startBlue, endRed, endGreen, endBlue)
			Else
				Local startHue:Float = Float(node.GetAttribute("startHue","0"))
				Local endHue:Float = Float(node.GetAttribute("endHue","0"))
				Local startSaturation:Float = Float(node.GetAttribute("startSaturation","1"))
				Local endSaturation:Float = Float(node.GetAttribute("endSaturation","1"))
				Local startLuminance:Float = Float(node.GetAttribute("startLuminance","1"))
				Local endLuminance:Float = Float(node.GetAttribute("endLuminance","1"))
				Return CreateColorHSL(startTime, endTime, easeType, startHue, startSaturation, startLuminance, endHue, endSaturation, endLuminance)
			End
		End
		Return Null
	End

	Function CreateScale:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startScale#, endScale#)
		Return New StoryboardSpriteTransform(TRANSFORM_SCALE, startTime, endTime, easeType, startScale, endScale)
	End

	Function CreateScaleVector:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startScaleX#, startScaleY#, endScaleX#, endScaleY#)
		Return New StoryboardSpriteTransform(TRANSFORM_SCALE_VECTOR, startTime, endTime, easeType, startScaleX, endScaleX, startScaleY, endScaleY)
	End

	Function CreateAlpha:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startAlpha#, endAlpha#)
		startAlpha = Max(0.0,Min(1.0,startAlpha))
		endAlpha = Max(0.0,Min(1.0,endAlpha))
		Return New StoryboardSpriteTransform(TRANSFORM_ALPHA, startTime, endTime, easeType, startAlpha, endAlpha)
	End

	Function CreateRotation:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startRotation#, endRotation#)
		Return New StoryboardSpriteTransform(TRANSFORM_ROTATION, startTime, endTime, easeType, startRotation, endRotation)
	End

	Function CreatePosition:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startX#, startY#, endX#, endY#)
		Return New StoryboardSpriteTransform(TRANSFORM_POSITION, startTime, endTime, easeType, startX, endX, startY, endY)
	End

	Function CreateHandle:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startX#, startY#, endX#, endY#)
		Return New StoryboardSpriteTransform(TRANSFORM_HANDLE, startTime, endTime, easeType, startX, endX, startY, endY)
	End

	Function CreateColorRGB:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startRed#, startGreen#, startBlue#, endRed#, endGreen#, endBlue#)
		startRed = Max(0.0,Min(255.0,startRed))
		startGreen = Max(0.0,Min(255.0,startGreen))
		startBlue = Max(0.0,Min(255.0,startBlue))
		endRed = Max(0.0,Min(255.0,endRed))
		endGreen = Max(0.0,Min(255.0,endGreen))
		endBlue = Max(0.0,Min(255.0,endBlue))
		RGBtoHSL(startRed, startGreen, startBlue, hslArray)
		Local startHue:Float = hslArray[0], startSaturation:Float = hslArray[1], startLuminance:Float = hslArray[2]
		RGBtoHSL(endRed, endGreen, endBlue, hslArray)
		Local endHue:Float = hslArray[0], endSaturation:Float = hslArray[1], endLuminance:Float = hslArray[2]
		Return CreateColorHSL(startTime, endTime, easeType, startHue, startSaturation, startLuminance, endHue, endSaturation, endLuminance)
	End

	Function CreateColorHSL:StoryboardSpriteTransform(startTime%, endTime%, easeType%, startHue#, startSaturation#, startLuminance#, endHue#, endSaturation#, endLuminance#)
		startHue = Max(0.0, Min(1.0, startHue))
		startSaturation = Max(0.0, Min(1.0, startSaturation))
		startLuminance = Max(0.0, Min(1.0, startLuminance))
		endHue = Max(0.0, Min(1.0, endHue))
		endSaturation = Max(0.0, Min(1.0, endSaturation))
		endLuminance = Max(0.0, Min(1.0, endLuminance))
		Return New StoryboardSpriteTransform(TRANSFORM_COLOR, startTime, endTime, easeType, startHue, endHue, startSaturation, endSaturation, startLuminance, endLuminance)
	End

	Method New(transformType:Int, startTime:Int, endTime:Int, easeType:Int,
							startValue1:Float=0, endValue1:Float=0,
							startValue2:Float=0, endValue2:Float=0,
							startValue3:Float=0, endValue3:Float=0,
							startValue4:Float=0, endValue4:Float=0,
							startValue5:Float=0, endValue5:Float=0)
		Self.transformType = transformType
		Self.startTime = startTime
		Self.endTime = endTime
		Self.easeType = easeType
		Select transformType
			Case TRANSFORM_ALPHA, TRANSFORM_SCALE, TRANSFORM_ROTATION
				Init(1, startValue1, endValue1)
			Case TRANSFORM_POSITION, TRANSFORM_SCALE_VECTOR, TRANSFORM_HANDLE
				Init(2, startValue1, endValue1, startValue2, endValue2)
			Case TRANSFORM_COLOR
				Init(3, startValue1, endValue1, startValue2, endValue2, startValue3, endValue3)
		End
	End

	Method Update:Void(currentTime:Int)
		Self.currentTime = currentTime
		For Local i:Int = 0 Until startValues.Length
			currentValues[i] = Calculate(startValues[i], endValues[i])
		Next
	End

	Method Apply:Void(sprite:StoryboardSprite)
		Select transformType
			Case TRANSFORM_ALPHA
				sprite.alpha = currentValues[0]
			Case TRANSFORM_SCALE
				sprite.scale = currentValues[0]
			Case TRANSFORM_ROTATION
				sprite.rotation = currentValues[0]
			Case TRANSFORM_POSITION
				sprite.x = currentValues[0]
				sprite.y = currentValues[1]
			Case TRANSFORM_SCALE_VECTOR
				sprite.scaleX = currentValues[0]
				sprite.scaleY = currentValues[1]
			Case TRANSFORM_COLOR
				HSLtoRGB(currentValues[0], currentValues[1], currentValues[2], rgbArray)
				sprite.red = rgbArray[0]
				sprite.green = rgbArray[1]
				sprite.blue = rgbArray[2]
			Case TRANSFORM_HANDLE
				If sprite And sprite.image And sprite.image.image Then
					sprite.image.image.SetHandle(currentValues[0], currentValues[1])
				End
		End
	End

	Method Compare:Int(other:Object)
		Local o:StoryboardSpriteTransform = StoryboardSpriteTransform(other)
		If Not o Then Return -1
		If o = Self Then Return 0
		If startTime > o.startTime Then Return 1
		If startTime < o.startTime Then Return -1
		If endTime > o.endTime Then Return 1
		If endTime < o.endTime Then Return -1
		If transformType > o.transformType Then Return 1
		If transformType < o.transformType Then Return -1
		Return 0
	End

	Method Equals:Bool(other:Object)
		Return Compare(other)=0
	End

Private
	Method Init:Void(elementCount:Int,
							startValue1:Float=0, endValue1:Float=0,
							startValue2:Float=0, endValue2:Float=0,
							startValue3:Float=0, endValue3:Float=0,
							startValue4:Float=0, endValue4:Float=0,
							startValue5:Float=0, endValue5:Float=0)
		startValues = New Float[elementCount]
		endValues = New Float[elementCount]
		currentValues = New Float[elementCount]
		If elementCount >= 1 Then
			startValues[0] = startValue1
			endValues[0] = endValue1
		End
		If elementCount >= 2 Then
			startValues[1] = startValue2
			endValues[1] = endValue2
		End
		If elementCount >= 3 Then
			startValues[2] = startValue3
			endValues[2] = endValue3
		End
		If elementCount >= 4 Then
			startValues[3] = startValue4
			endValues[3] = endValue4
		End
		If elementCount >= 5 Then
			startValues[4] = startValue5
			endValues[4] = endValue5
		End
	End

	Method Calculate:Float(startValue:Float, endValue:Float)
		' shortcut for start/end (also accounts for the case when startTime==endTime)
		' we check endTime first because if startTime==endTime==currentTime, we want to use endValue
		If currentTime >= endTime Then Return endValue
		If currentTime <= startTime Then Return startValue

		' how far through are we?
		Local progress:Float = Float(currentTime - startTime) / Float(endTime - startTime)

		Select easeType
			Case EASE_IN_DOUBLE
				Return Lerp(endValue, startValue, (1-progress)*(1-progress)*(1-progress)*(1-progress))
			Case EASE_IN
				Return Lerp(endValue, startValue, (1-progress)*(1-progress))
			Case EASE_IN_HALF
				Return Lerp(endValue, startValue, Pow(1-progress, 1.5))
			Case EASE_OUT
				Return Lerp(startValue, endValue, progress * progress)
			Case EASE_OUT_HALF
				Return Lerp(startValue, endValue, Pow(progress, 1.5))
			Case EASE_OUT_DOUBLE
				Return Lerp(startValue, endValue, progress*progress*progress*progress)
			Case EASE_IN_OUT
				Return startValue + (-2*(progress*progress*progress) + 3*(progress*progress)) * (endValue - startValue)
			Default
				Return Lerp(startValue, endValue, progress);
		End
	End

	Method Lerp:Float(startValue:Float, endValue:Float, progress:Float)
		Return startValue + (endValue-startValue) * progress
	End
End


Fixes for Input class

Fixes for Input class

Strict

Class Input Abstract

Private
	Field _map:InputState[]
	Field _total:Int

Public
	Method New()
		_total = GetTotal()
		_map = _map.Resize(_total)

		Local i:Int = 0

		While (i < _total)
			_map = New InputState()
			i += 1
		Wend
	End Method

	Method Update:Void()
		Local i:Int = 0
		Local is:InputState

		While (i < _total)
			is = _map[i]

			If (GetState(i)) Then
				If (is.current > 0 And is.last = 2) Then
					is.current = 1
				Else
					is.current = 2
				End If
			Else
				If (is.current > 0 And is.last > 0) Then
					is.current = -1
				Else
					is.current = 0
				End If
			End If

			is.last = is.current
		Wend
	End Method

	Method Reset:Void()
		Local i:Int = 0
		Local is:InputState

		While (i < _total)
			is = _map[i]
			is.current = 0
			is.last = 0
		Wend
	End Method

	Method Pressed:Bool(key:Int)
		Return _map[key] = 1
	End Method

	Method JustPressed:Bool(key:Int)
		Return _map[key] = 2
	End Method

	Method JustReleased:Bool(key:Int)
		Return _map[key] = -1
	End Method

	Method Record:Stack<InputRecord>()
		Local data:Stack<InputRecord> = Null
		Local i:Int = 0
		Local is:InputState

		While (i < _total)
			is = _map[i]

			If (is.current = 0) Continue

			If (data = Null) Then
				data = New Stack<InputRecord>()
			End If

			data.Push(New InputRecord(i, is.current))

			i += 1
		Wend

		Return data
	End Method

	Method Playback:Void(record:Stack<InputRecord>)
		Local i:Int = 0
		Local l:Int = record.Length()
		Local ir:InputRecord

		While (i < l)
			ir = record[i]
			_map[ir.code].current = ir.value
			i += 1
		Wend
	End Method

	Method Any:Bool()
		Local i:Int = 0

		While (i < _total)
			If (_map[i].current > 0) Return True
			i += 1
		Wend

		Return False
	End Method

	Method Destroy:Void()
		_total = 0
	End Method

	Method GetTotal:Int() Abstract

	Method GetState:Bool(index:Int) Abstract

End Class

Class InputRecord

	Field code:Int
	Field value:Int

	Method New (code:Int, value:Int)
		Self.code = code
		Self.value = value
	End Method

End Class

Private
Class InputState

	Field current:Int
	Field last:Int

	Method New()
		current = 0
		last = 0
	End Method

End Class

Strict

Class Input Abstract

Private
	Field _map:InputState[]
	Field _total:Int

Public
	Method New()
		_total = GetTotal()
		_map = _map.Resize(_total)

		Local i:Int = 0

		While (i < _total)
			_map = New InputState()
			i += 1
		Wend
	End Method

	Method Update:Void()
		Local i:Int = 0
		Local is:InputState

		While (i < _total)
			is = _map[i]

			If (GetState(i)) Then
				If (is.current > 0) Then
					is.current = 1
				Else
					is.current = 2
				End If
			Else
				If (is.current > 0) Then
					is.current = -1
				Else
					is.current = 0
				End If
			End If
		Wend
	End Method

	Method Pressed:Bool(key:Int)
		Return _map[key] = 1
	End Method

	Method JustPressed:Bool(key:Int)
		Return _map[key] = 2
	End Method

	Method JustReleased:Bool(key:Int)
		Return _map[key] = -1
	End Method

	Method Record:Stack<InputRecord>()
		Local data:Stack<InputRecord> = Null
		Local i:Int = 0
		Local is:InputState

		While (i < _total)
			is = _map[i]

			If (is.current = 0) Continue

			If (data = Null) Then
				data = New Stack<InputRecord>()
			End If

			data.Push(New InputRecord(i, is.current))

			i += 1
		Wend

		Return data
	End Method

	Method Playback:Void(record:Stack<InputRecord>)
		Local i:Int = 0
		Local l:Int = record.Length()
		Local ir:InputRecord

		While (i < l)
			ir = record[i]
			_map[ir.code].current = ir.value
			i += 1
		Wend
	End Method

	Method Any:Bool()
		Local i:Int = 0

		While (i < _total)
			If (_map[i].current > 0) Return True
			i += 1
		Wend

		Return False
	End Method

	Method Destroy:Void()
		_total = 0
	End Method

	Method GetTotal:Int() Abstract

	Method GetState:Bool(index:Int) Abstract

End Class

Class InputRecord

	Field code:Int
	Field value:Int

	Method New (code:Int, value:Int)
		Self.code = code
		Self.value = value
	End Method

End Class

Private
Class InputState

	Field current:Int
	Field last:Int

	Method New()
		current = -1
		last = -1
	End Method

End Class


Android compile error fixed

Android compile error fixed

Strict

Import flixel.flxg
Import replay.keyrecord
Import replay.xyrecord
Import replay.xyzrecord
Import replay.framerecord

Class FlxReplay

	Field seed:Float

	Field frame:Int

	Field frameCount:Int

	Field finished:Bool

Private
	Field _frames:FrameRecord[]

	Field _capacity:Int

	Field _marker:Int

Public
	Method New()
		seed = 0
		frame = 0
		frameCount = 0
		finished = False
		_frames = []
		_capacity = 0
		_marker = 0
	End Method

	Method Destroy:Void()
		If (_frames.Length() = 0) Return

		Local i:Int = frameCount - 1

		While (i >= 0)
			_frames[i].Destroy()
			i -= 1
		Wend

		_frames = []
	End Method

	Method Create:Void(seed:Int)
		Destroy()
		_Init()
		Self.seed = seed
		Rewind()
	End Method

	Method Load:Void(fileContents:String)
		_Init()

		Local lines:String[] = fileContents.Split("~n")

		seed = Int(lines[0])

		Local line:String
		Local i:Int = 1
		Local l:Int = lines.Length()

		While (i < l)
			line = lines[i]

			If (line.Length() > 3) Then
				_frames[frameCount] = (New FrameRecord()).Load(line)
				frameCount += 1

				If (frameCount >= _capacity) Then
					_capacity *= 2
					_frames = _frames.Resize(_capacity)
				End If
			End If

			i += 1
		Wend

		Rewind()
	End Method

	Method Save:String()
		If (frameCount <= 0) Return ""

		Local output:StringStack = New StringStack()
		output.Push(FlxG.globalSeed + "~n")

		Local i:Int = 0
		While (i < frameCount)
			_frames[i].Save(output)
			output.Push("~n")
			i += 1
		Wend

		Return output.Join("")
	End Method

	Method RecordFrame:Void()
		Local accelRecord:XYZRecord
		Local joystickRecord:Stack<XYZRecord[]>
		Local touchRecord:Stack<XYRecord>
		Local keysRecord:Stack<KeyRecord>
		Local mouseRecord:XYRecord

		#If TARGET = "html5" Or TARGET = "ios" Or TARGET = "android"
			accelRecord = FlxG.accel.RecordXYZ()
		#End

		#If TARGET = "xna" Or TARGET = "glfw"
			If (Not FlxG.mobile) Then
				Local joyCount:Int = FlxG.JoystickCount()
				Local joyXYZRecord:XYZRecord[]

				For Local i:Int = 0 Until joyCount
					joyXYZRecord = FlxG.Joystick(i).RecordXYZ()

					If (joyXYZRecord.Length() > 0) Then
						If (joystickRecord = Null) joystickRecord = New Stack<XYZRecord[]>()
						joystickRecord.Insert(i, FlxG.Joystick(i).RecordXYZ())
					End If

					keysRecord = FlxG.Joystick(i).RecordKeys(keysRecord)
				Next
			End If
		#End

		#If TARGET = "ios" Or TARGET = "android"
			Local touchCount:Int = FlxG.TouchCount()
			Local touchXYRecord:XYRecord

			For Local i:Int = 0 Until touchCount
				touchXYRecord = FlxG.Touch(i).RecordXY()

				If (touchXYRecord <> Null) Then
					If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
					touchRecord.Insert(i, FlxG.Touch(i).RecordXY())
				End If

				keysRecord = FlxG.Touch(i).RecordKeys(keysRecord)

				If (i <> touchCount - 1 And Not FlxG.Touch(i + 1).Used()) Exit
			Next
		#Else
			#If TARGET = "xna" Or TARGET = "html5"
				If (Not FlxG.mobile) Then
					keysRecord = FlxG.keys.RecordKeys(keysRecord)
				End If
			#Else
				keysRecord = FlxG.keys.RecordKeys(keysRecord)
			#End
		#End

		#If TARGET = "html5" Or TARGET = "xna" Or TARGET = "glfw"
			Local touchXYRecord:XYRecord = FlxG.Touch(0).RecordXY()

			If (touchXYRecord <> Null) Then
				If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
				touchRecord.Insert(0, FlxG.Touch(0).RecordXY())
			End If

			keysRecord = FlxG.Touch(0).RecordKeys(keysRecord)
		#End

		mouseRecord = FlxG.mouse.RecordXY()
		keysRecord = FlxG.mouse.RecordKeys(keysRecord)

		If (keysRecord = Null And mouseRecord = Null And joystickRecord = Null And touchRecord = Null And accelRecord = Null) Then
			frame += 1
			Return
		End If

		_frames[frameCount] = (New FrameRecord()).Create(frame, keysRecord, mouseRecord, joystickRecord, touchRecord, accelRecord)
		frame += 1
		frameCount += 1

		If (frameCount >= _capacity) Then
			_capacity *= 2
			_frames = _frames.Resize(_capacity)
		End If
	End Method

	Method PlayNextFrame:Void()
		FlxG.ResetInput()

		If (_marker >= frameCount) Then
			finished = True
			Return
		End If

		If (_frames[_marker].frame <> frame) Then
			frame += 1
			Return
		End If

		frame += 1

		Local fr:FrameRecord = _frames[_marker]
		_marker += 1

		If (fr.keys <> Null) FlxG.keys.PlaybackKeys(fr.keys)
		If (fr.mouse <> Null) FlxG.mouse.PlaybackXY(fr.mouse)

		If (fr.joystick <> Null) Then
			Local i:Int = 0
			Local l:Int = fr.joystick.Length()

			While (i < l)
				FlxG.Joystick(i).PlaybackXYZ(fr.joystick.Get(i))
				i += 1
			Wend
		End If

		If (fr.touch <> Null) Then
			Local i:Int = 0
			Local l:Int = FlxG.TouchCount()
			Local tr:XYRecord

			While (i < l)
				tr = fr.touch.Get(i)
				If (tr = Null) Exit

				FlxG.Touch(i).PlaybackXY(tr)

				i += 1
			Wend
		End If

		If (fr.accel <> Null) FlxG.accel.PlaybackXYZ(fr.accel)
	End Method

	Method Rewind:Void()
		_marker = 0
		frame = 0
		finished = False
	End Method

Private
	Method _Init:Void()
		_capacity = 100
		_frames = _frames.Resize(_capacity)
		frameCount = 0
	End Method

End Class

Strict

Import flixel.flxg
Import replay.keyrecord
Import replay.xyrecord
Import replay.xyzrecord
Import replay.framerecord

Class FlxReplay

	Field seed:Float

	Field frame:Int

	Field frameCount:Int

	Field finished:Bool

Private
	Field _frames:FrameRecord[]

	Field _capacity:Int

	Field _marker:Int

Public
	Method New()
		seed = 0
		frame = 0
		frameCount = 0
		finished = False
		_frames = []
		_capacity = 0
		_marker = 0
	End Method

	Method Destroy:Void()
		If (_frames.Length() = 0) Return

		Local i:Int = frameCount - 1

		While (i >= 0)
			_frames[i].Destroy()
			i -= 1
		Wend

		_frames = []
	End Method

	Method Create:Void(seed:Int)
		Destroy()
		_Init()
		Self.seed = seed
		Rewind()
	End Method

	Method Load:Void(fileContents:String)
		_Init()

		Local lines:String[] = fileContents.Split("~n")

		seed = Int(lines[0])

		Local line:String
		Local i:Int = 1
		Local l:Int = lines.Length()

		While (i < l)
			line = lines[i]

			If (line.Length() > 3) Then
				_frames[frameCount] = (New FrameRecord()).Load(line)
				frameCount += 1

				If (frameCount >= _capacity) Then
					_capacity *= 2
					_frames = _frames.Resize(_capacity)
				End If
			End If

			i += 1
		Wend

		Rewind()
	End Method

	Method Save:String()
		If (frameCount <= 0) Return ""

		Local output:StringStack = New StringStack()
		output.Push(FlxG.globalSeed + "~n")

		Local i:Int = 0
		While (i < frameCount)
			_frames[i].Save(output)
			output.Push("~n")
			i += 1
		Wend

		Return output.Join("")
	End Method

	Method RecordFrame:Void()
		Local accelRecord:XYZRecord
		Local joystickRecord:Stack<XYZRecord[]>
		Local touchRecord:Stack<XYRecord>
		Local keysRecord:Stack<KeyRecord>
		Local mouseRecord:XYRecord

		#If TARGET = "html5" Or TARGET = "ios" Or TARGET = "android"
			accelRecord = FlxG.accel.RecordXYZ()
		#End

		#If TARGET = "xna" Or TARGET = "glfw"
			If (Not FlxG.mobile) Then
				Local joyCount:Int = FlxG.JoystickCount()
				Local joyXYZRecord:XYZRecord[]

				For Local i:Int = 0 Until joyCount
					joyXYZRecord = FlxG.Joystick(i).RecordXYZ()

					If (joyXYZRecord.Length() > 0) Then
						If (joystickRecord = Null) joystickRecord = New Stack<XYZRecord[]>()
						joystickRecord.Insert(i, FlxG.Joystick(i).RecordXYZ())
					End If

					keysRecord = FlxG.Joystick(i).RecordKeys(keysRecord)
				Next
			End If
		#End

		#If TARGET = "ios" Or TARGET = "android"
			Local touchCount:Int = FlxG.TouchCount()
			Local touchXYRecord:XYRecord

			For Local i:Int = 0 Until touchCount
				touchXYRecord = FlxG.Touch(i).RecordXY()

				If (touchXYRecord <> Null) Then
					If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
					touchRecord.Insert(i, FlxG.Touch(i).RecordXY())
				End If

				keysRecord = FlxG.Touch(i).RecordKeys(keysRecord)

				If (i <> touchCount - 1 And Not FlxG.Touch(i + 1).Used()) Exit
			Next
		#Else
			#If TARGET = "xna" Or TARGET = "html5"
				If (Not FlxG.mobile) Then
					keysRecord = FlxG.keys.RecordKeys(keysRecord)
				End If
			#Else
				keysRecord = FlxG.keys.RecordKeys(keysRecord)
			#End

			Local touchXYRecord:XYRecord = FlxG.Touch(0).RecordXY()

			If (touchXYRecord <> Null) Then
				If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
				touchRecord.Insert(0, FlxG.Touch(0).RecordXY())
			End If

			keysRecord = FlxG.Touch(0).RecordKeys(keysRecord)
		#End

		mouseRecord = FlxG.mouse.RecordXY()
		keysRecord = FlxG.mouse.RecordKeys(keysRecord)

		If (keysRecord = Null And mouseRecord = Null And joystickRecord = Null And touchRecord = Null And accelRecord = Null) Then
			frame += 1
			Return
		End If

		_frames[frameCount] = (New FrameRecord()).Create(frame, keysRecord, mouseRecord, joystickRecord, touchRecord, accelRecord)
		frame += 1
		frameCount += 1

		If (frameCount >= _capacity) Then
			_capacity *= 2
			_frames = _frames.Resize(_capacity)
		End If
	End Method

	Method PlayNextFrame:Void()
		FlxG.ResetInput()

		If (_marker >= frameCount) Then
			finished = True
			Return
		End If

		If (_frames[_marker].frame <> frame) Then
			frame += 1
			Return
		End If

		frame += 1

		Local fr:FrameRecord = _frames[_marker]
		_marker += 1

		If (fr.keys <> Null) FlxG.keys.PlaybackKeys(fr.keys)
		If (fr.mouse <> Null) FlxG.mouse.PlaybackXY(fr.mouse)

		If (fr.joystick <> Null) Then
			Local i:Int = 0
			Local l:Int = fr.joystick.Length()

			While (i < l)
				FlxG.Joystick(i).PlaybackXYZ(fr.joystick.Get(i))
				i += 1
			Wend
		End If

		If (fr.touch <> Null) Then
			Local i:Int = 0
			Local l:Int = FlxG.TouchCount()
			Local tr:XYRecord

			While (i < l)
				tr = fr.touch.Get(i)
				If (tr = Null) Exit

				FlxG.Touch(i).PlaybackXY(tr)

				i += 1
			Wend
		End If

		If (fr.accel <> Null) FlxG.accel.PlaybackXYZ(fr.accel)
	End Method

	Method Rewind:Void()
		_marker = 0
		frame = 0
		finished = False
	End Method

Private
	Method _Init:Void()
		_capacity = 100
		_frames = _frames.Resize(_capacity)
		frameCount = 0
	End Method

End Class