Fixed OnSpriteUnbind for PrimitiveSpriteRenderer

Strict

Import mojo

Import flxextern
Import flxpoint
Import flxobject
Import flxcamera
Import flxg

Import system.flxanim
Import system.flxcolor

Import "data/default_flx.png"

Class FlxSprite Extends FlxObject

	Global ClassObject:Object

	Field origin:FlxPoint

	Field offset:FlxPoint

	Field scale:FlxPoint

	Field blend:Int

	Field finished:Bool

	Field frameWidth:Int

	Field frameHeight:Int

	Field frames:Int

	Field dirty:Bool

	Field _color:FlxColor

	Field _mixedColor:FlxColor

	Field _camera:FlxCamera

Private
	Global _GraphicLoader:FlxGraphicLoader = New FlxGraphicLoader()

	Global _Matrix:Float[6]

	Field _animations:StringMap<FlxAnim>

	Field _flipped:Bool

	Field _flipNeeded:Bool

	Field _curAnim:FlxAnim

	Field _curFrame:Int

	Field _curIndex:Int

	Field _frameTimer:Float

	Field _paused:Bool

	Field _callback:FlxAnimationListener

	Field _facing:Int

	Field _alpha:Float

	Field _bakedRotation:Float

	Field _pixels:Image

	Field _halfWidth:Float

	Field _halfHeight:Float

	Field _renderer:FlxSpriteRenderer

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

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

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

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

		_callback = Null

		_mixedColor = New FlxColor(FlxG.WHITE)

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

		LoadGraphic(simpleGraphic)
	End Method

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

		offset = Null
		origin = Null
		scale = Null
		_curAnim = Null
		_callback = Null
		_color = Null
		_mixedColor = Null
		_camera = Null
		_renderer = Null

		Super.Destroy()
	End Method

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

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

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

		_flipped = reverse

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

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

		ResetHelpers()

		Return Self
	End Method

	Method LoadRotatedGraphic:FlxSprite(graphic:String, rotations:Int = 16, frame:Int = -1)
		Error "LoadRotatedGraphic not currenlty support in " + FlxG.LIBRARY_NAME
		'TODO
		Return Null
	End Method

	Method MakeGraphic:FlxSprite(width:Int, height:Int, color:Int = FlxG.WHITE, unique:Bool = False, key:String = "")
		_pixels = FlxG.CreateBitmap(width, height, color, unique, key)
		_bakedRotation = 0

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

		ResetHelpers()

		Return Self
	End Method

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

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

		_camera = FlxG._CurrentCamera

		If (_cameras <> Null And Not _cameras.Contains(_camera)) Return
		If (Not OnScreen(_camera)) Return

		If (dirty) _CalcFrame()

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

		_point.x = x - int(_camera.scroll.x * scrollFactor.x) - offset.x
		_point.y = y - int(_camera.scroll.y * scrollFactor.y) - offset.y

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

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

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

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

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

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

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			If ( Not _flipNeeded) Then
				If (_renderer = Null) Then
					DrawImage(_pixels, _point.x, _point.y, _curIndex)
				Else
					_renderer.OnSpriteRender(_point.x, _point.y)
				End If
			Else
				PushMatrix()
					Transform(-1, 0, 0, 1, _point.x + _halfWidth, _point.y + _halfHeight)

					If (_renderer = Null) Then
						DrawImage(_pixels, -_halfWidth, -_halfHeight, _curIndex)
					Else
						_renderer.OnSpriteRender(-_halfWidth, -_halfHeight)
					End If
				PopMatrix()
			End If
		Else
			PushMatrix()
				'Translate
				_Matrix[4] = _point.x + origin.x
				_Matrix[5] = _point.y + origin.y

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

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

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

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

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

				If (_renderer = Null) Then
					DrawImage(_pixels, -origin.x, -origin.y, _curIndex)
				Else
					_renderer.OnSpriteRender(-origin.x, -origin.y)
				End If
			PopMatrix()
		End If

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

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

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

		Return anim
	End Method

	Method AddAnimation:FlxAnim(name:String, startFrame:Int, endFrame:Int, frameRate:Float = 0, looped:Bool = True)
		Local i:Int = 0
		Local l:Int = endFrame - startFrame + 1
		Local frames:Int[] = New Int[l]

		While (i < l)
			frames[i] = startFrame + i
			i += 1
		Wend

		Return AddAnimation(name, frames, frameRate, looped)
	End Method

	Method AddAnimation:FlxAnim(name:String, frameRate:Float = 0, looped:Bool = True)
		If (_pixels = Null) Return Null
		Return AddAnimation(name, 0, _pixels.Frames() -1, frameRate, looped)
	End Method

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

	Method ClearAnimationCallback:Void()
		_callback = Null
	End Method

	Method GetAnimation:FlxAnim(animName:String)
		Return _animations.Get(animName)
	End Method

	Method GetAnimation:FlxAnim()
		Return _curAnim
	End Method

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

		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0
		_paused = False

		Local anim:FlxAnim = _animations.Get(animName)

		If (anim <> Null) Then
			_curAnim = anim

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

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

		#If FLX_DEBUG_ENABLED = "1"
			FlxG.Log("WARNING: No animation called ~q" + animName + "~q")
		#End
	End Method

	Method Stop:Void()
		Frame = 0
	End Method

	Method Pause:Void()
		_paused = True
	End Method

	Method Resume:Void()
		_paused = False
	End Method

	Method RandomFrame:Void()
		_curAnim = Null

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

		dirty = True
	End Method

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

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

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

	Method Pixels:Image() Property
		Return _pixels
	End Method

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

		Local width:Int = 0
		Local height:Int = 0

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

		If (_pixels <> Null Or _renderer = Null) Then
			Self.width = width
			Self.height = height
			frameWidth = width
			frameHeight = height
			ResetHelpers()
		End If
	End Method

	Method Facing:Int() Property
		Return _facing
	End Method

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

		_flipNeeded = (_flipped And _facing = LEFT)
	End Method

	Method Alpha:Float() Property
		Return _alpha
	End Method

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

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

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

	Method Frame:Int() Property
		Return _curIndex
	End Method

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

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

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

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

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

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

	Method SetRenderer:Void(render:FlxSpriteRenderer)
		_renderer = render
		_renderer.OnSpriteBind(Self)

		frameWidth = width
		frameHeight = height

		ResetHelpers()
	End Method

	Method ClearRenderer:Void()
		Local renderer:FlxSpriteRenderer = _renderer
		_renderer = Null
		renderer.OnSpriteUnbind()

		frameWidth = width
		frameHeight = height

		ResetHelpers()
	End Method

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

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

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

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

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

			If (oldIndex <> _curIndex) _CalcFrame()
			Return
		End If

		If (_paused) Return

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

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

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

		If (dirty) _CalcFrame()
	End Method

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

End Class

Interface FlxSpriteRenderer

	Method OnSpriteBind:Void(sprite:FlxSprite)

	Method OnSpriteUnbind:Void()

	Method OnSpriteRender:Void(x:Float, y:Float)

End Interface

Class PrimitiveSpriteRenderer Implements FlxSpriteRenderer Abstract

	Method OnSpriteBind:Void(sprite:FlxSprite)
		_sprite = sprite
		_sprite.Pixels = Null
	End Method

	Method OnSpriteUnbind:Void()
		_sprite.Pixels = Null
		_sprite = Null
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float) Abstract

	Private

	Field _sprite:FlxSprite

End Class

Class RectSpriteRenderer Extends PrimitiveSpriteRenderer

	Method OnSpriteRender:Void(x:Float, y:Float)
		DrawRect(x, y, _sprite.frameWidth, _sprite.frameHeight)
	End Method

End Class

Class OvalSpriteRenderer Extends PrimitiveSpriteRenderer

	Method OnSpriteRender:Void(x:Float, y:Float)
		DrawOval(x, y, _sprite.frameWidth, _sprite.frameHeight)
	End Method

End Class

Private
Class FlxGraphicLoader Extends FlxResourceLoader<Image>

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

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

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

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

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

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

End Class

Strict

Import mojo

Import flxextern
Import flxpoint
Import flxobject
Import flxcamera
Import flxg

Import system.flxanim
Import system.flxcolor

Import "data/default_flx.png"

Class FlxSprite Extends FlxObject

	Global ClassObject:Object

	Field origin:FlxPoint

	Field offset:FlxPoint

	Field scale:FlxPoint

	Field blend:Int

	Field finished:Bool

	Field frameWidth:Int

	Field frameHeight:Int

	Field frames:Int

	Field dirty:Bool

	Field _color:FlxColor

	Field _mixedColor:FlxColor

	Field _camera:FlxCamera

Private
	Global _GraphicLoader:FlxGraphicLoader = New FlxGraphicLoader()

	Global _Matrix:Float[6]

	Field _animations:StringMap<FlxAnim>

	Field _flipped:Bool

	Field _flipNeeded:Bool

	Field _curAnim:FlxAnim

	Field _curFrame:Int

	Field _curIndex:Int

	Field _frameTimer:Float

	Field _paused:Bool

	Field _callback:FlxAnimationListener

	Field _facing:Int

	Field _alpha:Float

	Field _bakedRotation:Float

	Field _pixels:Image

	Field _halfWidth:Float

	Field _halfHeight:Float

	Field _renderer:FlxSpriteRenderer

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

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

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

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

		_callback = Null

		_mixedColor = New FlxColor(FlxG.WHITE)

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

		LoadGraphic(simpleGraphic)
	End Method

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

		offset = Null
		origin = Null
		scale = Null
		_curAnim = Null
		_callback = Null
		_color = Null
		_mixedColor = Null
		_camera = Null
		_renderer = Null

		Super.Destroy()
	End Method

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

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

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

		_flipped = reverse

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

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

		ResetHelpers()

		Return Self
	End Method

	Method LoadRotatedGraphic:FlxSprite(graphic:String, rotations:Int = 16, frame:Int = -1)
		Error "LoadRotatedGraphic not currenlty support in " + FlxG.LIBRARY_NAME
		'TODO
		Return Null
	End Method

	Method MakeGraphic:FlxSprite(width:Int, height:Int, color:Int = FlxG.WHITE, unique:Bool = False, key:String = "")
		_pixels = FlxG.CreateBitmap(width, height, color, unique, key)
		_bakedRotation = 0

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

		ResetHelpers()

		Return Self
	End Method

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

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

		_camera = FlxG._CurrentCamera

		If (_cameras <> Null And Not _cameras.Contains(_camera)) Return
		If (Not OnScreen(_camera)) Return

		If (dirty) _CalcFrame()

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

		_point.x = x - int(_camera.scroll.x * scrollFactor.x) - offset.x
		_point.y = y - int(_camera.scroll.y * scrollFactor.y) - offset.y

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

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

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

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

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

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

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			If ( Not _flipNeeded) Then
				If (_renderer = Null) Then
					DrawImage(_pixels, _point.x, _point.y, _curIndex)
				Else
					_renderer.OnSpriteRender(_point.x, _point.y)
				End If
			Else
				PushMatrix()
					Transform(-1, 0, 0, 1, _point.x + _halfWidth, _point.y + _halfHeight)

					If (_renderer = Null) Then
						DrawImage(_pixels, -_halfWidth, -_halfHeight, _curIndex)
					Else
						_renderer.OnSpriteRender(-_halfWidth, -_halfHeight)
					End If
				PopMatrix()
			End If
		Else
			PushMatrix()
				'Translate
				_Matrix[4] = _point.x + origin.x
				_Matrix[5] = _point.y + origin.y

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

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

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

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

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

				If (_renderer = Null) Then
					DrawImage(_pixels, -origin.x, -origin.y, _curIndex)
				Else
					_renderer.OnSpriteRender(-origin.x, -origin.y)
				End If
			PopMatrix()
		End If

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

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

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

		Return anim
	End Method

	Method AddAnimation:FlxAnim(name:String, startFrame:Int, endFrame:Int, frameRate:Float = 0, looped:Bool = True)
		Local i:Int = 0
		Local l:Int = endFrame - startFrame + 1
		Local frames:Int[] = New Int[l]

		While (i < l)
			frames[i] = startFrame + i
			i += 1
		Wend

		Return AddAnimation(name, frames, frameRate, looped)
	End Method

	Method AddAnimation:FlxAnim(name:String, frameRate:Float = 0, looped:Bool = True)
		If (_pixels = Null) Return Null
		Return AddAnimation(name, 0, _pixels.Frames() -1, frameRate, looped)
	End Method

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

	Method ClearAnimationCallback:Void()
		_callback = Null
	End Method

	Method GetAnimation:FlxAnim(animName:String)
		Return _animations.Get(animName)
	End Method

	Method GetAnimation:FlxAnim()
		Return _curAnim
	End Method

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

		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0
		_paused = False

		Local anim:FlxAnim = _animations.Get(animName)

		If (anim <> Null) Then
			_curAnim = anim

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

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

		#If FLX_DEBUG_ENABLED = "1"
			FlxG.Log("WARNING: No animation called ~q" + animName + "~q")
		#End
	End Method

	Method Stop:Void()
		Frame = 0
	End Method

	Method Pause:Void()
		_paused = True
	End Method

	Method Resume:Void()
		_paused = False
	End Method

	Method RandomFrame:Void()
		_curAnim = Null

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

		dirty = True
	End Method

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

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

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

	Method Pixels:Image() Property
		Return _pixels
	End Method

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

		Local width:Int = 0
		Local height:Int = 0

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

		If (_pixels <> Null Or _renderer = Null) Then
			Self.width = width
			Self.height = height
			frameWidth = width
			frameHeight = height
			ResetHelpers()
		End If
	End Method

	Method Facing:Int() Property
		Return _facing
	End Method

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

		_flipNeeded = (_flipped And _facing = LEFT)
	End Method

	Method Alpha:Float() Property
		Return _alpha
	End Method

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

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

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

	Method Frame:Int() Property
		Return _curIndex
	End Method

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

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

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

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

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

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

	Method SetRenderer:Void(render:FlxSpriteRenderer)
		_renderer = render
		_renderer.OnSpriteBind(Self)

		frameWidth = width
		frameHeight = height

		ResetHelpers()
	End Method

	Method ClearRenderer:Void()
		Local renderer:FlxSpriteRenderer = _renderer
		_renderer = Null
		renderer.OnSpriteUnbind()

		frameWidth = width
		frameHeight = height

		ResetHelpers()
	End Method

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

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

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

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

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

			If (oldIndex <> _curIndex) _CalcFrame()
			Return
		End If

		If (_paused) Return

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

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

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

		If (dirty) _CalcFrame()
	End Method

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

End Class

Interface FlxSpriteRenderer

	Method OnSpriteBind:Void(sprite:FlxSprite)

	Method OnSpriteUnbind:Void()

	Method OnSpriteRender:Void(x:Float, y:Float)

End Interface

Class PrimitiveSpriteRenderer Implements FlxSpriteRenderer Abstract

	Method OnSpriteBind:Void(sprite:FlxSprite)
		_sprite = sprite
		_sprite.Pixels = Null
	End Method

	Method OnSpriteUnbind:Void()
		_sprite = Null
		_sprite.Pixels = Null
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float) Abstract

	Private

	Field _sprite:FlxSprite

End Class

Class RectSpriteRenderer Extends PrimitiveSpriteRenderer

	Method OnSpriteRender:Void(x:Float, y:Float)
		DrawRect(x, y, _sprite.frameWidth, _sprite.frameHeight)
	End Method

End Class

Class OvalSpriteRenderer Extends PrimitiveSpriteRenderer

	Method OnSpriteRender:Void(x:Float, y:Float)
		DrawOval(x, y, _sprite.frameWidth, _sprite.frameHeight)
	End Method

End Class

Private
Class FlxGraphicLoader Extends FlxResourceLoader<Image>

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

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

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

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

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

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

End Class


Added 'SetUnsigned' to the 'IntArrayView' classes.

Strict

Public

#Rem
	TODO:
		* Replace uses of 'Int' and 'Float' with 'Long' and 'Double'.
			('LongArrayView', 'DoubleArrayView', etc)
#End

' Preprocessor related:
#REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS = True

'#If CONFIG = "debug"
	'#REGAL_UTIL_BUFFERS_SAFE = True
'#End

' Imports (Public):
Import brl.databuffer

' Imports (Private):
Private

Import memory

Import regal.sizeof
Import regal.typetool

Import monkey.math

Public

' Interfaces:
Interface BufferView
	' Methods:
	' Nothing so far.

	' Properties:
	Method Data:DataBuffer() Property
	Method Offset:UInt() Property ' Int
End

' Classes:
Class ArrayView<ValueType> Implements BufferView Abstract
	' Global variable(s):
	Global NIL:ValueType

	' Constructor(s):
	Method New(ElementSize:UInt, Count:UInt, __Direct:Bool=False)
		Self.ElementSize = ElementSize
		Self.Data = New DataBuffer(CountInBytes(Count), __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Self.Offset = OffsetInBytes
		Self.ElementSize = ElementSize
		Self.Data = Data
	End

	' The 'ExtraOffset' argument is used to offset from the 'View' object's offset.
	Method New(ElementSize:UInt, View:BufferView, ExtraOffset:UInt=0)
		Self.ElementSize = ElementSize
		Self.Offset = (View.Offset + ExtraOffset)
		Self.Data = View.Data
	End

	' Methods (Public):

	' If overridden, these methods must respect the 'Offset' property:
	Method Get:ValueType(Index:UInt)
		Return GetRaw(Offset + IndexToAddress(Index))
	End

	Method Set:Void(Index:UInt, Value:ValueType)
		SetRaw(Offset + IndexToAddress(Index), Value)

		Return
	End

	Method GetArray:Bool(Index:UInt, Output:ValueType[], Count:UInt, OutputOffset:UInt=0)
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		If (ByteBounds > Size) Then
			Return False
		Endif

		Local OutputPosition:= OutputOffset

		Local Address:= OffsetIndexToAddress(Index)

		While (Address < ByteBounds)
			Output[OutputPosition] = GetRaw(Address)

			OutputPosition += 1

			Address += ElementSize
		Wend

		' Return the default response.
		Return True
	End

	Method GetArray:ValueType[](Index:UInt, Count:UInt, _ArrOff:UInt=0)
		Local Output:= New ValueType[_ArrOff + Count]

		If (Not GetArray(Index, Output, Count, _ArrOff)) Then
			Return []
		Endif

		Return Output
	End

	Method SetArray:Bool(Index:UInt, Input:ValueType[], Count:UInt, InputOffset:UInt=0)
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		If (ByteBounds > Size) Then
			Return False
		Endif

		Local InputPosition:= InputOffset

		Local Address:= OffsetIndexToAddress(Index)

		While (Address < ByteBounds)
			SetRaw(Address, Input[InputPosition])

			InputPosition += 1

			Address += ElementSize
		Wend

		' Return the default response.
		Return True
	End

	Method SetArray:Bool(Index:UInt, Input:ValueType[])
		Return SetArray(Index, Input, Input.Length)
	End

	' TODO: Optimize this overload to bypass index conversion.
	Method SetArray:Bool(Input:ValueType[])
		Return SetArray(0, Input)
	End

	Method Add:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) + Value)

		Set(Index, Result)

		Return Result
	End

	Method Subtract:ValueType(Index:UInt, Value:ValueType)
		Return Add(Index, -Value)
	End

	' This supplies the raw size of 'Count' elements in bytes.
	Method CountInBytes:UInt(Count:Int)
		Return IndexToAddress(Count) ' (Count * ElementSize)
	End

	' This converts an index to a raw address for use with the internal buffer.
	Method IndexToAddress:UInt(Index:UInt)
		#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Index
			Endif
		#End

		Return (Index * ElementSize)
	End

	' This converts a raw address to an index.
	Method AddressToIndex:UInt(Address:UInt)
		#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Address
			Endif
		#End

		Return (Address / ElementSize)
	End

	#Rem
		The result of this method is a non-convertible raw address.
		By definition, this address may not be used with commands like
		'AddressToIndex', as the value would be skewed.

		To fix this address for such commands, subtract a stored result gathered
		from the 'Offset' property at the time this method was called.

		Because the value of 'Offset' can be changed, you must always ensure that
		the offset you apply to restore the appropriate value corresponds with the
		original value associated with this object's 'Offset' property.

		This type of address manipulation is not recommended,
		and should be avoided wherever possible.
	#End

	Method OffsetIndexToAddress:UInt(Index:UInt) Final
		Return (IndexToAddress(Index) + Offset)
	End

	' This method is an extension, which should only be used when converting
	' a value from a real address to a convertible address.
	' This command is not recommended, and is subject to deletion.
	Method __OffsetAddressToIndex:UInt(_Address:UInt) Final
		Return AddressToIndex(_Address - Offset)
	End

	' Methods (Protected):
	Protected

	' Abstract:

	' These two methods operate on raw addresses.
	' This means the input should not be an index,
	' nor should it be a non-offset address.
	' The i
	Method GetRaw:ValueType(Address:UInt) Abstract
	Method SetRaw:Void(Address:UInt, Value:ValueType) Abstract

	Public

	' Properties (Public):

	' This provides the number of elements in this view.
	Method Length:UInt() Property
		#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Size
			Endif
		#End

		Return (Size / ElementSize)
	End

	' This provides the raw size of the internal buffer (In bytes).
	Method Size:UInt() Property
		Return UInt(Data.Length)
	End

	' This specifies the size of an element.
	Method ElementSize:UInt() Property
		Return Self._ElementSize
	End

	' This provides access to the internal buffer offset.
	Method Offset:UInt() Property ' Int
		Return Self._Offset
	End

	Method Offset:Void(Value:UInt) Property
		Self._Offset = Value

		#If REGAL_UTIL_BUFFERS_SAFE
			' Nothing so far.
		#End

		Return
	End

	' This provides access to the internal buffer.
	Method Data:DataBuffer() Property ' Final
		Return Self._Data
	End

	Method Data:Void(Value:DataBuffer) Property
		Self._Data = Value

		Return
	End

	' Properties (Protected):
	Protected

	Method ElementSize:Void(Value:UInt) Property
		Self._ElementSize = Value

		Return
	End

	Public

	' Fields (Protected):
	Protected

	Field _ElementSize:UInt ' Int
	Field _Offset:UInt ' Int
	Field _Data:DataBuffer

	Public
End

' This is an intermediate class which defines mathematical routines for both integral and floating-point types.
Class MathArrayView<ValueType> Extends ArrayView<ValueType> Abstract
	' Constructor(s):
	Method New(ElementSize:UInt, Count:UInt, __Direct:Bool=False)
		Super.New(ElementSize, Count, __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(ElementSize, Data, OffsetInBytes)
	End

	Method New(ElementSize:UInt, View:BufferView, ExtraOffset:UInt=0)
		Super.New(ElementSize, View, ExtraOffset)
	End

	' Methods:

	' This increments the value located at 'Index' by one.
	Method Increment:ValueType(Index:UInt)
		Return Add(Index, ValueType(1))
	End

	' This decrements the value located at 'Index' by one.
	Method Decrement:ValueType(Index:UInt)
		Return Subtract(Index, ValueType(1))
	End

	' This multiplies the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Multiply:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) * Value)

		Set(Index, Result)

		Return Result
	End

	' This divides the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Divide:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) / Value)

		Set(Index, Result)

		Return Result
	End

	' This squares the value located at 'Index'.
	' The result is both returned by the method, and
	' written into memory at the specified location.
	Method Sq:ValueType(Index:UInt) ' Square
		Local Value:= Get(Index)
		Local Result:= (Value * Value)

		Set(Index, Result)

		Return Result
	End

	' This calculates the square-root of the value located at 'Index'.
	' The result is both returned by this method, and
	' written into memory at the specified location.
	Method Sqrt:ValueType(Index:UInt) ' SquareRoot
		Local Result:= Sqrt(Get(Index))

		Set(Index, Result)

		Return Result
	End
End

Class IntArrayView Extends MathArrayView<Int> ' ArrayView<Long> ' Int ' LongArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Integer ' 4

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(Type_Size:UInt, View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt)
		Return (Get(Index) & $FFFFFFFF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int)
		Set(Index, (Value & $FFFFFFFF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Int(Address:UInt) ' Final
		Return Data.PeekInt(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Int) ' Final
		Data.PokeInt(Address, Value)

		Return
	End

	Public
End

Class ShortArrayView Extends IntArrayView ' ArrayView<Int> ' Short
	' Constant variable(s):
	Const Type_Size:= SizeOf_Short

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(Type_Size:UInt, View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt) ' Short
		Return (Get(Index) & $FFFF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int) ' Short
		Set(Index, (Value & $FFFF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Int(Address:UInt) ' Final ' Short
		Return Data.PeekShort(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Int) ' Final ' Short
		Data.PokeShort(Address, Value)

		Return
	End

	Public
End

Class ByteArrayView Extends ShortArrayView ' ArrayView<Int> ' Byte
	' Constant variable(s):
	Const Type_Size:= SizeOf_Byte

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Methods (Public):
	#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
		' These two methods follow the integrity rules of 'Offset':
		Method Get:Int(Address:UInt) ' Byte
			Return GetRaw(Offset + Address)
		End

		Method Set:Void(Address:UInt, Value:Int) ' Byte
			SetRaw(Offset + Address, Value)

			Return
		End

		#Rem
			Method IndexToAddress:UInt(Address:UInt) ' Index:UInt
				Return Address
			End

			Method AddressToIndex:UInt(Address:UInt)
				Return Address
			End
		#End
	#End

	Method GetUnsigned:Int(Address:UInt) ' Byte
		Return (Get(Address) & $FF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int) ' Byte
		Set(Index, (Value & $FF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Int(Address:UInt) ' Final ' Byte
		Return Data.PeekByte(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Int) ' Final ' Byte
		Data.PokeByte(Address, Value)

		Return
	End

	Public
End

Class FloatArrayView Extends MathArrayView<Float> ' DoubleArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Float ' 4 ' 8

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Float(Address:UInt) ' Final ' Double
		Return Data.PeekFloat(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Float) ' Final ' Double
		Data.PokeFloat(Address, Value)

		Return
	End

	Public
End

Strict

Public

#Rem
	TODO:
		* Replace uses of 'Int' and 'Float' with 'Long' and 'Double'.
			('LongArrayView', 'DoubleArrayView', etc)
#End

' Preprocessor related:
#REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS = True

'#If CONFIG = "debug"
	'#REGAL_UTIL_BUFFERS_SAFE = True
'#End

' Imports (Public):
Import brl.databuffer

' Imports (Private):
Private

Import memory

Import regal.sizeof
Import regal.typetool

Import monkey.math

Public

' Interfaces:
Interface BufferView
	' Methods:
	' Nothing so far.

	' Properties:
	Method Data:DataBuffer() Property
	Method Offset:UInt() Property ' Int
End

' Classes:
Class ArrayView<ValueType> Implements BufferView Abstract
	' Global variable(s):
	Global NIL:ValueType

	' Constructor(s):
	Method New(ElementSize:UInt, Count:UInt, __Direct:Bool=False)
		Self.ElementSize = ElementSize
		Self.Data = New DataBuffer(CountInBytes(Count), __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Self.Offset = OffsetInBytes
		Self.ElementSize = ElementSize
		Self.Data = Data
	End

	' The 'ExtraOffset' argument is used to offset from the 'View' object's offset.
	Method New(ElementSize:UInt, View:BufferView, ExtraOffset:UInt=0)
		Self.ElementSize = ElementSize
		Self.Offset = (View.Offset + ExtraOffset)
		Self.Data = View.Data
	End

	' Methods (Public):

	' If overridden, these methods must respect the 'Offset' property:
	Method Get:ValueType(Index:UInt)
		Return GetRaw(Offset + IndexToAddress(Index))
	End

	Method Set:Void(Index:UInt, Value:ValueType)
		SetRaw(Offset + IndexToAddress(Index), Value)

		Return
	End

	Method GetArray:Bool(Index:UInt, Output:ValueType[], Count:UInt, OutputOffset:UInt=0)
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		If (ByteBounds > Size) Then
			Return False
		Endif

		Local OutputPosition:= OutputOffset

		Local Address:= OffsetIndexToAddress(Index)

		While (Address < ByteBounds)
			Output[OutputPosition] = GetRaw(Address)

			OutputPosition += 1

			Address += ElementSize
		Wend

		' Return the default response.
		Return True
	End

	Method GetArray:ValueType[](Index:UInt, Count:UInt, _ArrOff:UInt=0)
		Local Output:= New ValueType[_ArrOff + Count]

		If (Not GetArray(Index, Output, Count, _ArrOff)) Then
			Return []
		Endif

		Return Output
	End

	Method SetArray:Bool(Index:UInt, Input:ValueType[], Count:UInt, InputOffset:UInt=0)
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		If (ByteBounds > Size) Then
			Return False
		Endif

		Local InputPosition:= InputOffset

		Local Address:= OffsetIndexToAddress(Index)

		While (Address < ByteBounds)
			SetRaw(Address, Input[InputPosition])

			InputPosition += 1

			Address += ElementSize
		Wend

		' Return the default response.
		Return True
	End

	Method SetArray:Bool(Index:UInt, Input:ValueType[])
		Return SetArray(Index, Input, Input.Length)
	End

	' TODO: Optimize this overload to bypass index conversion.
	Method SetArray:Bool(Input:ValueType[])
		Return SetArray(0, Input)
	End

	Method Add:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) + Value)

		Set(Index, Result)

		Return Result
	End

	Method Subtract:ValueType(Index:UInt, Value:ValueType)
		Return Add(Index, -Value)
	End

	' This supplies the raw size of 'Count' elements in bytes.
	Method CountInBytes:UInt(Count:Int)
		Return IndexToAddress(Count) ' (Count * ElementSize)
	End

	' This converts an index to a raw address for use with the internal buffer.
	Method IndexToAddress:UInt(Index:UInt)
		#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Index
			Endif
		#End

		Return (Index * ElementSize)
	End

	' This converts a raw address to an index.
	Method AddressToIndex:UInt(Address:UInt)
		#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Address
			Endif
		#End

		Return (Address / ElementSize)
	End

	#Rem
		The result of this method is a non-convertible raw address.
		By definition, this address may not be used with commands like
		'AddressToIndex', as the value would be skewed.

		To fix this address for such commands, subtract a stored result gathered
		from the 'Offset' property at the time this method was called.

		Because the value of 'Offset' can be changed, you must always ensure that
		the offset you apply to restore the appropriate value corresponds with the
		original value associated with this object's 'Offset' property.

		This type of address manipulation is not recommended,
		and should be avoided wherever possible.
	#End

	Method OffsetIndexToAddress:UInt(Index:UInt) Final
		Return (IndexToAddress(Index) + Offset)
	End

	' This method is an extension, which should only be used when converting
	' a value from a real address to a convertible address.
	' This command is not recommended, and is subject to deletion.
	Method __OffsetAddressToIndex:UInt(_Address:UInt) Final
		Return AddressToIndex(_Address - Offset)
	End

	' Methods (Protected):
	Protected

	' Abstract:

	' These two methods operate on raw addresses.
	' This means the input should not be an index,
	' nor should it be a non-offset address.
	' The i
	Method GetRaw:ValueType(Address:UInt) Abstract
	Method SetRaw:Void(Address:UInt, Value:ValueType) Abstract

	Public

	' Properties (Public):

	' This provides the number of elements in this view.
	Method Length:UInt() Property
		#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Size
			Endif
		#End

		Return (Size / ElementSize)
	End

	' This provides the raw size of the internal buffer (In bytes).
	Method Size:UInt() Property
		Return UInt(Data.Length)
	End

	' This specifies the size of an element.
	Method ElementSize:UInt() Property
		Return Self._ElementSize
	End

	' This provides access to the internal buffer offset.
	Method Offset:UInt() Property ' Int
		Return Self._Offset
	End

	Method Offset:Void(Value:UInt) Property
		Self._Offset = Value

		#If REGAL_UTIL_BUFFERS_SAFE
			' Nothing so far.
		#End

		Return
	End

	' This provides access to the internal buffer.
	Method Data:DataBuffer() Property ' Final
		Return Self._Data
	End

	Method Data:Void(Value:DataBuffer) Property
		Self._Data = Value

		Return
	End

	' Properties (Protected):
	Protected

	Method ElementSize:Void(Value:UInt) Property
		Self._ElementSize = Value

		Return
	End

	Public

	' Fields (Protected):
	Protected

	Field _ElementSize:UInt ' Int
	Field _Offset:UInt ' Int
	Field _Data:DataBuffer

	Public
End

' This is an intermediate class which defines mathematical routines for both integral and floating-point types.
Class MathArrayView<ValueType> Extends ArrayView<ValueType> Abstract
	' Constructor(s):
	Method New(ElementSize:UInt, Count:UInt, __Direct:Bool=False)
		Super.New(ElementSize, Count, __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(ElementSize, Data, OffsetInBytes)
	End

	Method New(ElementSize:UInt, View:BufferView, ExtraOffset:UInt=0)
		Super.New(ElementSize, View, ExtraOffset)
	End

	' Methods:

	' This increments the value located at 'Index' by one.
	Method Increment:ValueType(Index:UInt)
		Return Add(Index, ValueType(1))
	End

	' This decrements the value located at 'Index' by one.
	Method Decrement:ValueType(Index:UInt)
		Return Subtract(Index, ValueType(1))
	End

	' This multiplies the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Multiply:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) * Value)

		Set(Index, Result)

		Return Result
	End

	' This divides the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Divide:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) / Value)

		Set(Index, Result)

		Return Result
	End

	' This squares the value located at 'Index'.
	' The result is both returned by the method, and
	' written into memory at the specified location.
	Method Sq:ValueType(Index:UInt) ' Square
		Local Value:= Get(Index)
		Local Result:= (Value * Value)

		Set(Index, Result)

		Return Result
	End

	' This calculates the square-root of the value located at 'Index'.
	' The result is both returned by this method, and
	' written into memory at the specified location.
	Method Sqrt:ValueType(Index:UInt) ' SquareRoot
		Local Result:= Sqrt(Get(Index))

		Set(Index, Result)

		Return Result
	End
End

Class IntArrayView Extends MathArrayView<Int> ' ArrayView<Long> ' Int ' LongArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Integer ' 4

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(Type_Size:UInt, View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt)
		Return (Get(Index) & $FFFFFFFF)
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Int(Address:UInt) ' Final
		Return Data.PeekInt(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Int) ' Final
		Data.PokeInt(Address, Value)

		Return
	End

	Public
End

Class ShortArrayView Extends IntArrayView ' ArrayView<Int> ' Short
	' Constant variable(s):
	Const Type_Size:= SizeOf_Short

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(Type_Size:UInt, View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt) ' Short
		Return (Get(Index) & $FFFF)
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Int(Address:UInt) ' Final ' Short
		Return Data.PeekShort(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Int) ' Final ' Short
		Data.PokeShort(Address, Value)

		Return
	End

	Public
End

Class ByteArrayView Extends ShortArrayView ' ArrayView<Int> ' Byte
	' Constant variable(s):
	Const Type_Size:= SizeOf_Byte

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Methods (Public):
	#If REGAL_UTIL_BUFFERS_BYTE_OPTIMIZATIONS
		' These two methods follow the integrity rules of 'Offset':
		Method Get:Int(Address:UInt) ' Byte
			Return GetRaw(Offset + Address)
		End

		Method Set:Void(Address:UInt, Value:Int) ' Byte
			SetRaw(Offset + Address, Value)

			Return
		End

		#Rem
			Method IndexToAddress:UInt(Address:UInt) ' Index:UInt
				Return Address
			End

			Method AddressToIndex:UInt(Address:UInt)
				Return Address
			End
		#End
	#End

	Method GetUnsigned:Int(Address:UInt) ' Byte
		Return (Get(Address) & $FF)
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Int(Address:UInt) ' Final ' Byte
		Return Data.PeekByte(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Int) ' Final ' Byte
		Data.PokeByte(Address, Value)

		Return
	End

	Public
End

Class FloatArrayView Extends MathArrayView<Float> ' DoubleArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Float ' 4 ' 8

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0)
		Super.New(Type_Size, Data, OffsetInBytes)
	End

	Method New(View:BufferView, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ExtraOffset)
	End

	' Methods (Protected):
	Protected

	Method GetRaw:Float(Address:UInt) ' Final ' Double
		Return Data.PeekFloat(Address)
	End

	Method SetRaw:Void(Address:UInt, Value:Float) ' Final ' Double
		Data.PokeFloat(Address, Value)

		Return
	End

	Public
End


Minor cleanup; Monkey.

Minor cleanup; Monkey.

Strict

Public

' Preprocessor related:
#VIRTUALOS_FLAG_OS = True
#VIRTUALOS_STANDALONE = True

#If TARGET = "html5"
	#VIRTUALOS_JS_TARGET = True
#End

#If VIRTUALOS_JS_TARGET
	#VIRTUALOS_IMPLEMENTED = True

	#VIRTUALOS_MAP_ENV = True
	#VIRTUALOS_MAP_FILETIMES = True

	#VIRTUALOS_EXTENSION_DL = True
	#VIRTUALOS_EXTENSION_VFILE = True
	#VIRTUALOS_EXTENSION_REMOTEPATH = True
	#VIRTUALOS_EXTENSION_NATIVE_RECURSION = True

	'#VIRTUALOS_REAL_FILEPATH = True

	#If CONFIG = "debug"
		'#VIRTUALOS_DEBUG = True
	#End
#Elseif LANG = "cpp" And TARGET <> "win8" And TARGET <> "ios"
	#VIRTUALOS_REAL = True
#End

#If VIRTUALOS_IMPLEMENTED
	#If VIRTUALOS_FLAG_OS
		#BRL_OS_IMPLEMENTED = True
	#End
#End

' Imports (Public):
#If Not VIRTUALOS_IMPLEMENTED
	#If VIRTUALOS_REAL
		Import os
	#End
#Else
	Import "native/os.${LANG}"
#End

#If VIRTUALOS_REAL_FILEPATH
	Import brl.filepath
#End

' Imports (Private):
Private

#If VIRTUALOS_IMPLEMENTED
	#If Not VIRTUALOS_STANDALONE
		Import regal.stringutil

		#If VIRTUALOS_EXTENSION_DL And VIRTUALOS_EXTENSION_VFILE
			Import regal.ioutil.stringstream
		#End
	#Else
		Import brl.databuffer
		Import brl.datastream
	#End

	Import monkey.map

	#If VIRTUALOS_JS_TARGET
		'Import dom
	#End
#End

Public

#If VIRTUALOS_IMPLEMENTED
	' Constant variable(s) (Public):

	' File-types (Must be the same across borders):
	Const FILETYPE_NONE:=		0
	Const FILETYPE_FILE:=		1
	Const FILETYPE_DIR:=		2

	Const FILETIME_UNAVAILABLE:= 0

	' Constant variable(s) (Private):
	Private

	#If VIRTUALOS_STANDALONE
		Const STRING_INVALID_LOCATION:= -1
	#End

	Public

	' External bindings:
	Extern

	' Global variable(s) (External) (Private):
	Extern Private

	' If you are supporting these extensions, please provide '__OS_Storage'.
	#If VIRTUALOS_EXTENSION_VFILE
		#If VIRTUALOS_JS_TARGET
			Global __OS_Storage:StorageHandle="__os_storage"
		#End
	#End

	Extern

	' Classes (External) (Private):
	Extern Private

	' API:
	' Nothing so far.

	' Extensions:
	#If VIRTUALOS_EXTENSION_VFILE
		#If VIRTUALOS_JS_TARGET
			Class StorageHandle = "Storage" ' Extends DOMObject
				' Methods:
				' Nothing so far.
			End
		#Else
			#Error "Unable to resolve type: 'StorageHandle'"
		#End
	#End

	Extern

	' Functions (External):

	' API:
	Function HostOS:String()
	Function AppPath:String()
	Function AppArgs:String[]()
	Function RealPath:String(Path:String)
	Function FileType:Int(Path:String)
	Function FileSize:Int(Path:String)

	#If Not VIRTUALOS_MAP_FILETIMES
		Function FileTime:Int(Path:String)

		Function CopyFile:Bool(Src:String, Dst:String)
		Function DeleteFile:Bool(Path:String)
	#Else
		Function _CopyFile:Bool(Src:String, Dst:String)="CopyFile"
		Function _DeleteFile:Bool(Path:String)="DeleteFile"
	#End

	Function LoadString:String(Path:String)
	Function SaveString:Int(Str:String, Path:String)
	Function LoadDir:String[](Path:String)
	Function CreateDir:Bool(Path:String)

	Function DeleteDir:Bool(Path:String)

	#If VIRTUALOS_EXTENSION_NATIVE_RECURSION
		Function DeleteDir:Bool(Path:String, Recursive:Bool)
	#End

	Function ChangeDir:Int(Path:String)
	Function CurrentDir:String()

	#If Not VIRTUALOS_MAP_ENV
		Function SetEnv:Int(Name:String, Value:String)
		Function GetEnv:String(Name:String)
	#End

	Function Execute:Int(CMD:String)

	#If Not VIRTUALOS_DEBUG
		Function ExitApp:Int(RetCode:Int)
	#Else
		Function _ExitApp:Int(RetCode:Int)="ExitApp"
	#End

	' Extensions:
	#If VIRTUALOS_EXTENSION_REMOTEPATH
		Function __OS_ToRemotePath:String(RealPath:String)="__os_toRemotePath"
	#End

	#If VIRTUALOS_EXTENSION_DL
		Function __OS_Download:String(URL:String)="__os_download_as_string"

		' Virtual file-system (JavaScript) extensions:
		#If VIRTUALOS_EXTENSION_VFILE ' VIRTUALOS_JS_TARGET
			Function __OS_DownloadFileUsingRep:String(Storage:StorageHandle, URL:String, Rep:String)="__os_downloadFileUsingRep"
			Function __OS_DownloadFile:Void(Storage:StorageHandle, RealPath:String)="__os_downloadFile"
		#End
	#End

	#If VIRTUALOS_EXTENSION_VFILE
		Function __OS_StorageSupported:Bool()="__os_storageSupported"
		Function __OS_CreateFileLink:Void(Rep:String)="__os_createFileLink"
	#End

	Public

	' Global variable(s) (Private):
	Private

	#If VIRTUALOS_MAP_ENV
		Global __OS_Env:= New StringMap<String>()
	#End

	#If VIRTUALOS_MAP_FILETIMES
		Global __OS_FileTimes:= New StringMap<Int>()
	#End

	Public

	' Functions (Monkey) (Public):

	' API:
	#If VIRTUALOS_MAP_ENV
		Function SetEnv:Void(Name:String, Value:String)
			__OS_Env.Set(Name, Value)

			Return
		End

		Function GetEnv:String(Name:String)
			Return __OS_Env.Get(Name)
		End
	#End

	#If VIRTUALOS_MAP_FILETIMES
		Function FileTime:Int(Path:String)
			Return __OS_FileTimes.Get(RealPath(Path)) ' FILETIME_UNAVAILABLE
		End

		Function CopyFile:Bool(Src:String, Dst:String)
			Local Result:= _CopyFile(Src, Dst)

			If (Result) Then
				__OS_SetFileTime(RealPath(Dst), FileTime(Src)) ' ; Return True
			Endif

			Return Result ' False
		End

		Function DeleteFile:Bool(Path:String)
			Local Result:= _DeleteFile(Path)

			If (Result) Then
				__OS_RemoveFileTime(RealPath(Path))
			Endif

			Return Result
		End
	#End

	Function LoadDir:String[](Path:String, Recursive:Bool, Hidden:Bool=False)
		Local Dirs:= New StringDeque()
		Local Files:= New StringDeque()

		' Add an initial value for safety.
		Dirs.PushFirst("")

		While (Not Dirs.IsEmpty)
			Local Dir:= Dirs.PopFirst()

			For Local F:= Eachin LoadDir(Path + "/" + Dir)
				If ((Not Hidden) And F.StartsWith(".")) Then
					Continue
				Endif

				If (Dir.Length > 0) Then
					F = Dir + "/" + F
				Endif

				Select (FileType(Path + "/" + F))
					Case FILETYPE_FILE
						Files.PushLast(F)
					Case FILETYPE_DIR
						If (Recursive) Then
							Dirs.PushLast(F)
						Else
							Files.PushLast(F)
						Endif
				End Select
			Next
		Wend

		Return Files.ToArray()
	End

	Function CopyDir:Bool(SourcePath:String, DestinationPath:String, Recursive:Bool=True, Hidden:Bool=False)
		If (FileType(SourcePath) <> FILETYPE_DIR) Then
			Return False
		Endif

		Local Files:= LoadDir(SourcePath)

		Select (FileType(DestinationPath))
			Case FILETYPE_NONE
				If (Not CreateDir(DestinationPath)) Then
					Return False
				Endif
			Case FILETYPE_FILE
				Return False
		End Select

		For Local F:= Eachin Files
			If ((Not Hidden) And F.StartsWith(".")) Then
				Continue
			Endif

			Local SrcP:= (SourcePath + "/" + F)
			Local DstP$= (DestinationPath + "/" + F)

			Select (FileType(SrcP))
				Case FILETYPE_FILE
					If (Not CopyFile(SrcP, DstP)) Then
						Return False
					Endif
				Case FILETYPE_DIR
					If (Recursive And (Not CopyDir(SrcP, DstP, Recursive, Hidden))) Then
						Return False
					Endif
			End Select
		Next

		' Return the default response.
		Return True
	End

	#If Not VIRTUALOS_EXTENSION_NATIVE_RECURSION
		Function DeleteDir:Bool(Path$, Recursive:Bool)
			If (Not Recursive) Then
				Return DeleteDir(Path)
			Endif

			Select (FileType(Path))
				Case FILETYPE_NONE
					Return True
				Case FILETYPE_FILE
					Return False
			End Select

			For Local F:= Eachin LoadDir(Path)
				If (F = "." Or F = "..") Then
					Continue
				Endif

				Local FPath:= (Path + "/" + F)

				If (FileType(FPath) = FILETYPE_DIR) Then
					If (Not DeleteDir(FPath, True)) Then
						Return False
					Endif
				Else
					If (Not DeleteFile(FPath)) Then
						Return False
					Endif
				Endif
			Next

			Return DeleteDir(Path)
		End
	#End

	#If Not VIRTUALOS_REAL_FILEPATH
		Function StripDir:String(Path:String)
			Local I:= Path.FindLast("/")

			If (I = STRING_INVALID_LOCATION) Then
				I = Path.FindLast( "\" )
			Endif

			If (I <> STRING_INVALID_LOCATION) Then
				Return Path[I+1..]
			Endif

			Return Path
		End

		Function ExtractDir:String(Path:String)
			Local I:= Path.FindLast("/")

			If (I = STRING_INVALID_LOCATION) Then
				I = Path.FindLast("\")
			Endif

			If (I <> STRING_INVALID_LOCATION) Then
				Return Path[..I]
			Endif

			Return ""
		End

		Function StripExt:String(Path:String)
			Local I:= Path.FindLast(".")
			Local SecondarySearchPos:= (I+1)

			If ((I <> STRING_INVALID_LOCATION) And (Path.Find("/", SecondarySearchPos) = STRING_INVALID_LOCATION) And (Path.Find("\", SecondarySearchPos)= STRING_INVALID_LOCATION)) Then
				Return Path[..I]
			Endif

			Return Path
		End

		Function ExtractExt:String(Path:String)
			Local I:= Path.FindLast(".")
			Local SecondarySearchPos:= (I+1)

			If ((I <> STRING_INVALID_LOCATION) And (Path.Find("/", SecondarySearchPos) = STRING_INVALID_LOCATION) And (Path.Find("\", SecondarySearchPos)= STRING_INVALID_LOCATION)) Then
				Return Path[SecondarySearchPos..]
			Endif

			Return ""
		End

		Function StripAll:String(Path:String)
			Return StripDir(StripExt(Path))
		End
	#End

	' Debugging related:
	#If VIRTUALOS_DEBUG
		Function ExitApp:Int(RetCode:Int)
			DebugStop()

			_ExitApp(RetCode)

			Return 0
		End
	#End

	' Extensions:
	#If VIRTUALOS_MAP_FILETIMES
		Function __OS_SetFileTime:Void(RealPath:String, Time:Int)
			__OS_FileTimes.Set(RealPath, Time)

			Return
		End

		Function __OS_RemoveFileTime:Void(RealPath:String)
			__OS_FileTimes.Remove(RealPath)

			Return
		End
	#End

	#If VIRTUALOS_EXTENSION_VFILE
		#If VIRTUALOS_EXTENSION_DL
			Function __OS_AddFileSystem:Void(URL:String)
				Local FileData:= __OS_Download(URL)

				#If Not VIRTUALOS_STANDALONE
					Local SS:= New StringStream(FileData, ,,,, True)
				#Else
					Local Buf:= New DataBuffer(FileData.Length)

					Buf.PokeString(0, FileData)

					Local SS:= New DataStream(Buf)
				#End

				__OS_ParseFileSystem(SS)

				Return
			End
		#End

		Function __OS_ParseFileSystem:Void(S:Stream) ' StringStream
			__OS_ParseFileSystem(S, New StringStack())

			Return
		End

		Function __OS_ParseFileSystem_Update_ContextRep:String(Context:Stack<String>)
			Local Rep:String

			For Local Folder:= Eachin Context
				Rep += (Folder + "/")
			Next

			Return Rep
		End

		' A simple parser that takes each line of a 'Stream', and decodes a folder structure from the input.
		' The 'OpenPrefix' argument is used to toggle adding an extra slash before generated paths.
		Function __OS_ParseFileSystem:Void(S:Stream, Context:Stack<String>, OpenPrefix:Bool=True) ' StringStream
			' Constant variable(s):
			#If Not VIRTUALOS_STANDALONE
				Const DIVIDER:= ASCII_CHARACTER_SPACE
			#Else
				Const DIVIDER:= 32
			#End

			' Local variable(s):
			Local LastMasterPath:String

			Local Cache_Context:String

			While (Not S.Eof) ' Eof()
				Local Origin:= S.Position
				Local IsFileDescriptor:Bool

				' Not the best of methods:
				Local FirstChar:= S.ReadString(1) ' S.ReadByte() ' S.ReadChar()

				Select FirstChar
					Case "~n", "~r", "~t", " " ' 10, 13, 9, 32
						Continue
					Case "!" ' 33
						IsFileDescriptor = True
					Case "{" ' 123
						Context.Push(LastMasterPath)

						' Update the current context-cache.
						Cache_Context = __OS_ParseFileSystem_Update_ContextRep(Context)

						Continue
					Case "}" ' 125
						If (Not Context.IsEmpty) Then
							Context.Pop()
						Endif

						Cache_Context = __OS_ParseFileSystem_Update_ContextRep(Context)

						Continue
					Default
						IsFileDescriptor = False

						S.Seek(Origin)
				End Select

				Local Line:= S.ReadLine() ' Line.Replace("~t", "")

				Local Entries:String[] = Line.Split(",")

				If (Not IsFileDescriptor) Then
					LastMasterPath = Entries[Entries.Length-1]
				Endif

				' Not using 'EachIn' for performance reasons:
				For Local I:= 0 Until Entries.Length
					Local E:= Entries[I]
					Local E_Length:= E.Length

					E = Cache_Context + SmartClip(E, DIVIDER, E_Length)

					' Extensions:
					Local Time_First:= E.Find("[")

					If (Time_First <> STRING_INVALID_LOCATION) Then
						Local Processed_E:= E[..Time_First]

						#If VIRTUALOS_MAP_FILETIMES
							Local Time_Second:= E.Find("]")

							If (Time_Second <> STRING_INVALID_LOCATION) Then
								Local FTime:= Int(E[Time_First+1..Time_Second])

								If (FTime <> FILETIME_UNAVAILABLE) Then
									__OS_SetFileTime(RealPath(Processed_E), FTime)
								Endif
							Endif
						#End

						E = Processed_E
					Endif

					If (IsFileDescriptor) Then
						__OS_CreateFileLink(RealPath(E)) ' __OS_DownloadFile(__OS_Storage, RealPath(E))
					Else
						CreateDir(E)
					Endif
				Next
			Wend

			Return
		End
	#Else
		Function __OS_StorageSupported:Bool()
			Return False
		End
	#End

	' Functions (Monkey) (Private):
	Private

	#If VIRTUALOS_STANDALONE
		' Ripped from 'regal.stringutil':
		Function SmartClip:String(Input:String, Symbol:Int)
			Return SmartClip(Input, Symbol, Input.Length)
		End

		Function SmartClip:String(Input:String, Symbol:Int, Length:Int)
			' Local variable(s):
			Local FinalChar:= (Length - 1)

			Local XClip:Int
			Local YClip:Int

			If (Input[0] = Symbol) Then
				XClip = 1
			Else
				XClip = 0
			Endif

			If (Input[FinalChar] = Symbol) Then
				YClip = FinalChar
			Else
				YClip = Length
			Endif

			If (XClip <> 0 Or YClip <> 0) Then
				Return Input[XClip..YClip]
			Endif

			Return Input
		End
	#End

	Public
#End

Strict

Public

' Preprocessor related:
#VIRTUALOS_FLAG_OS = True
#VIRTUALOS_STANDALONE = True

#If TARGET = "html5"
	#VIRTUALOS_JS_TARGET = True
#End

#If VIRTUALOS_JS_TARGET
	#VIRTUALOS_IMPLEMENTED = True

	#VIRTUALOS_MAP_ENV = True
	#VIRTUALOS_MAP_FILETIMES = True

	#VIRTUALOS_EXTENSION_DL = True
	#VIRTUALOS_EXTENSION_VFILE = True
	#VIRTUALOS_EXTENSION_REMOTEPATH = True
	#VIRTUALOS_EXTENSION_NATIVE_RECURSION = True

	'#VIRTUALOS_REAL_FILEPATH = True

	#If CONFIG = "debug"
		'#VIRTUALOS_DEBUG = True
	#End
#Elseif LANG = "cpp" And TARGET <> "win8" And TARGET <> "ios"
	#VIRTUALOS_REAL = True
#End

#If VIRTUALOS_IMPLEMENTED
	#If VIRTUALOS_FLAG_OS
		#BRL_OS_IMPLEMENTED = True
	#End
#End

' Imports (Public):
#If Not VIRTUALOS_IMPLEMENTED
	#If VIRTUALOS_REAL
		Import os
	#End
#Else
	Import "native/os.${LANG}"
#End

#If VIRTUALOS_REAL_FILEPATH
	Import brl.filepath
#End

' Imports (Private):
Private

#If VIRTUALOS_IMPLEMENTED
	#If Not VIRTUALOS_STANDALONE
		Import regal.stringutil

		#If VIRTUALOS_EXTENSION_DL And VIRTUALOS_EXTENSION_VFILE
			Import regal.ioutil.stringstream
		#End
	#Else
		Import brl.databuffer
		Import brl.datastream
	#End

	Import monkey.map

	#If VIRTUALOS_JS_TARGET
		'Import dom
	#End
#End

Public

#If VIRTUALOS_IMPLEMENTED
	' Constant variable(s) (Public):

	' File-types (Must be the same across borders):
	Const FILETYPE_NONE:=		0
	Const FILETYPE_FILE:=		1
	Const FILETYPE_DIR:=		2

	Const FILETIME_UNAVAILABLE:= 0

	' Constant variable(s) (Private):
	Private

	#If VIRTUALOS_STANDALONE
		Const STRING_INVALID_LOCATION:= -1
	#End

	Public

	' External bindings:
	Extern

	' Global variable(s) (External) (Private):
	Extern Private

	' If you are supporting these extensions, please provide '__OS_Storage'.
	#If VIRTUALOS_EXTENSION_VFILE
		#If VIRTUALOS_JS_TARGET
			Global __OS_Storage:StorageHandle="__os_storage"
		#End
	#End

	Extern

	' Classes (External) (Private):
	Extern Private

	' API:
	' Nothing so far.

	' Extensions:
	#If VIRTUALOS_EXTENSION_VFILE
		#If VIRTUALOS_JS_TARGET
			Class StorageHandle = "Storage" ' Extends DOMObject
				' Methods:
				' Nothing so far.
			End
		#Else
			#Error "Unable to resolve type: 'StorageHandle'"
		#End
	#End

	Extern

	' Functions (External):

	' API:
	Function HostOS:String()
	Function AppPath:String()
	Function AppArgs:String[]()
	Function RealPath:String(Path:String)
	Function FileType:Int(Path:String)
	Function FileSize:Int(Path:String)

	#If Not VIRTUALOS_MAP_FILETIMES
		Function FileTime:Int(Path:String)

		Function CopyFile:Bool(Src:String, Dst:String)
		Function DeleteFile:Bool(Path:String)
	#Else
		Function _CopyFile:Bool(Src:String, Dst:String)="CopyFile"
		Function _DeleteFile:Bool(Path:String)="DeleteFile"
	#End

	Function LoadString:String(Path:String)
	Function SaveString:Int(Str:String, Path:String)
	Function LoadDir:String[](Path:String)
	Function CreateDir:Bool(Path:String)

	Function DeleteDir:Bool(Path:String)

	#If VIRTUALOS_EXTENSION_NATIVE_RECURSION
		Function DeleteDir:Bool(Path:String, Recursive:Bool)
	#End

	Function ChangeDir:Int(Path:String)
	Function CurrentDir:String()

	#If Not VIRTUALOS_MAP_ENV
		Function SetEnv:Int(Name:String, Value:String)
		Function GetEnv:String(Name:String)
	#End

	Function Execute:Int(CMD:String)

	#If Not VIRTUALOS_DEBUG
		Function ExitApp:Int(RetCode:Int)
	#Else
		Function _ExitApp:Int(RetCode:Int)="ExitApp"
	#End

	' Extensions:
	#If VIRTUALOS_EXTENSION_REMOTEPATH
		Function __OS_ToRemotePath:String(RealPath:String)="__os_toRemotePath"
	#End

	#If VIRTUALOS_EXTENSION_DL
		Function __OS_Download:String(URL:String)="__os_download_as_string"

		' Virtual file-system (JavaScript) extensions:
		#If VIRTUALOS_EXTENSION_VFILE ' VIRTUALOS_JS_TARGET
			Function __OS_DownloadFileUsingRep:String(Storage:StorageHandle, URL:String, Rep:String)="__os_downloadFileUsingRep"
			Function __OS_DownloadFile:Void(Storage:StorageHandle, RealPath:String)="__os_downloadFile"
		#End
	#End

	#If VIRTUALOS_EXTENSION_VFILE
		Function __OS_StorageSupported:Bool()="__os_storageSupported"
		Function __OS_CreateFileLink:Void(Rep:String)="__os_createFileLink"
	#End

	Public

	' Global variable(s) (Private):
	Private

	#If VIRTUALOS_MAP_ENV
		Global __OS_Env:= New StringMap<String>()
	#End

	#If VIRTUALOS_MAP_FILETIMES
		Global __OS_FileTimes:= New StringMap<Int>()
	#End

	Public

	' Functions (Monkey) (Public):

	' API:
	#If VIRTUALOS_MAP_ENV
		Function SetEnv:Void(name:String, value:String)
			__OS_Env.Set(name, value)

			Return
		End

		Function GetEnv:String(name:String)
			Return __OS_Env.Get(name)
		End
	#End

	#If VIRTUALOS_MAP_FILETIMES
		Function FileTime:Int(Path:String)
			Return __OS_FileTimes.Get(RealPath(Path)) ' FILETIME_UNAVAILABLE
		End

		Function CopyFile:Bool(Src:String, Dst:String)
			Local Result:= _CopyFile(Src, Dst)

			If (Result) Then
				__OS_SetFileTime(RealPath(Dst), FileTime(Src)) ' ; Return True
			Endif

			Return Result ' False
		End

		Function DeleteFile:Bool(Path:String)
			Local Result:= _DeleteFile(Path)

			If (Result) Then
				__OS_RemoveFileTime(RealPath(Path))
			Endif

			Return Result
		End
	#End

	Function LoadDir:String[](Path:String, Recursive:Bool, Hidden:Bool=False)
		Local Dirs:= New StringDeque()
		Local Files:= New StringDeque()

		' Add an initial value for safety.
		Dirs.PushFirst("")

		While (Not Dirs.IsEmpty)
			Local Dir:= Dirs.PopFirst()

			For Local F:= Eachin LoadDir(Path + "/" + Dir)
				If ((Not Hidden) And F.StartsWith(".")) Then
					Continue
				Endif

				If (Dir.Length > 0) Then
					F = Dir + "/" + F
				Endif

				Select (FileType(Path + "/" + F))
					Case FILETYPE_FILE
						Files.PushLast(F)
					Case FILETYPE_DIR
						If (Recursive) Then
							Dirs.PushLast(F)
						Else
							Files.PushLast(F)
						Endif
				End Select
			Next
		Wend

		Return Files.ToArray()
	End

	Function CopyDir:Bool(SourcePath:String, DestinationPath:String, Recursive:Bool=True, Hidden:Bool=False)
		If (FileType(SourcePath) <> FILETYPE_DIR) Then
			Return False
		Endif

		Local Files:= LoadDir(SourcePath)

		Select (FileType(DestinationPath))
			Case FILETYPE_NONE
				If (Not CreateDir(DestinationPath)) Then
					Return False
				Endif
			Case FILETYPE_FILE
				Return False
		End Select

		For Local F:= Eachin Files
			If ((Not Hidden) And F.StartsWith(".")) Then
				Continue
			Endif

			Local SrcP:= (SourcePath + "/" + F)
			Local DstP$= (DestinationPath + "/" + F)

			Select (FileType(SrcP))
				Case FILETYPE_FILE
					If (Not CopyFile(SrcP, DstP)) Then
						Return False
					Endif
				Case FILETYPE_DIR
					If (Recursive And (Not CopyDir(SrcP, DstP, Recursive, Hidden))) Then
						Return False
					Endif
			End Select
		Next

		' Return the default response.
		Return True
	End

	#If Not VIRTUALOS_EXTENSION_NATIVE_RECURSION
		Function DeleteDir:Bool(Path$, Recursive:Bool)
			If (Not Recursive) Then
				Return DeleteDir(Path)
			Endif

			Select (FileType(Path))
				Case FILETYPE_NONE
					Return True
				Case FILETYPE_FILE
					Return False
			End Select

			For Local F:= Eachin LoadDir(Path)
				If (F = "." Or F = "..") Then
					Continue
				Endif

				Local FPath:= (Path + "/" + F)

				If (FileType(FPath) = FILETYPE_DIR) Then
					If (Not DeleteDir(FPath, True)) Then
						Return False
					Endif
				Else
					If (Not DeleteFile(FPath)) Then
						Return False
					Endif
				Endif
			Next

			Return DeleteDir(Path)
		End
	#End

	#If Not VIRTUALOS_REAL_FILEPATH
		Function StripDir:String(Path:String)
			Local I:= Path.FindLast("/")

			If (I = STRING_INVALID_LOCATION) Then
				I = Path.FindLast( "\" )
			Endif

			If (I <> STRING_INVALID_LOCATION) Then
				Return Path[I+1..]
			Endif

			Return Path
		End

		Function ExtractDir:String(Path:String)
			Local I:= Path.FindLast("/")

			If (I = STRING_INVALID_LOCATION) Then
				I = Path.FindLast("\")
			Endif

			If (I <> STRING_INVALID_LOCATION) Then
				Return Path[..I]
			Endif

			Return ""
		End

		Function StripExt:String(Path:String)
			Local I:= Path.FindLast(".")
			Local SecondarySearchPos:= (I+1)

			If ((I <> STRING_INVALID_LOCATION) And (Path.Find("/", SecondarySearchPos) = STRING_INVALID_LOCATION) And (Path.Find("\", SecondarySearchPos)= STRING_INVALID_LOCATION)) Then
				Return Path[..I]
			Endif

			Return Path
		End

		Function ExtractExt:String(Path:String)
			Local I:= Path.FindLast(".")
			Local SecondarySearchPos:= (I+1)

			If ((I <> STRING_INVALID_LOCATION) And (Path.Find("/", SecondarySearchPos) = STRING_INVALID_LOCATION) And (Path.Find("\", SecondarySearchPos)= STRING_INVALID_LOCATION)) Then
				Return Path[SecondarySearchPos..]
			Endif

			Return ""
		End

		Function StripAll:String(Path:String)
			Return StripDir(StripExt(Path))
		End
	#End

	' Debugging related:
	#If VIRTUALOS_DEBUG
		Function ExitApp:Int(RetCode:Int)
			DebugStop()

			_ExitApp(RetCode)

			Return 0
		End
	#End

	' Extensions:
	#If VIRTUALOS_MAP_FILETIMES
		Function __OS_SetFileTime:Void(RealPath:String, Time:Int)
			__OS_FileTimes.Set(RealPath, Time)

			Return
		End

		Function __OS_RemoveFileTime:Void(RealPath:String)
			__OS_FileTimes.Remove(RealPath)

			Return
		End
	#End

	#If VIRTUALOS_EXTENSION_VFILE
		#If VIRTUALOS_EXTENSION_DL
			Function __OS_AddFileSystem:Void(URL:String)
				Local FileData:= __OS_Download(URL)

				#If Not VIRTUALOS_STANDALONE
					Local SS:= New StringStream(FileData, ,,,, True)
				#Else
					Local Buf:= New DataBuffer(FileData.Length)

					Buf.PokeString(0, FileData)

					Local SS:= New DataStream(Buf)
				#End

				__OS_ParseFileSystem(SS)

				Return
			End
		#End

		Function __OS_ParseFileSystem:Void(S:Stream) ' StringStream
			__OS_ParseFileSystem(S, New StringStack())

			Return
		End

		Function __OS_ParseFileSystem_Update_ContextRep:String(Context:Stack<String>)
			Local Rep:String

			For Local Folder:= Eachin Context
				Rep += (Folder + "/")
			Next

			Return Rep
		End

		' A simple parser that takes each line of a 'Stream', and decodes a folder structure from the input.
		' The 'OpenPrefix' argument is used to toggle adding an extra slash before generated paths.
		Function __OS_ParseFileSystem:Void(S:Stream, Context:Stack<String>, OpenPrefix:Bool=True) ' StringStream
			' Constant variable(s):
			#If Not VIRTUALOS_STANDALONE
				Const DIVIDER:= ASCII_CHARACTER_SPACE
			#Else
				Const DIVIDER:= 32
			#End

			' Local variable(s):
			Local LastMasterPath:String

			Local Cache_Context:String

			While (Not S.Eof) ' Eof()
				Local Origin:= S.Position
				Local IsFileDescriptor:Bool

				' Not the best of methods:
				Local FirstChar:= S.ReadString(1) ' S.ReadByte() ' S.ReadChar()

				Select FirstChar
					Case "~n", "~r", "~t", " " ' 10, 13, 9, 32
						Continue
					Case "!" ' 33
						IsFileDescriptor = True
					Case "{" ' 123
						Context.Push(LastMasterPath)

						' Update the current context-cache.
						Cache_Context = __OS_ParseFileSystem_Update_ContextRep(Context)

						Continue
					Case "}" ' 125
						If (Not Context.IsEmpty) Then
							Context.Pop()
						Endif

						Cache_Context = __OS_ParseFileSystem_Update_ContextRep(Context)

						Continue
					Default
						IsFileDescriptor = False

						S.Seek(Origin)
				End Select

				Local Line:= S.ReadLine() ' Line.Replace("~t", "")

				Local Entries:String[] = Line.Split(",")

				If (Not IsFileDescriptor) Then
					LastMasterPath = Entries[Entries.Length-1]
				Endif

				' Not using 'EachIn' for performance reasons:
				For Local I:= 0 Until Entries.Length
					Local E:= Entries[I]
					Local E_Length:= E.Length

					E = Cache_Context + SmartClip(E, DIVIDER, E_Length)

					' Extensions:
					Local Time_First:= E.Find("[")

					If (Time_First <> STRING_INVALID_LOCATION) Then
						Local Processed_E:= E[..Time_First]

						#If VIRTUALOS_MAP_FILETIMES
							Local Time_Second:= E.Find("]")

							If (Time_Second <> STRING_INVALID_LOCATION) Then
								Local FTime:= Int(E[Time_First+1..Time_Second])

								If (FTime <> FILETIME_UNAVAILABLE) Then
									__OS_SetFileTime(RealPath(Processed_E), FTime)
								Endif
							Endif
						#End

						E = Processed_E
					Endif

					If (IsFileDescriptor) Then
						__OS_CreateFileLink(RealPath(E)) ' __OS_DownloadFile(__OS_Storage, RealPath(E))
					Else
						CreateDir(E)
					Endif
				Next
			Wend

			Return
		End
	#Else
		Function __OS_StorageSupported:Bool()
			Return False
		End
	#End

	' Functions (Monkey) (Private):
	Private

	#If VIRTUALOS_STANDALONE
		' Ripped from 'regal.stringutil':
		Function SmartClip:String(Input:String, Symbol:Int)
			Return SmartClip(Input, Symbol, Input.Length)
		End

		Function SmartClip:String(Input:String, Symbol:Int, Length:Int)
			' Local variable(s):
			Local FinalChar:= (Length - 1)

			Local XClip:Int
			Local YClip:Int

			If (Input[0] = Symbol) Then
				XClip = 1
			Else
				XClip = 0
			Endif

			If (Input[FinalChar] = Symbol) Then
				YClip = FinalChar
			Else
				YClip = Length
			Endif

			If (XClip <> 0 Or YClip <> 0) Then
				Return Input[XClip..YClip]
			Endif

			Return Input
		End
	#End

	Public
#End


Removed confusing method ;)

Removed confusing method ;)

Strict

import diddy
Import mojo

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

Global gameScreen:GameScreen
Global titleScreen:TitleScreen

Class MyGame Extends DiddyApp

	Method OnCreate:Int()
		Super.OnCreate()

		LoadImages()

		gameScreen = new GameScreen
		titleScreen = new TitleScreen

		titleScreen.PreStart()

		Return 0
	End

	'***********************
	'* Load Images
	'***********************
	Function LoadImages:Void()
		' create tmpImage for animations
		local tmpImage:Image

		images.LoadAnim("Ship1.png", 64, 64, 7, tmpImage)
	End
End

Class TitleScreen Extends Screen

	Method New()
		name = "Title"
	End

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

	Method Render:Void()
		Cls
		DrawText "TITLE SCREEN!", SCREEN_WIDTH2, SCREEN_HEIGHT2, 0.5, 0.5
		DrawText "Click to Play!", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 20, 0.5, 0.5
	End

	Method Update:Void()
		if KeyHit(KEY_SPACE) or MouseHit(0)
			game.screenFade.Start(50, true)
			game.nextScreen = gameScreen
		End
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = game.exitScreen
		End
	End
End

Class GameScreen Extends Screen
	Field player:Player

	Method New()
		name = "Game"
	End

	Method Start:Void()
		game.screenFade.Start(50, false)
		player = new Player(game.images.Find("Ship1"), SCREEN_WIDTH2, SCREEN_HEIGHT2)
	End

	Method Render:Void()
		Cls
		player.Draw()
		DrawHUD()
	End

	Method Update:Void()
		player.UpdateAnimation()
		player.Update()

		if KeyHit(KEY_1)
			player.SetFrame(0, 6, 100, false, false)
		End
		if KeyHit(KEY_2)
			player.SetFrame(0, 6, 100, false, true)
		End
		if KeyHit(KEY_3)
			player.SetFrame(0, 6, 100, true, false)
		End
		if KeyHit(KEY_4)
			player.SetFrame(0, 6, 100, true, true)
		End

		if KeyHit(KEY_Q)
			player.SetFrame(6, 0, 100, false, false)
		End
		if KeyHit(KEY_W)
			player.SetFrame(6, 0, 100, false, true)
		End
		if KeyHit(KEY_E)
			player.SetFrame(6, 0, 100, true, false)
		End
		if KeyHit(KEY_R)
			player.SetFrame(6, 0, 100, true, true)
		End

		if KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = titleScreen
		End
	End

	Method DrawHUD:Void()
		DrawText("frame = "+player.frame, 10, 0)

		If player.pingPong
			DrawText("pingPong = true" , 10, 30)
		else
			DrawText("pingPong = false" , 10, 30)
		End
		If player.loop
			DrawText("loop = true" , 10, 60)
		Else
			DrawText("loop = false" , 10, 60)
		End
		DrawText("startFrame = "+player.frameStart, 10, 90)
		DrawText("startEnd = "+player.frameEnd, 10, 120)

		DrawText("Press 1 - 4 to change animation", 10, 150)

		DrawText("Press <ESC> to return to the Title Screen", 10, SCREEN_HEIGHT-12)

		FPSCounter.Draw(SCREEN_WIDTH, SCREEN_HEIGHT  - 12, 1)

	End
End

Class Player Extends Sprite
    Field vx:Float, vy:Float
    Field ax:Float, ay:Float
    Field direction:Float

	Method New(gi:GameImage, x#, y#)
		Super.New(gi, x, y)
		direction = -90
		frame = 3
	End

	Method Update:Void()
		Local Acceleration:Float

		If KeyDown(KEY_UP)
			Acceleration=.2
		else If KeyDown(KEY_DOWN)
			Acceleration=-.2
		Else
			Acceleration=0
		End If
		If KeyDown(KEY_LEFT)
			direction-=2
			self.rotation+=2
		End If
		If KeyDown(KEY_RIGHT)
			direction+=2
			self.rotation-=2
		End If

		Local ax:Float = Acceleration * Cos(direction)
		Local ay:Float = Acceleration * Sin(direction)

		vx+=ax
		vy+=ay
		x+=vx * dt.delta
		y+=vy * dt.delta

		If x < 0 x = SCREEN_WIDTH
		If x > SCREEN_WIDTH x = 0
		If y < 0 y = SCREEN_HEIGHT
		If y > SCREEN_HEIGHT y = 0
	End

End

Strict

import diddy
Import mojo

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

Global gameScreen:GameScreen
Global titleScreen:TitleScreen

Class MyGame Extends DiddyApp

	Method OnCreate:Int()
		Super.OnCreate()

		LoadImages()

		gameScreen = new GameScreen
		titleScreen = new TitleScreen

		titleScreen.PreStart()

		Return 0
	End

	'***********************
	'* Load Images
	'***********************
	Function LoadImages:Void()
		' create tmpImage for animations
		local tmpImage:Image

		images.LoadAnim("Ship1.png", 64, 64, 7, tmpImage)
	End
End

Class TitleScreen Extends Screen

	Method New()
		name = "Title"
	End

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

	Method Render:Void()
		Cls
		DrawText "TITLE SCREEN!", SCREEN_WIDTH2, SCREEN_HEIGHT2, 0.5, 0.5
		DrawText "Click to Play!", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 20, 0.5, 0.5
	End

	Method Update:Void()
		if KeyHit(KEY_SPACE) or MouseHit(0)
			game.screenFade.Start(50, true)
			game.nextScreen = gameScreen
		End
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = game.exitScreen
		End
	End
End

Class GameScreen Extends Screen
	Field player:Player

	Method New()
		name = "Game"
	End

	Method Start:Void()
		game.screenFade.Start(50, false)
		player = new Player(game.images.Find("Ship1"), SCREEN_WIDTH2, SCREEN_HEIGHT2)
	End

	Method Render:Void()
		Cls
		player.Draw()
		DrawHUD()
	End

	Method Update:Void()
		player.UpdateAnimation()
		player.Update()

		if KeyHit(KEY_1)
			player.SetFrame(0, 6, 100, false, false)
		End
		if KeyHit(KEY_2)
			player.SetFrame(0, 6, 100, false, true)
		End
		if KeyHit(KEY_3)
			player.SetFrame(0, 6, 100, true, false)
		End
		if KeyHit(KEY_4)
			player.SetFrame(0, 6, 100, true, true)
		End

		if KeyHit(KEY_Q)
			player.SetFrame(6, 0, 100, false, false)
		End
		if KeyHit(KEY_W)
			player.SetFrame(6, 0, 100, false, true)
		End
		if KeyHit(KEY_E)
			player.SetFrame(6, 0, 100, true, false)
		End
		if KeyHit(KEY_R)
			player.SetFrame(6, 0, 100, true, true)
		End

		if KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = titleScreen
		End
	End

	Method DrawHUD:Void()
		DrawText("frame = "+player.frame, 10, 0)

		If player.pingPong
			DrawText("pingPong = true" , 10, 30)
		else
			DrawText("pingPong = false" , 10, 30)
		End
		If player.loop
			DrawText("loop = true" , 10, 60)
		Else
			DrawText("loop = false" , 10, 60)
		End
		DrawText("startFrame = "+player.frameStart, 10, 90)
		DrawText("startEnd = "+player.frameEnd, 10, 120)

		DrawText("Press 1 - 4 to change animation", 10, 150)

		DrawText("Press <ESC> to return to the Title Screen", 10, SCREEN_HEIGHT-12)

		FPSCounter.Draw(SCREEN_WIDTH, SCREEN_HEIGHT  - 12, 1)

	End
End

Class Player Extends Sprite
    Field vx:Float, vy:Float
    Field ax:Float, ay:Float
    Field direction:Float

	Method New(gi:GameImage, x#, y#)
		Super.New(gi, x, y)
		direction = -90
		frame = 3
	End

	Method Update:Void()
		Local Acceleration:Float

		If KeyDown(KEY_UP)
			Acceleration=.2
		else If KeyDown(KEY_DOWN)
			Acceleration=-.2
		Else
			Acceleration=0
		End If
		If KeyDown(KEY_LEFT)
			direction-=2
			self.rotation+=2
		End If
		If KeyDown(KEY_RIGHT)
			direction+=2
			self.rotation-=2
		End If

		Local ax:Float = Acceleration * Cos(direction)
		Local ay:Float = Acceleration * Sin(direction)

		vx+=ax
		vy+=ay
		x+=vx * dt.delta
		y+=vy * dt.delta

		If x < 0 x = SCREEN_WIDTH
		If x > SCREEN_WIDTH x = 0
		If y < 0 y = SCREEN_HEIGHT
		If y > SCREEN_HEIGHT y = 0
	End

	Method draw:Void()
		Super.draw()
	End

End


Router calls OnEnter now AFTER OnCreate ...

Strict

Private

Import director
Import directorevents
Import keyevent
Import routerevents
Import touchevent

Public

Class Router Implements DirectorEvents
    Private

    Field handlers:StringMap<DirectorEvents> = New StringMap<DirectorEvents>()
    Field routers:StringMap<RouterEvents> = New StringMap<RouterEvents>()
    Field created:List<String> = New List<String>()
    Field _current:DirectorEvents
    Field _currentName:String
    Field _previous:DirectorEvents
    Field _previousName:String
    Field director:Director

    Public

    Method Add:Void(name:String, handler:DirectorEvents)
        If handlers.Contains(name)
            Error("Router already contains a handler named " + name)
        End

        handlers.Set(name, handler)
        routers.Set(name, RouterEvents(handler))
    End

    Method Get:DirectorEvents(name:String)
        If Not handlers.Contains(name)
            Error("Router has no handler named " + name)
        End

        Return handlers.Get(name)
    End

    Method Goto:Void(name:String)
        If name = _currentName Then Return

        _previous = _current
        _previousName = _currentName

        _current = Get(name)
        _currentName = name
        DispatchOnCreate()

        Local tmpRouter:RouterEvents = routers.Get(_previousName)
        If tmpRouter Then tmpRouter.OnLeave()

        tmpRouter = routers.Get(_currentName)
        If tmpRouter Then tmpRouter.OnEnter()
    End

    Method OnCreate:Void(director:Director)
        Self.director = director
        DispatchOnCreate()
    End

    Method OnLoading:Void()
        If _current Then _current.OnLoading()
    End

    Method OnUpdate:Void(delta:Float, frameTime:Float)
        If _current Then _current.OnUpdate(delta, frameTime)
    End

    Method OnRender:Void()
        If _current Then _current.OnRender()
    End

    Method OnSuspend:Void()
        If _current Then _current.OnSuspend()
    End

    Method OnResume:Void(delta:Int)
        If _current Then _current.OnResume(delta)
    End

    Method OnKeyDown:Void(event:KeyEvent)
        If _current Then _current.OnKeyDown(event)
    End

    Method OnKeyPress:Void(event:KeyEvent)
        If _current Then _current.OnKeyPress(event)
    End

    Method OnKeyUp:Void(event:KeyEvent)
        If _current Then _current.OnKeyUp(event)
    End

    Method OnTouchDown:Void(event:TouchEvent)
        If _current Then _current.OnTouchDown(event)
    End

    Method OnTouchMove:Void(event:TouchEvent)
        If _current Then _current.OnTouchMove(event)
    End

    Method OnTouchUp:Void(event:TouchEvent)
        If _current Then _current.OnTouchUp(event)
    End

    Method current:DirectorEvents() Property
        Return _current
    End

    Method currentName:String() Property
        Return _currentName
    End

    Method previous:DirectorEvents() Property
        Return _previous
    End

    Method previousName:String() Property
        Return _previousName
    End

    Private

    Method DispatchOnCreate:Void()
        If Not director Then Return
        If Not _current Then Return
        If created.Contains(_currentName) Then Return

        _current.OnCreate(director)
        created.AddLast(_currentName)
    End
End

Strict

Private

Import director
Import directorevents
Import keyevent
Import routerevents
Import touchevent

Public

Class Router Implements DirectorEvents
    Private

    Field handlers:StringMap<DirectorEvents> = New StringMap<DirectorEvents>()
    Field routers:StringMap<RouterEvents> = New StringMap<RouterEvents>()
    Field created:List<String> = New List<String>()
    Field _current:DirectorEvents
    Field _currentName:String
    Field _previous:DirectorEvents
    Field _previousName:String
    Field director:Director

    Public

    Method Add:Void(name:String, handler:DirectorEvents)
        If handlers.Contains(name)
            Error("Router already contains a handler named " + name)
        End

        handlers.Set(name, handler)
        routers.Set(name, RouterEvents(handler))
    End

    Method Get:DirectorEvents(name:String)
        If Not handlers.Contains(name)
            Error("Router has no handler named " + name)
        End

        Return handlers.Get(name)
    End

    Method Goto:Void(name:String)
        If name = _currentName Then Return

        _previous = _current
        _previousName = _currentName

        Local tmpRouter:RouterEvents = routers.Get(name)
        If tmpRouter Then tmpRouter.OnLeave()

        _current = Get(name)
        _currentName = name
        DispatchOnCreate()

        tmpRouter = routers.Get(name)
        If tmpRouter Then tmpRouter.OnEnter()
    End

    Method OnCreate:Void(director:Director)
        Self.director = director
        DispatchOnCreate()
    End

    Method OnLoading:Void()
        If _current Then _current.OnLoading()
    End

    Method OnUpdate:Void(delta:Float, frameTime:Float)
        If _current Then _current.OnUpdate(delta, frameTime)
    End

    Method OnRender:Void()
        If _current Then _current.OnRender()
    End

    Method OnSuspend:Void()
        If _current Then _current.OnSuspend()
    End

    Method OnResume:Void(delta:Int)
        If _current Then _current.OnResume(delta)
    End

    Method OnKeyDown:Void(event:KeyEvent)
        If _current Then _current.OnKeyDown(event)
    End

    Method OnKeyPress:Void(event:KeyEvent)
        If _current Then _current.OnKeyPress(event)
    End

    Method OnKeyUp:Void(event:KeyEvent)
        If _current Then _current.OnKeyUp(event)
    End

    Method OnTouchDown:Void(event:TouchEvent)
        If _current Then _current.OnTouchDown(event)
    End

    Method OnTouchMove:Void(event:TouchEvent)
        If _current Then _current.OnTouchMove(event)
    End

    Method OnTouchUp:Void(event:TouchEvent)
        If _current Then _current.OnTouchUp(event)
    End

    Method current:DirectorEvents() Property
        Return _current
    End

    Method currentName:String() Property
        Return _currentName
    End

    Method previous:DirectorEvents() Property
        Return _previous
    End

    Method previousName:String() Property
        Return _previousName
    End

    Private

    Method DispatchOnCreate:Void()
        If Not director Then Return
        If Not _current Then Return
        If created.Contains(_currentName) Then Return

        _current.OnCreate(director)
        created.AddLast(_currentName)
    End
End