FlxG.Play fixed #40

FlxG.Play fixed #40

Strict

Import mojo.audio

Import flxbasic
Import flxobject
Import flxu
Import flxg
Import system.flxassetsmanager
Import system.flxresourcesmanager

Class FlxSound Extends FlxBasic

	Global ClassObject:FlxSoundClass = New FlxSoundClass()

	Field x:Float

	Field y:Float

	Field survive:Bool

	Field name:String

	Field artist:String

	Field autoDestroy:Bool

	Field _paused:Bool

	Field _looped:Bool

Private
	Const _CHANNELS_COUNT:Int = 32

	Global _NextChannel:Int = -1

	Global _LoopedChannels:Bool[_CHANNELS_COUNT]

	Global _SoundLoader:FlxSoundLoader = New FlxSoundLoader()

	Field _sound:Sound

	Field _channel:Int

	Field _soundVolume:Float

	Field _soundPan:Float

	Field _volume:Float

	Field _volumeAdjust:Float

	Field _target:FlxObject

	Field _radius:Float

	Field _pan:Bool

	Field _fadeOutTimer:Float

	Field _fadeOutTotal:Float

	Field _pauseOnFadeOut:Bool

	Field _fadeInTimer:Float

	Field _fadeInTotal:Float

	Field _oldMute:Bool

Public
	Method New()
		Super.New()
		_channel = -1
		_CreateSound()
	End Method

	Method Destroy:Void()
		Kill()
		_sound = Null
		_target = Null

		Super.Destroy()
	End Method

	Method Update:Void()
		If (_paused) Return

		Local radial:Float = 1.0
		Local fade:Float = 1.0
		Local updateNeeded:Bool = False

		If (_target <> Null) Then
			'in original flixel: radial = FlxU.GetDistance(_target.x, _target.y, x, y) / _radius. Maybe a bug?
			radial = 1 - FlxU.GetDistance(_target.x, _target.y, x, y) / _radius
			If (radial < 0) radial = 0

			If (_pan) Then
				'in original flixel: Local d:Float = (_target.x - x) / _radius. Maybe a bug?
				Local d:Float = -(_target.x - x) / _radius

				If (d < -1) Then
					d = -1
				ElseIf (d > 1) Then
					d = 1
				End If

				_soundPan = d
			End If

			updateNeeded = True
		End If

		If (_fadeOutTimer > 0) Then
			_fadeOutTimer -= FlxG.Elapsed

			If (_fadeOutTimer <= 0) Then
				If (_pauseOnFadeOut) Then
					Pause()
				Else
					Stop()
				End If
			End If

			fade = _fadeOutTimer / _fadeOutTotal
			If (fade < 0) fade = 0

			updateNeeded = True
		ElseIf (_fadeInTimer > 0) Then
			_fadeInTimer -= FlxG.Elapsed

			fade = _fadeInTimer / _fadeInTotal
			If (fade < 0) fade = 0
			fade = 1 - fade

			updateNeeded = True
		End If

		_volumeAdjust = radial * fade

		If (_oldMute <> FlxG.Mute) Then
			_UpdateTransform()
			_oldMute = FlxG.Mute
			updateNeeded = True
		End If

		If (updateNeeded) _UpdateTransform()
	End Method

	Method Kill:Void()
		Stop()
		Super.Kill()
	End Method

	Method Load:FlxSound(sound:String, looped:Bool = False, autoDestroy:Bool = False, stopPrevious:Bool = True)
		If (stopPrevious) Stop()

		_CreateSound()

		_channel = _GetFreeChannel()
		If (looped And _channel >= 0) _LoopedChannels[_channel] = True

		name = sound
		_sound = FlxG.AddSound(sound, _SoundLoader)
		_looped = looped
		_UpdateTransform()
		If (_sound <> Null) exists = True
		Return Self
	End Method

	Method Proximity:FlxSound(x:Float, y:Float, object:FlxObject, radius:Float, pan:Bool = True)
		Self.x = x
		Self.y = y
		_target = object
		_radius = radius
		_pan = pan

		Return Self
	End Method

	Method Play:Void(forceRestart:Bool = False)
		If (forceRestart) Then
			Local oldAutoDestroy:Bool = autoDestroy
			autoDestroy = False
			Stop()
			autoDestroy = oldAutoDestroy
		End If

		If (_channel < 0)  Then
			_channel = _GetFreeChannel()
			If (_channel < 0) Then
				exists = False
				Return
			ElseIf (_looped) Then
				_LoopedChannels[_channel] = True
			End If
		End If

		_UpdateTransform()

		If (Not _paused) Then
			PlaySound(_sound, _channel, _looped)
		Else
			ResumeChannel(_channel)
		End If

		active = True
		_paused = False
	End Method

	Method Resume:Void()
		If (Not _paused) Return

		If (_channel < 0) Then
			exists = False
			Return
		End If

		ResumeChannel(_channel)
		_paused = False
		active = True
	End Method

	Method Pause:Void()
		If (_channel < 0) Then
			exists = False
			Return
		End If

		PauseChannel(_channel)
		_paused = True
		active = False
	End Method

	Method Stop:Void()
		_paused = False

		If (_channel >= 0 And exists) Then
			StopChannel(_channel)
			If (_looped) _LoopedChannels[_channel] = False
			_channel = -1
			active = False

			If (autoDestroy) Then
				Destroy()
			End If
		Else
			exists = False
		End If
	End Method

	Method FadeOut:Void(seconds:Float, pauseInstead:Bool = False)
		_pauseOnFadeOut = pauseInstead
		_fadeInTimer = 0
		_fadeOutTimer = seconds
		_fadeOutTotal = _fadeOutTimer
	End Method

	Method FadeIn:Void(seconds:Float)
		_fadeOutTimer = 0
		_fadeInTimer = seconds
		_fadeInTotal = _fadeInTimer
	End Method

	Method Volume:Float() Property
		Return _volume
	End Method

	Method Volume:Void(volume:Float) Property
		_volume = volume

		If (_volume < 0) Then
			_volume = 0
		ElseIf (_volume > 1)
			_volume = 1
		End If

		_UpdateTransform()
	End Method

	Method GetActualVolume:Float()
		Return _volume * _volumeAdjust
	End Method

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

	Function GetValidExt:String()
	#If TARGET = "glfw" Or TARGET = "xna"
		Return "wav"
	#ElseIf TARGET = "html5"
		If (IsIE()) Then
			Return "mp3"
		Else
			Return "ogg"
		End If
	#Else
		Return "mp3"
	#End
	End Function

	Method _SetTransform:Void(volume:Float, pan:Float)
		If (_channel >= 0) Then
			SetChannelVolume(_channel, volume)
			SetChannelPan(_channel, pan)
		End If
	End Method

	Method _CreateSound:Void()
		Destroy()
		x = 0
		y = 0
		_sound = Null
		_channel = -1
		_paused = False
		_volume = 1.0
		_volumeAdjust = 1.0
		_looped = False
		_target = Null
		_radius = 0
		_pan = False
		_fadeOutTimer = 0
		_fadeOutTotal = 0
		_pauseOnFadeOut = False
		_fadeInTimer = 0
		_fadeInTotal = 0
		exists = False
		active = False
		visible = False
		name = ""
		artist = ""
		autoDestroy = False
	End Method

	Method _UpdateTransform:Void()
		If (Not FlxG.Mute) Then
			_soundVolume = FlxG._Volume * _volume * _volumeAdjust
		Else
			_soundVolume = 0
		End If

		If (_soundVolume < 0) _soundVolume = 0
		_SetTransform(_soundVolume, _soundPan)
	End Method

Private
	Method _GetFreeChannel:Int()
		Local i:Int = 0

		If (ChannelState(0) >= 0) Then
			While (i < _CHANNELS_COUNT)
				If (ChannelState(i) = 0) Return i
				i += 1
			Wend
		Else
			Local counter:Int = 0

			Repeat
				_NextChannel += 1
				If (_NextChannel >= _CHANNELS_COUNT) _NextChannel = 0

				counter += 1
				If (counter >= _CHANNELS_COUNT) Then
					FlxG.Log("Free channels for sound " + name + " are not found")
					Exit
				End If
			Until (Not _LoopedChannels[_NextChannel])

			Return _NextChannel
		End If

		Return -1
	End Method

End Class

Interface FlxVolumeChangeListener

	Method OnVolumeChange:Void(volume:Float)

 End Interface

Private
Class FlxSoundClass Implements FlxClass

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

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

End Class

Class FlxSoundLoader Extends FlxResourceLoader<Sound>

	Method Load:Sound(name:String)
		Return LoadSound(FlxAssetsManager.GetSoundPath(name))
	End Method

End Class

Strict

Import mojo.audio

Import flxbasic
Import flxobject
Import flxu
Import flxg
Import system.flxassetsmanager
Import system.flxresourcesmanager

Class FlxSound Extends FlxBasic

	Global ClassObject:FlxSoundClass = New FlxSoundClass()

	Field x:Float

	Field y:Float

	Field survive:Bool

	Field name:String

	Field artist:String

	Field autoDestroy:Bool

	Field _paused:Bool

	Field _looped:Bool

Private
	Const _CHANNELS_COUNT:Int = 32

	Global _NextChannel:Int = -1

	Global _LoopedChannels:Bool[_CHANNELS_COUNT]

	Global _SoundLoader:FlxSoundLoader = New FlxSoundLoader()

	Field _sound:Sound

	Field _channel:Int

	Field _soundVolume:Float

	Field _soundPan:Float

	Field _volume:Float

	Field _volumeAdjust:Float

	Field _target:FlxObject

	Field _radius:Float

	Field _pan:Bool

	Field _fadeOutTimer:Float

	Field _fadeOutTotal:Float

	Field _pauseOnFadeOut:Bool

	Field _fadeInTimer:Float

	Field _fadeInTotal:Float

	Field _oldMute:Bool

Public
	Method New()
		Super.New()
		_channel = -1
		_CreateSound()
	End Method

	Method Destroy:Void()
		Kill()
		_sound = Null
		_target = Null

		Super.Destroy()
	End Method

	Method Update:Void()
		If (_paused) Return

		Local radial:Float = 1.0
		Local fade:Float = 1.0
		Local updateNeeded:Bool = False

		If (_target <> Null) Then
			'in original flixel: radial = FlxU.GetDistance(_target.x, _target.y, x, y) / _radius. Maybe a bug?
			radial = 1 - FlxU.GetDistance(_target.x, _target.y, x, y) / _radius
			If (radial < 0) radial = 0

			If (_pan) Then
				'in original flixel: Local d:Float = (_target.x - x) / _radius. Maybe a bug?
				Local d:Float = -(_target.x - x) / _radius

				If (d < -1) Then
					d = -1
				ElseIf (d > 1) Then
					d = 1
				End If

				_soundPan = d
			End If

			updateNeeded = True
		End If

		If (_fadeOutTimer > 0) Then
			_fadeOutTimer -= FlxG.Elapsed

			If (_fadeOutTimer <= 0) Then
				If (_pauseOnFadeOut) Then
					Pause()
				Else
					Stop()
				End If
			End If

			fade = _fadeOutTimer / _fadeOutTotal
			If (fade < 0) fade = 0

			updateNeeded = True
		ElseIf (_fadeInTimer > 0) Then
			_fadeInTimer -= FlxG.Elapsed

			fade = _fadeInTimer / _fadeInTotal
			If (fade < 0) fade = 0
			fade = 1 - fade

			updateNeeded = True
		End If

		_volumeAdjust = radial * fade

		If (_oldMute <> FlxG.Mute) Then
			_UpdateTransform()
			_oldMute = FlxG.Mute
			updateNeeded = True
		End If

		If (updateNeeded) _UpdateTransform()
	End Method

	Method Kill:Void()
		Super.Kill()
		Stop()
	End Method

	Method Load:FlxSound(sound:String, looped:Bool = False, autoDestroy:Bool = False, stopPrevious:Bool = True)
		If (stopPrevious) Stop()

		_CreateSound()

		_channel = _GetFreeChannel()
		If (looped And _channel >= 0) _LoopedChannels[_channel] = True

		name = sound
		_sound = FlxG.AddSound(sound, _SoundLoader)
		_looped = looped
		_UpdateTransform()
		If (_sound <> Null) exists = True
		Return Self
	End Method

	Method Proximity:FlxSound(x:Float, y:Float, object:FlxObject, radius:Float, pan:Bool = True)
		Self.x = x
		Self.y = y
		_target = object
		_radius = radius
		_pan = pan

		Return Self
	End Method

	Method Play:Void(forceRestart:Bool = False)
		If (forceRestart) Then
			Local oldAutoDestroy:Bool = autoDestroy
			autoDestroy = False
			Stop()
			autoDestroy = oldAutoDestroy
		End If

		If (_channel < 0)  Then
			_channel = _GetFreeChannel()
			If (_channel < 0) Then
				exists = False
				Return
			ElseIf (_looped) Then
				_LoopedChannels[_channel] = True
			End If
		End If

		_UpdateTransform()

		If (Not _paused) Then
			PlaySound(_sound, _channel, _looped)
		Else
			ResumeChannel(_channel)
		End If

		active = True
		_paused = False
	End Method

	Method Resume:Void()
		If (Not _paused) Return

		If (_channel < 0) Then
			exists = False
			Return
		End If

		ResumeChannel(_channel)
		_paused = False
		active = True
	End Method

	Method Pause:Void()
		If (_channel < 0) Then
			exists = False
			Return
		End If

		PauseChannel(_channel)
		_paused = True
		active = False
	End Method

	Method Stop:Void()
		_paused = False

		If (_channel >= 0) Then
			StopChannel(_channel)
			If (_looped) _LoopedChannels[_channel] = False
			_channel = -1
			active = False

			If (autoDestroy) Then
				Destroy()
			End If
		Else
			exists = False
		End If
	End Method

	Method FadeOut:Void(seconds:Float, pauseInstead:Bool = False)
		_pauseOnFadeOut = pauseInstead
		_fadeInTimer = 0
		_fadeOutTimer = seconds
		_fadeOutTotal = _fadeOutTimer
	End Method

	Method FadeIn:Void(seconds:Float)
		_fadeOutTimer = 0
		_fadeInTimer = seconds
		_fadeInTotal = _fadeInTimer
	End Method

	Method Volume:Float() Property
		Return _volume
	End Method

	Method Volume:Void(volume:Float) Property
		_volume = volume

		If (_volume < 0) Then
			_volume = 0
		ElseIf (_volume > 1)
			_volume = 1
		End If

		_UpdateTransform()
	End Method

	Method GetActualVolume:Float()
		Return _volume * _volumeAdjust
	End Method

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

	Function GetValidExt:String()
	#If TARGET = "glfw" Or TARGET = "xna"
		Return "wav"
	#ElseIf TARGET = "html5"
		If (IsIE()) Then
			Return "mp3"
		Else
			Return "ogg"
		End If
	#Else
		Return "mp3"
	#End
	End Function

	Method _SetTransform:Void(volume:Float, pan:Float)
		If (_channel >= 0) Then
			SetChannelVolume(_channel, volume)
			SetChannelPan(_channel, pan)
		End If
	End Method

	Method _CreateSound:Void()
		Destroy()
		x = 0
		y = 0
		_sound = Null
		_channel = -1
		_paused = False
		_volume = 1.0
		_volumeAdjust = 1.0
		_looped = False
		_target = Null
		_radius = 0
		_pan = False
		_fadeOutTimer = 0
		_fadeOutTotal = 0
		_pauseOnFadeOut = False
		_fadeInTimer = 0
		_fadeInTotal = 0
		exists = False
		active = False
		visible = False
		name = ""
		artist = ""
		autoDestroy = False
	End Method

	Method _UpdateTransform:Void()
		If (Not FlxG.Mute) Then
			_soundVolume = FlxG._Volume * _volume * _volumeAdjust
		Else
			_soundVolume = 0
		End If

		If (_soundVolume < 0) _soundVolume = 0
		_SetTransform(_soundVolume, _soundPan)
	End Method

Private
	Method _GetFreeChannel:Int()
		Local i:Int = 0

		If (ChannelState(0) >= 0) Then
			While (i < _CHANNELS_COUNT)
				If (ChannelState(i) = 0) Return i
				i += 1
			Wend
		Else
			Local counter:Int = 0

			Repeat
				_NextChannel += 1
				If (_NextChannel >= _CHANNELS_COUNT) _NextChannel = 0

				counter += 1
				If (counter >= _CHANNELS_COUNT) Then
					FlxG.Log("Free channels for sound " + name + " are not found")
					Exit
				End If
			Until (Not _LoopedChannels[_NextChannel])

			Return _NextChannel
		End If

		Return -1
	End Method

End Class

Interface FlxVolumeChangeListener

	Method OnVolumeChange:Void(volume:Float)

 End Interface

Private
Class FlxSoundClass Implements FlxClass

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

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

End Class

Class FlxSoundLoader Extends FlxResourceLoader<Sound>

	Method Load:Sound(name:String)
		Return LoadSound(FlxAssetsManager.GetSoundPath(name))
	End Method

End Class


Added constructor to accept root element.

' Diddy XML parser
' This is a very simple XML parser, and expects a document to be as close to well-formed as possible.
' The only real leniency it has is that attributes may be defined without surrounding quotes/double quotes.
' All XML characters should be escaped in strings. > < etc.
' Currently it supports regular tag and attribute definitions, processing instructions, and skipping comments.
' For now, exported indentation is hardcoded to 2 spaces.
' Based on a Java example: http://www.devx.com/xml/Article/10114

Import functions
Import collections
Import io

Class XMLParser
Private
	Field reader:Reader
	Field elements:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field currentElement:XMLElement = Null

	Method SkipWhitespace:Void()
		While reader.IsReady()
			reader.Mark()
			If Not IsWhitespace(reader.Read())
				reader.Reset()
				Return
			End
		End
	End

	Method SkipProlog:Void()
		reader.Skip(1)
		Local type:Int = reader.Read()
		While True
			reader.Mark()
			Local str:String = ""
			If type = ASC_QUESTION Then
				str = reader.ReadString(2)
				If str.Length < 2 Or str = "?>" Then Exit
			ElseIf type = ASC_EXCLAMATION Then
				str = reader.ReadString(3)
				If str.Length < 3 Or str = "-->" Then Exit
			End
			reader.Reset()
			If reader.Skip(1) <= 0 Then Exit
		End
	End

	Method SkipPrologs:Void()
		While True
			SkipWhitespace()
			Local arr:Int[] = New Int[2]
			Local numread:Int = reader.PeekArray(arr, 0, 2)
			If numread < 2 Then Return
			If arr[0] <> ASC_LESS_THAN Then
				Error("Expected '<' but got '" + String.FromChar(arr[0]) + "'.")
			End
			If arr[1] = ASC_QUESTION Or arr[1] = ASC_EXCLAMATION Then
				SkipProlog()
			Else
				Exit
			End
		End
	End

	Method ReadTag:String()
		SkipWhitespace()
		Local rv:String = ""
		Local ch:Int = reader.Peek()
		If ch <> ASC_LESS_THAN Then
			Error("Expected '<' but got '" + String.FromChar(ch) + "'.")
		End

		rv += reader.ReadChar()
		While reader.Peek() <> ASC_GREATER_THAN
			rv += reader.ReadChar()
		End
		rv += reader.ReadChar()

		Return rv
	End

	' at the moment, this does not support the CDATA tag
	Method ReadText:String()
		Local rv:String = ""
		While reader.Peek() <> ASC_LESS_THAN
			rv += reader.ReadChar()
		End
		Return rv
	End

Public
' Methods
	Method ParseString:XMLDocument(xmlString:String)
		Return ParseReader(New StringReader(xmlString))
	End

	Method ParseFile:XMLDocument(filename:String)
		' TODO: read from files, once FileReader has been implemented
		Return Null'ParseReader(New FileReader(filename))
	End

	Method ParseReader:XMLDocument(reader:Reader)
		Self.reader = reader

		Local doc:XMLDocument = New XMLDocument

		While True
			SkipPrologs()
			Local index:Int, tagName:String
			Local currentTag:String = ReadTag().Trim()
			If currentTag.StartsWith("</") Then
				tagName = currentTag[2..currentTag.Length-1]
				If currentElement = Null Then
					Error("Got close tag " + tagName + " without open tag.")
				End
				If tagName <> currentElement.name Then
					Error("Expected close tag for " + currentElement.name + " but got " + tagName + ".")
				End
				If elements.IsEmpty() Then
					doc.root = currentElement
					Return doc
				End
				currentElement = elements.RemoveAt(elements.Size-1)
			Else
				index = currentTag.Find(" ")
				If index < 0 Then
					If currentTag.EndsWith("/>") Then
						tagName = currentTag[1..(currentTag.Length-2)]
						currentTag = "/>"
					Else
						tagName = currentTag[1..(currentTag.Length-1)]
						currentTag = ""
					End
				Else
					tagName = currentTag[1..index]
					currentTag = currentTag[(index+1)..]
				End

				Local element:XMLElement = New XMLElement(tagName, currentElement)
				Local tagClosed:Bool = False
				While currentTag.Length > 0
					currentTag = currentTag.Trim()

					If currentTag = "/>" Then
						tagClosed = True
						Exit
					ElseIf currentTag = ">" Then
						Exit
					End

					index = currentTag.Find("=")
					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					' this handles attributes without quoted values, but that breaks well-formed-ness and should be avoided
					Local attributeName:String = currentTag[0..index]
					currentTag = currentTag[(index+1)..]
					Local attributeValue:String
					Local quoted:Bool = True
					If currentTag.StartsWith("~q") Then
						index = currentTag.Find("~q", 1)
					ElseIf currentTag.StartsWith("'") Then
						index = currentTag.Find("'", 1)
					Else
						quoted = False
						index = currentTag.Find(" ")
						If index < 0 Then
							index = currentTag.Find(">")
							If index < 0 Then
								index = currentTag.Find("/")
							End
						End
					End

					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					If quoted Then
						attributeValue = currentTag[1..index]
					Else
						attributeValue = currentTag[0..index]
					End

					element.SetAttribute(attributeName, UnescapeXMLString(attributeValue))
					currentTag = currentTag[(index+1)..]
				End

				If Not tagClosed Then
					element.value = UnescapeXMLString(ReadText())
					If currentElement <> Null Then
						elements.Add(currentElement)
					End
					currentElement = element
				ElseIf currentElement = Null Then
					doc.root = element
					Return doc
				End
			End
		End
	End
End

Class XMLDocument
Private
	Field xmlVersion:String = "1.0"
	Field xmlEncoding:String = "UTF-8"
	Field root:XMLElement
Public

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

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

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

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

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

	Method Version:String() Property
		Return xmlVersion
	End

	Method Encoding:String() Property
		Return xmlEncoding
	End
End

Class XMLAttribute
Private
	Field name:String
	Field value:String

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

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

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

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

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

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

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

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

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

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

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

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

	Method Parent:XMLElement() Property
		Return parent
	End

	Method Name:String() Property
		Return name
	End

	Method Value:String() Property
		Return value
	End
End

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

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

' Diddy XML parser
' This is a very simple XML parser, and expects a document to be as close to well-formed as possible.
' The only real leniency it has is that attributes may be defined without surrounding quotes/double quotes.
' All XML characters should be escaped in strings. > < etc.
' Currently it supports regular tag and attribute definitions, processing instructions, and skipping comments.
' For now, exported indentation is hardcoded to 2 spaces.
' Based on a Java example: http://www.devx.com/xml/Article/10114

Import functions
Import collections
Import io

Class XMLParser
Private
	Field reader:Reader
	Field elements:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field currentElement:XMLElement = Null

	Method SkipWhitespace:Void()
		While reader.IsReady()
			reader.Mark()
			If Not IsWhitespace(reader.Read())
				reader.Reset()
				Return
			End
		End
	End

	Method SkipProlog:Void()
		reader.Skip(1)
		Local type:Int = reader.Read()
		While True
			reader.Mark()
			Local str:String = ""
			If type = ASC_QUESTION Then
				str = reader.ReadString(2)
				If str.Length < 2 Or str = "?>" Then Exit
			Elseif type = ASC_EXCLAMATION Then
				str = reader.ReadString(3)
				If str.Length < 3 Or str = "-->" Then Exit
			End
			reader.Reset()
			If reader.Skip(1) <= 0 Then Exit
		End
	End

	Method SkipPrologs:Void()
		While True
			SkipWhitespace()
			Local arr:Int[] = New Int[2]
			Local numread:Int = reader.PeekArray(arr, 0, 2)
			If numread < 2 Then Return
			If arr[0] <> ASC_LESS_THAN Then
				Error("Expected '<' but got '" + String.FromChar(arr[0]) + "'.")
			End
			If arr[1] = ASC_QUESTION Or arr[1] = ASC_EXCLAMATION Then
				SkipProlog()
			Else
				Exit
			End
		End
	End

	Method ReadTag:String()
		SkipWhitespace()
		Local rv:String = ""
		Local ch:Int = reader.Peek()
		If ch <> ASC_LESS_THAN Then
			Error("Expected '<' but got '" + String.FromChar(ch) + "'.")
		End

		rv += reader.ReadChar()
		While reader.Peek() <> ASC_GREATER_THAN
			rv += reader.ReadChar()
		End
		rv += reader.ReadChar()

		Return rv
	End

	' at the moment, this does not support the CDATA tag
	Method ReadText:String()
		Local rv:String = ""
		While reader.Peek() <> ASC_LESS_THAN
			rv += reader.ReadChar()
		End
		Return rv
	End

Public
' Methods
	Method ParseString:XMLDocument(xmlString:String)
		Return ParseReader(New StringReader(xmlString))
	End

	Method ParseFile:XMLDocument(filename:String)
		' TODO: read from files, once FileReader has been implemented
		Return Null'ParseReader(New FileReader(filename))
	End

	Method ParseReader:XMLDocument(reader:Reader)
		Self.reader = reader

		Local doc:XMLDocument = New XMLDocument

		While True
			SkipPrologs()
			Local index:Int, tagName:String
			Local currentTag:String = ReadTag().Trim()
			If currentTag.StartsWith("</") Then
				tagName = currentTag[2..currentTag.Length-1]
				If currentElement = Null Then
					Error("Got close tag " + tagName + " without open tag.")
				End
				If tagName <> currentElement.name Then
					Error("Expected close tag for " + currentElement.name + " but got " + tagName + ".")
				End
				If elements.IsEmpty() Then
					doc.root = currentElement
					Return doc
				End
				currentElement = elements.RemoveAt(elements.Size-1)
			Else
				index = currentTag.Find(" ")
				If index < 0 Then
					If currentTag.EndsWith("/>") Then
						tagName = currentTag[1..(currentTag.Length-2)]
						currentTag = "/>"
					Else
						tagName = currentTag[1..(currentTag.Length-1)]
						currentTag = ""
					End
				Else
					tagName = currentTag[1..index]
					currentTag = currentTag[(index+1)..]
				End

				Local element:XMLElement = New XMLElement(tagName, currentElement)
				Local tagClosed:Bool = False
				While currentTag.Length > 0
					currentTag = currentTag.Trim()

					If currentTag = "/>" Then
						tagClosed = True
						Exit
					ElseIf currentTag = ">" Then
						Exit
					End

					index = currentTag.Find("=")
					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					' this handles attributes without quoted values, but that breaks well-formed-ness and should be avoided
					Local attributeName:String = currentTag[0..index]
					currentTag = currentTag[(index+1)..]
					Local attributeValue:String
					Local quoted:Bool = True
					If currentTag.StartsWith("~q") Then
						index = currentTag.Find("~q", 1)
					ElseIf currentTag.StartsWith("'") Then
						index = currentTag.Find("'", 1)
					Else
						quoted = False
						index = currentTag.Find(" ")
						If index < 0 Then
							index = currentTag.Find(">")
							If index < 0 Then
								index = currentTag.Find("/")
							End
						End
					End

					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					If quoted Then
						attributeValue = currentTag[1..index]
					Else
						attributeValue = currentTag[0..index]
					End

					element.SetAttribute(attributeName, UnescapeXMLString(attributeValue))
					currentTag = currentTag[(index+1)..]
				End

				If Not tagClosed Then
					element.value = UnescapeXMLString(ReadText())
					If currentElement <> Null Then
						elements.Add(currentElement)
					End
					currentElement = element
				ElseIf currentElement = Null Then
					doc.root = element
					Return doc
				End
			End
		End
	End
End

Class XMLDocument
Private
	Field xmlVersion:String = "1.0"
	Field xmlEncoding:String = "UTF-8"
	Field root:XMLElement
Public

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

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

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

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

	Method Version:String() Property
		Return xmlVersion
	End

	Method Encoding:String() Property
		Return xmlEncoding
	End
End

Class XMLAttribute
Private
	Field name:String
	Field value:String

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

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

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

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

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

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

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

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

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

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

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

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

	Method Parent:XMLElement() Property
		Return parent
	End

	Method Name:String() Property
		Return name
	End

	Method Value:String() Property
		Return value
	End
End

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

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


Updated Sound example to show Stop command for GameSound

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

Strict

import diddy

Function Main:Int()
	game = new MyGame()
	Return 0
End Function

Global gameScreen:GameScreen

Class MyGame Extends DiddyApp

	Method OnCreate:Int()
		Super.OnCreate()

		LoadSounds()

		gameScreen = new GameScreen

		gameScreen.PreStart()

		Return 0
	End

	'***********************
	'* Load Sounds
	'***********************
	Method LoadSounds:Void()
		sounds.Load("lazer")
		sounds.Load("boom3")
	End
End

Class GameScreen Extends Screen
	Field boom:GameSound
	Field lazer:GameSound

	Method New()
		name = "Game"
		boom = game.sounds.Find("boom3")
		lazer = game.sounds.Find("lazer")
	End

	Method Start:Void()
		game.screenFade.Start(50, false)
	End

	Method Render:Void()
		Cls
		DrawText "GAME SCREEN!", SCREEN_WIDTH2, SCREEN_HEIGHT2, 0.5, 0.5
		DrawText "Press 1 for Boom Sound", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 20, 0.5, 0.5
		DrawText "Press 2 for Lazer Sound", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 40, 0.5, 0.5
		DrawText "Press 3 for Looping Lazer Sound", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 60, 0.5, 0.5
		DrawText "Press 4 to Stop Lazer Sound", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 80, 0.5, 0.5
		DrawText "Channel : "+SoundPlayer.channel, SCREEN_WIDTH2, SCREEN_HEIGHT2 + 120, 0.5, 0.5

		For Local i:Int = 0 to SoundPlayer.MAX_CHANNELS
			DrawText "C", 1 + (i * 20), 10
			DrawText i, 1 + (i * 20), 20

			DrawText ChannelState(i), 1 + (i * 20), 40 ' doesnt work in Flash!!!
			DrawText SoundPlayer.playerChannelState[i], 1 + (i * 20), 60
		Next

	End

	Method Update:Void()
		if KeyHit(KEY_1)
			boom.Play()
		End
		if KeyHit(KEY_2)
			lazer.loop = 0
			lazer.Play()
		End

		if KeyHit(KEY_3)
			lazer.loop = 1
			lazer.Play()
		End

		if KeyHit(KEY_4)
			lazer.Stop()
		End

		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = game.exitScreen
		End
	End
End

Strict

import diddy

Function Main:Int()
	game = new MyGame()
	Return 0
End Function

Global gameScreen:GameScreen

Class MyGame Extends DiddyApp

	Method OnCreate:Int()
		Super.OnCreate()

		LoadSounds()

		gameScreen = new GameScreen

		gameScreen.PreStart()

		Return 0
	End

	'***********************
	'* Load Sounds
	'***********************
	Method LoadSounds:Void()
		sounds.Load("lazer")
		sounds.Load("boom3")
	End
End

Class GameScreen Extends Screen
	Field boom:GameSound
	Field lazer:GameSound

	Method New()
		name = "Game"
		boom = game.sounds.Find("boom3")
		lazer = game.sounds.Find("lazer")
	End

	Method Start:Void()
		game.screenFade.Start(50, false)
	End

	Method Render:Void()
		Cls
		DrawText "GAME SCREEN!", SCREEN_WIDTH2, SCREEN_HEIGHT2, 0.5, 0.5
		DrawText "Press 1 for Boom Sound", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 20, 0.5, 0.5
		DrawText "Press 2 for Lazer Sound", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 40, 0.5, 0.5
		DrawText "Channel : "+SoundPlayer.channel, SCREEN_WIDTH2, SCREEN_HEIGHT2 + 60, 0.5, 0.5
	End

	Method Update:Void()
		if KeyHit(KEY_1)
			boom.Play()
		End
		if KeyHit(KEY_2)
			lazer.Play()
		End

		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = game.exitScreen
		End
	End
End


Destroy and Clear for FlxSignals

Destroy and Clear for FlxSignals

Strict

Import reflection
Import system.signals.flxsignal

Class FlxSignals

Private
	Field _signals:IntMap<FlxSignal>

	Field _methodSlots:List<FlxMethodSlot>

	Field _functionSlots:List<FlxFunctionSlot>

Public
	Method New()
		_signals = New IntMap<FlxSignal>()
		_methodSlots = New List<FlxMethodSlot>()
		_functionSlots = New List<FlxFunctionSlot>()
	End Method

	Method Destroy:Void(signalID:Int)
		Local s:FlxSignal = _signals.Get(signalID)
		If (s <> Null) s.Destroy()
	End Method

	Method Destroy:Void()
		For Local signal:FlxSignal = EachIn _signals.Values()
			signal.Destroy()
		Next

		_signals.Clear()

		Local methodSlotNode:list.Node<FlxMethodSlot> = _methodSlots.FirstNode()

		While (methodSlotNode <> Null)
			methodSlotNode.Value().Destroy()
			methodSlotNode.Remove()

			methodSlotNode = methodSlotNode.NextNode()
		Wend

		Local functionSlotNode:list.Node<FlxFunctionSlot> = _functionSlots.FirstNode()

		While (methodSlotNode <> Null)
			functionSlotNode.Value().Destroy()
			functionSlotNode.Remove()

			functionSlotNode = functionSlotNode.NextNode()
		Wend

		_signals = Null
		_methodSlots = Null
		_functionSlots = Null
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener)
		_GetSignal(signalID).RegisterListener(listener)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool)
		_GetSignal(signalID).RegisterListener(listener, instant)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool, priority:Int)
		_GetSignal(signalID).RegisterListener(listener, instant, priority)
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object)
		Connect(signalID, _GetMethodSlot(methodInfo, context))
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object, instant:Bool)
		Connect(signalID, _GetMethodSlot(methodInfo, context), instant)
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object, instant:Bool, priority:Int)
		Connect(signalID, _GetMethodSlot(methodInfo, context), instant, priority)
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo)
		Connect(signalID, _GetFunctionSlot(functionInfo))
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo, instant:Bool)
		Connect(signalID, _GetFunctionSlot(functionInfo), instant)
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo, instant:Bool, priority:Int)
		Connect(signalID, _GetFunctionSlot(functionInfo), instant, priority)
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context))
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object, instant:Bool)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context), instant)
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object, instant:Bool, priority:Int)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context), instant, priority)
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])))
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String, instant:Bool)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])), instant)
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String, instant:Bool, priority:Int)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])), instant, priority)
	End Method

	Method Disconnect:Void(signalID:Int, listener:FlxSignalListener)
		Local s:FlxSignal = _signals.Get(signalID)
		If (s <> Null) s.RemoveListener(listener)
	End Method

	Method Disconnect:Void(signalID:Int, methodInfo:MethodInfo, context:Object)
		Disconnect(signalID, _GetMethodSlot(methodInfo, context))
	End Method

	Method Disconnect:Void(signalID:Int, functionInfo:FunctionInfo)
		Disconnect(signalID, _GetFunctionSlot(functionInfo))
	End Method

	Method DisconnectM:Void(signalID:Int, methodName:String, context:Object)
		Disconnect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context))
	End Method

	Method DisconnectF:Void(signalID:Int, functionName:String)
		Disconnect(signalID, _GetFunctionSlot(GetFunction(functionName,[])))
	End Method

	Method Emit:Void(signalID:Int, data:Object = Null)
		_GetSignal(signalID).Emit(data)
	End Method

	Method Emit:Void(signalIDs:Int[], data:Object = Null)
		For Local id:Int = EachIn signalIDs
			_GetSignal(id).Emit(data)
		Next
	End Method

	Method Emit:Void(data:Object = Null)
		For Local signal:FlxSignal = EachIn _signals.Values()
			signal.Emit(data)
		Next
	End Method

	Method Clear:Void(signalID:Int)
		Local s:FlxSignal = _signals.Get(signalID)
		If (s <> Null) s.Clear()
	End Method

	Method Clear:Void()
		For Local signal:FlxSignal = EachIn _signals.Values()
			signal.Clear()
		Next
	End Method

Private
	Method _GetSignal:FlxSignal(signalID:Int)
		Local s:FlxSignal = _signals.Get(signalID)

		If (s = Null) Then
			s = New FlxSignal(signalID)
			_signals.Set(signalID, s)
		End If

		Return s
	End Method

	Method _GetMethodInfo:MethodInfo(methodName:String, context:Object)
		Local classInfo:ClassInfo = GetClass(context)

		If (classInfo <> Null) Then
			Return classInfo.GetMethod(methodName,[])
		End If

		Return Null
	End Method

	Method _GetMethodSlot:FlxMethodSlot(methodInfo:MethodInfo, context:Object)
		If (methodInfo = Null) Return Null

		Local node:list.Node<FlxMethodSlot> = _methodSlots.FirstNode()

		While (node <> Null)
			If (node.Value()._method = methodInfo And node.Value()._context = context) Then
				Return node.Value()
			End If

			node = node.NextNode()
		Wend

		Return _methodSlots.AddLast(New FlxMethodSlot(context, methodInfo)).Value()
	End Method

	Method _GetFunctionSlot:FlxFunctionSlot(functionInfo:FunctionInfo)
		If (functionInfo = Null) Return Null

		Local node:list.Node<FlxFunctionSlot> = _functionSlots.FirstNode()

		While (node <> Null)
			If (node.Value()._function = functionInfo) Then
				Return node.Value()
			End If

			node = node.NextNode()
		Wend

		Return _functionSlots.AddLast(New FlxFunctionSlot(functionInfo)).Value()
	End Method

End Class

Private

Class FlxMethodSlot Implements FlxSignalListener

Private
	Field _context:Object

	Field _method:MethodInfo

Public
	Method New(context:Object, methodInfo:MethodInfo)
		_context = context
		_method = methodInfo
	End Method

	Method Destroy:Void()
		_context = Null
		_method = Null
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_method.Invoke(_context,[])
	End Method

End Class

Class FlxFunctionSlot Implements FlxSignalListener

Private
	Field _function:FunctionInfo

Public
	Method New(functionInfo:FunctionInfo)
		_function = functionInfo
	End Method

	Method Destroy:Void()
		_function = Null
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_function.Invoke([])
	End Method

End Class

Strict

Import reflection
Import system.signals.flxsignal

Class FlxSignals

Private
	Field _signals:IntMap<FlxSignal>

	Field _methodSlots:List<FlxMethodSlot>

	Field _functionSlots:List<FlxFunctionSlot>

Public
	Method New()
		_signals = New IntMap<FlxSignal>()
		_methodSlots = New List<FlxMethodSlot>()
		_functionSlots = New List<FlxFunctionSlot>()
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener)
		_GetSignal(signalID).RegisterListener(listener)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool)
		_GetSignal(signalID).RegisterListener(listener, instant)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool, priority:Int)
		_GetSignal(signalID).RegisterListener(listener, instant, priority)
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object)
		Connect(signalID, _GetMethodSlot(methodInfo, context))
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object, instant:Bool)
		Connect(signalID, _GetMethodSlot(methodInfo, context), instant)
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object, instant:Bool, priority:Int)
		Connect(signalID, _GetMethodSlot(methodInfo, context), instant, priority)
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo)
		Connect(signalID, _GetFunctionSlot(functionInfo))
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo, instant:Bool)
		Connect(signalID, _GetFunctionSlot(functionInfo), instant)
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo, instant:Bool, priority:Int)
		Connect(signalID, _GetFunctionSlot(functionInfo), instant, priority)
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context))
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object, instant:Bool)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context), instant)
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object, instant:Bool, priority:Int)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context), instant, priority)
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])))
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String, instant:Bool)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])), instant)
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String, instant:Bool, priority:Int)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])), instant, priority)
	End Method

	Method Disconnect:Void(signalID:Int, listener:FlxSignalListener)
		Local s:FlxSignal = _signals.Get(signalID)
		If (s <> Null) s.RemoveListener(listener)
	End Method

	Method Disconnect:Void(signalID:Int, methodInfo:MethodInfo, context:Object)
		Disconnect(signalID, _GetMethodSlot(methodInfo, context))
	End Method

	Method Disconnect:Void(signalID:Int, functionInfo:FunctionInfo)
		Disconnect(signalID, _GetFunctionSlot(functionInfo))
	End Method

	Method DisconnectM:Void(signalID:Int, methodName:String, context:Object)
		Disconnect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context))
	End Method

	Method DisconnectF:Void(signalID:Int, functionName:String)
		Disconnect(signalID, _GetFunctionSlot(GetFunction(functionName,[])))
	End Method

	Method Emit:Void(signalID:Int, data:Object = Null)
		_GetSignal(signalID).Emit(data)
	End Method

	Method Emit:Void(signalIDs:Int[], data:Object = Null)
		For Local id:Int = EachIn signalIDs
			_GetSignal(id).Emit(data)
		Next
	End Method

	Method EmitAll:Void(data:Object)
		For Local signal:FlxSignal = EachIn _signals.Values()
			signal.Emit(data)
		Next
	End Method

Private
	Method _GetSignal:FlxSignal(signalID:Int)
		Local s:FlxSignal = _signals.Get(signalID)

		If (s = Null) Then
			s = New FlxSignal(signalID)
			_signals.Set(signalID, s)
		End If

		Return s
	End Method

	Method _GetMethodInfo:MethodInfo(methodName:String, context:Object)
		Local classInfo:ClassInfo = GetClass(context)

		If (classInfo <> Null) Then
			Return classInfo.GetMethod(methodName,[])
		End If

		Return Null
	End Method

	Method _GetMethodSlot:FlxMethodSlot(methodInfo:MethodInfo, context:Object)
		If (methodInfo = Null) Return Null

		Local node:list.Node<FlxMethodSlot> = _methodSlots.FirstNode()

		While (node <> Null)
			If (node.Value()._method = methodInfo And node.Value()._context = context) Then
				Return node.Value()
			End If

			node = node.NextNode()
		Wend

		Return _methodSlots.AddLast(New FlxMethodSlot(context, methodInfo)).Value()
	End Method

	Method _GetFunctionSlot:FlxFunctionSlot(functionInfo:FunctionInfo)
		If (functionInfo = Null) Return Null

		Local node:list.Node<FlxFunctionSlot> = _functionSlots.FirstNode()

		While (node <> Null)
			If (node.Value()._function = functionInfo) Then
				Return node.Value()
			End If

			node = node.NextNode()
		Wend

		Return _functionSlots.AddLast(New FlxFunctionSlot(functionInfo)).Value()
	End Method

End Class

Private

Class FlxMethodSlot Implements FlxSignalListener

Private
	Field _context:Object

	Field _method:MethodInfo

Public
	Method New(context:Object, methodInfo:MethodInfo)
		_context = context
		_method = methodInfo
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_method.Invoke(_context,[])
	End Method

End Class

Class FlxFunctionSlot Implements FlxSignalListener

Private
	Field _function:FunctionInfo

Public
	Method New(functionInfo:FunctionInfo)
		_function = functionInfo
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_function.Invoke([])
	End Method

End Class


Fixed some storyboard bugs.

Fixed some storyboard bugs.

--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=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

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, firstY:Float
	field firstScaleX:Float, firstScaleY:Float
	Field firstScale:Float, firstRotation:Float
	Field firstRed:Float, firstGreen:Float, firstBlue:Float, firstAlpha:Float

	Field x:Float, y:Float
	field scaleX:Float, scaleY:Float
	Field scale:Float, rotation:Float
	Field red:Float, green:Float, blue:Float, alpha:Float

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

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","0"))
		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
		For Local i:Int = 0 Until currentTransforms.Length
			currentTransforms[i] = GetActiveTransform(i, currentTime)
			If currentTransforms[i] Then currentTransforms[i].Apply(Self)
		Next
	End

	Method Render:Void()
		If Not image Then image = game.images.Find(imageName)
		If Not image Then Return

		If 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