Added scrolling variables

Added scrolling variables

Strict

Import mojo
Import functions
Import collections

'Device width and height
Global DEVICE_WIDTH:Int
Global DEVICE_HEIGHT:Int

' Screen width and height
Global SCREEN_WIDTH:Float
Global SCREEN_HEIGHT:Float

' Half of SCREEN_WIDTH and HEIGHT
Global SCREEN_WIDTH2:Float
Global SCREEN_HEIGHT2:Float

' Used for Virtual Res
Global SCREENX_RATIO:Float = 1
Global SCREENY_RATIO:Float = 1

' THE GAME!!
Global game:DiddyApp

' Used for delta timing movement
Global dt:DeltaTimer

Class DiddyApp Extends App

	Field debugKeyOn:Bool = False
	Field debugOn:Bool = False
	Field drawFPSOn:Bool = False
	Field debugKey:Int = KEY_F1

	Field virtualResOn:Bool = True

	Field FPS% = 60

	' current Screen
	Field currentScreen:Screen
	' next Screen
	Field nextScreen:Screen
	' exit Screen
	Field exitScreen:ExitScreen = new ExitScreen()
	' used for fading
	Field screenFade:ScreenFade = New ScreenFade
	' scroll
	field scrollX:Float
	field scrollY:Float

	' mouse
	Field mouseX:Int, mouseY:Int
	Field mouseHit:Int

	' Store the images here
	Field images:ImageBank = New ImageBank
	' Store the sounds here
	Field sounds:SoundBank = New SoundBank
	' volume control
	Field musicFile:String = ""
	Field soundVolume:Int = 100
	Field musicVolume:Int = 100
	Field mojoMusicVolume:Float = 1.0
	Field musicOkay:Int

	Field clickSound:GameSound

	Method OnCreate:Int()
		' Store the device width and height
		DEVICE_WIDTH = DeviceWidth()
		DEVICE_HEIGHT = DeviceHeight()

		SetScreenSize(DEVICE_WIDTH, DEVICE_HEIGHT)

		' set the mouse x,y
		mouseX = MouseX() / SCREENX_RATIO
		mouseY = MouseY() / SCREENY_RATIO

		' Set the Random seed
		Seed = RealMillisecs()
		' Create the delta timer
		dt = New DeltaTimer(FPS)
		' Set the update rate
		SetUpdateRate FPS

		'create all the particles
		Particle.Cache()

		Return 0
	End

	Method SetScreenSize:Void(w:Float, h:Float)
		SCREEN_WIDTH = w
		SCREEN_HEIGHT = h
		SCREEN_WIDTH2 = SCREEN_WIDTH / 2
		SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2

		SCREENX_RATIO = DEVICE_WIDTH/SCREEN_WIDTH
		SCREENY_RATIO = DEVICE_HEIGHT/SCREEN_HEIGHT

		If SCREENX_RATIO <> 1 Or SCREENY_RATIO <> 1
			virtualResOn = True
		End
	End

	Method OnRender:Int()
		FPSCounter.Update()
		If virtualResOn
			PushMatrix
			Scale SCREENX_RATIO, SCREENY_RATIO
		End
			currentScreen.Render()
		if virtualResOn
			PopMatrix
		End
		currentScreen.ExtraRender()
		If screenFade.active then screenFade.Render()
		currentScreen.DebugRender()
		If debugOn
			DrawDebug()
		End
		If drawFPSOn
			DrawFPS()
		End
		Return 0
	End

	Method OnUpdate:Int()
		dt.UpdateDelta()

		If debugKeyOn
			If KeyHit(debugKey)
				debugOn = Not debugOn
			End
		End

		mouseX = MouseX() / SCREENX_RATIO
		mouseY = MouseY() / SCREENY_RATIO
		mouseHit = MouseHit()

		If screenFade.active then screenFade.Update()
		currentScreen.Update()

		Return 0
	End

	Method DrawDebug:Void()
		SetColor 255, 255, 255
		FPSCounter.Draw(0,0)
		Local y:Int = 10
		Local gap:Int = 10
		DrawText "Screen             = "+currentScreen.name, 0, y
		y += gap
		DrawText "Delta              = "+FormatNumber(dt.delta, 2) , 0, y
		y += gap
		DrawText "Frame Time         = "+dt.frametime , 0, y
		y += gap
		DrawText "Screen Width       = "+SCREEN_WIDTH, 0, y
		y += gap
		DrawText "Screen Height      = "+SCREEN_HEIGHT, 0, y
		y += gap
		DrawText "VMouseX            = "+Self.mouseX, 0, y
		y += gap
		DrawText "VMouseY            = "+Self.mouseY, 0, y
		y += gap
		DrawText "MouseX             = "+MouseX(), 0, y
		y += gap
		DrawText "MouseY             = "+MouseY(), 0, y
		y += gap
		DrawText "Music File         = "+musicFile, 0, y
		y += gap
		DrawText "MusicOkay          = "+musicOkay, 0, y
		y += gap
		DrawText "Music State        = "+MusicState(), 0, y
		y += gap
		DrawText "Music Volume       = "+Self.musicVolume, 0, y
		y += gap
		DrawText "Mojo Music Volume  = "+Self.mojoMusicVolume, 0, y
		y += gap
		DrawText "Sound Volume       = "+Self.soundVolume, 0, y
		y += gap
		DrawText "Sound Channel      = "+SoundPlayer.channel, 0, y
		y += gap
	End

	Method DrawFPS:Void()
		DrawText FPSCounter.totalFPS, 0, 0
	End

	Method MusicPlay:Void(file:String, flags:Int=1)
		musicFile = file

		musicOkay = PlayMusic("music/"+musicFile, flags)
		if musicOkay = -1
			Print "Error Playing Music - Music must be in the data\music folder"
		End
	End

	Method MusicSetVolume:Void(volume:Int)
		If volume < 0 Then volume = 0
		If volume > 100 Then volume = 100
		Self.musicVolume = volume
		SetMojoMusicVolume(musicVolume/100.0)
	End

	Method SetMojoMusicVolume:Void(volume:Float)
		If volume < 0 Then volume = 0
		If volume > 1 Then volume = 1
		mojoMusicVolume = volume
		SetMusicVolume(mojoMusicVolume)
	End

	Method SoundSetVolume:Void(volume:Int)
		If volume < 0 Then volume = 0
		If volume > 100 Then volume = 100
		Self.soundVolume = volume
		For Local i% = 0 To SoundPlayer.MAX_CHANNELS
			SetChannelVolume(i, game.soundVolume / 100.0)
		Next
	End

	Method CalcAnimLength:Float(ms:Int)
		Return ms / (1000.0 / FPS)
	End
End

Class ScreenFade
	Field fadeTime:Float
	Field fadeOut:Bool
	Field ratio:Float = 0
	Field active:Bool
	Field counter:Float
	Field fadeMusic:Bool
	Field fadeSound:Bool

	Method Start:Void(fadeTime:Float, fadeOut:Bool, fadeSound:Bool = False, fadeMusic:Bool = False)
		If active Then Return
		active = True
		Self.fadeTime = fadeTime
		Self.fadeOut = fadeOut
		Self.fadeMusic = fadeMusic
		Self.fadeSound = fadeSound
		If fadeOut Then
			ratio = 1
		Else
			ratio = 0
			' set the music volume to zero if fading in the music
			if Self.fadeMusic
				game.SetMojoMusicVolume(0)
			End
		End
		counter = 0
	End

	Method Update:Void()
		If Not active Return
		counter += dt.delta
		CalcRatio()
		If fadeSound Then
			For Local i% = 0 To SoundPlayer.MAX_CHANNELS
				SetChannelVolume(i, (ratio) * (game.soundVolume / 100.0))
			Next
		End
		If fadeMusic Then
			game.SetMojoMusicVolume((ratio) * (game.musicVolume / 100.0))
		End
		if counter > fadeTime
			active = false
			if fadeOut
				game.currentScreen.PostFadeOut()
			else
				game.currentScreen.PostFadeIn()
			End
		End
	End

	Method CalcRatio:Void()
		ratio = counter/fadeTime
		If ratio < 0
			ratio = 0
			if fadeMusic
				game.SetMojoMusicVolume(0)
			End
		End
		If ratio > 1
			ratio = 1
			if fadeMusic
				game.SetMojoMusicVolume(game.musicVolume / 100.0)
			End
		End
		If fadeOut Then
			ratio = 1 - ratio
		End
	End

	Method Render:Void()
		If Not active Return

		SetAlpha 1 - ratio
		SetColor 0, 0, 0
		DrawRect 0, 0, DEVICE_WIDTH, DEVICE_HEIGHT
		SetAlpha 1
		SetColor 255, 255, 255
	End

End

Class ExitScreen Extends Screen
	Method New()
		name = "exit"
	End

	Method Start:Void()
		ExitApp()
	End

	Method Render:Void()
	End

	Method Update:Void()
	End
End

Class Screen Abstract
	Field name$ = ""

	Method PreStart:Void()
		game.currentScreen = self
		Start()
	End

	Method Start:Void() Abstract

	Method Render:Void() Abstract

	Method Update:Void() Abstract

	Method PostFadeOut:Void()
		game.nextScreen.PreStart()
	End

	Method PostFadeIn:Void()
	End

	Method ExtraRender:Void()
	End

	Method DebugRender:Void()
	End

End Class

Class FPSCounter Abstract
	Global fpsCount:Int
	Global startTime:Int
	Global totalFPS:Int

	Function Update:Void()
		If Millisecs() - startTime >= 1000
			totalFPS = fpsCount
			fpsCount = 0
			startTime = Millisecs()
		Else
			fpsCount+=1
		End
	End

	Function Draw:Void(x% = 0, y% = 0, ax# = 0, ay# = 0)
		DrawText("FPS: " + totalFPS, x, y, ax, ay)
	End
End

' From James Boyd
Class DeltaTimer
	Field targetfps:Float = 60
	Field currentticks:Float
	Field lastticks:Float
	Field frametime:Float
	Field delta:Float

	Method New (fps:Float)
		targetfps = fps
		lastticks = Millisecs()
	End

	Method UpdateDelta:Void()
		currentticks = Millisecs()
		frametime = currentticks - lastticks
		delta = frametime / (1000.0 / targetfps)
		lastticks = currentticks
	End
End

Class ImageBank Extends StringMap<GameImage>

	Field path$ = "graphics/"

	Method Load:GameImage(name:String, nameoverride:String = "", midhandle:Bool=true, ignoreCache:Bool=False)
		' check if we already have the image in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old image if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).image.Discard()

		Local i:GameImage = New GameImage
		i.Load(path + name, midhandle)
		i.name = storeKey
		Self.Set(i.name, i)
		Return i
	End

	Method LoadAnim:GameImage(name:String, w%, h%, total%, tmpImage:Image, midhandle:Bool=true, ignoreCache:Bool=False, nameoverride:String = "")
		' check if we already have the image in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old image if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).image.Discard()

		Local i:GameImage = New GameImage
		i.LoadAnim(path + name, w, h, total, tmpImage, midhandle)
		i.name = storeKey
		Self.Set(i.name, i)
		Return i
	End

	Method LoadTileset:GameImage(name:String, tileWidth%, tileHeight%, tileMargin% = 0, tileSpacing% = 0, nameoverride:String = "", midhandle:Bool=False, ignoreCache:Bool=False)
		' check if we already have the image in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old image if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).image.Discard()

		' load the new one
		Local i:GameImage = New GameImage
		i.LoadTileset(path + name, tileWidth, tileHeight, tileMargin, tileSpacing, midhandle)
		i.name = storeKey
		Self.Set(i.name, i)
		Return i
	End

	Method Find:GameImage(name:String)
		name = name.ToUpper()

		' debug: print all keys in the map
	'	For Local key:String = EachIn self.Keys()
	'		Print key + " is stored in the map."
	'	Next

		Local i:GameImage = Self.Get(name)
		AssertNotNull(i, "Image '" + name + "' not found in the ImageBank")
		Return i
	End

	Method PreCache:Void()
		Local gi:GameImage
		For Local key:String = EachIn self.Keys()
			gi = Self.Get(key)
			gi.PreCache()
		Next
	End

End

Class GameImage
	Field name:String
	Field image:Image
	Field w:Int
	Field h:Int
	Field w2:Float
	Field h2:Float
	Field midhandled:Int = 0

	Field leftMargin:Int = 0
	Field rightMargin:Int = 0
	Field topMargin:Int = 0
	Field bottomMargin:Int = 0

	Field tileWidth:Int, tileHeight:Int
	Field tileCountX:Int, tileCountY:Int
	Field tileCount:Int
	Field tileSpacing:Int, tileMargin:Int

	Method Load:Void(file$, midhandle:bool=true)
		name = StripAll(file.ToUpper())
		image = LoadBitmap(file)
		CalcSize()
		MidHandle(midhandle)
	End

	Method LoadAnim:Void(file:String, w:Int, h:Int, total%, tmpImage:Image, midhandle:Bool=true)
		name = StripAll(file.ToUpper())
		image = LoadAnimBitmap(file, w, h, total, tmpImage)
		CalcSize()
		MidHandle(midhandle)
	End

	Method LoadTileset:Void(file:String, tileWidth:Int, tileHeight:Int, tileMargin:Int = 0, tileSpacing:Int = 0, midhandle:Bool=False)
		Load(file, midhandle)
		Self.tileWidth = tileWidth
		Self.tileHeight = tileHeight
		Self.tileMargin = tileMargin
		Self.tileSpacing = tileSpacing
		tileCountX = (w - tileMargin) / (tileWidth + tileSpacing)
		tileCountY = (h - tileMargin) / (tileHeight + tileSpacing)
		tileCount = tileCountX * tileCountY
	End

	Method CalcSize:Void()
		If image <> Null Then
			w = image.Width()
			h = image.Height()
			w2 = w/2
			h2 = h/2
		End
	End

	Method MidHandle:Void(On:Bool)
		If On Then
			image.SetHandle(w2, h2)
			midhandled=1
		Else
			image.SetHandle(0, 0)
			midhandled=0
		End
	End

	Method Draw:Void(x:Float, y:Float, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1, frame:Int = 0)
		DrawImage(self.image, x, y, rotation, scaleX, scaleY, frame)
	End

	Method DrawSubImage:Void(destX:Float, destY:Float, srcX:Int, srcY:Int, srcWidth:Int, srcHeight:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1, frame:Int = 0)
		DrawImageRect(Self.image, destX, destY, srcX, srcY, srcWidth, srcHeight, rotation, scaleX, scaleY, frame)
	End

	Method DrawTile:Void(x:Float, y:Float, tile:Int = 0, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		Local srcX% = tileMargin + (tileWidth + tileSpacing) * (tile Mod tileCountX)
		Local srcY% = tileMargin + (tileHeight + tileSpacing) * (tile / tileCountX)
		DrawImageRect(Self.image, x, y, srcX, srcY, tileWidth, tileHeight, rotation, scaleX, scaleY)
	End

	Method DrawGrid:Void(x:Float, y:Float, rw:Float, rh:Float, frame:Int = 0)
		' draw top left corner
		DrawImageRect(self.image, x, y, 0, 0, leftMargin, topMargin, frame)
		' draw top right corner
		DrawImageRect(self.image, x+rw-rightMargin, y, w-rightMargin, 0, rightMargin, topMargin, frame)
		' draw bottom left corner
		DrawImageRect(self.image, x, y+rh-bottomMargin, 0, h-bottomMargin, leftMargin, bottomMargin, frame)
		' draw bottom right corner
		DrawImageRect(self.image, x+rw-rightMargin, y+rh-bottomMargin, w-rightMargin, h-bottomMargin, rightMargin, bottomMargin, frame)

		' work out how many horizontal and vertical tiles
		Local tileWidth% = (w-leftMargin-rightMargin)
		Local tileHeight% = (h-topMargin-bottomMargin)
		Local tileXCount% = (rw-leftMargin-rightMargin) / tileWidth
		Local tileYCount% = (rh-topMargin-bottomMargin) / tileHeight
		Local tileXOverflow% = (rw-leftMargin-rightMargin) Mod tileWidth
		Local tileYOverflow% = (rh-topMargin-bottomMargin) Mod tileHeight

		' tile top and bottom edges
		For Local i% = 0 Until tileXCount
			DrawImageRect(self.image, leftMargin+i*tileWidth,0,leftMargin,0,tileWidth,topMargin,frame)
			DrawImageRect(self.image, leftMargin+i*tileWidth,rh-bottomMargin,leftMargin,h-bottomMargin,tileWidth,bottomMargin,frame)
		Next
		If tileXOverflow > 0 Then
			DrawImageRect(self.image, leftMargin+tileXCount*tileWidth,0,leftMargin,0,tileXOverflow,topMargin,frame)
			DrawImageRect(self.image, leftMargin+tileXCount*tileWidth,rh-bottomMargin,leftMargin,h-bottomMargin,tileXOverflow,bottomMargin,frame)
		End

		' tile left and right edges
		For Local i% = 0 Until tileYCount
			DrawImageRect(self.image, 0, topMargin+i*tileHeight,0,topMargin,leftMargin,tileHeight,frame)
			DrawImageRect(self.image, rw-rightMargin,topMargin+i*tileHeight,w-rightMargin,topMargin,rightMargin,tileHeight,frame)
		Next
		If tileYOverflow > 0 Then
			DrawImageRect(self.image, 0, topMargin+tileYCount*tileHeight,0,topMargin,leftMargin,tileYOverflow,frame)
			DrawImageRect(self.image, rw-rightMargin,topMargin+tileYCount*tileHeight,w-rightMargin,topMargin,rightMargin,tileYOverflow,frame)
		End

		' tile centre
		' TODO
	End

	Method DrawSubStretched:Void(destX:Float, destY:Float, destWidth:Float, destHeight:Float, srcX:Int, srcY:Int, srcWidth:Int, srcHeight:Int,
			rotation:Float = 0)', scaleX:Float = 1, scaleY:Float = 1, frame:Int = 0)
		' scales for stretching
		Local stretchScaleX:Float = destWidth / srcWidth
		Local stretchScaleY:Float = destHeight / srcHeight
		DrawImageRect(Self.image, destX, destY, srcX, srcY, srcWidth, srcHeight, rotation, stretchScaleX, stretchScaleY)', frame)
	End

	' Yes, a crapload of parameters.  Thankfully most of them are optional, and this will usually only be called internally by the GUI.
	' TODO: apply scale to parameters
	Method DrawSubGrid:Void(x:Float, y:Float, width:Float, height:Float,
			srcX:Int, srcY:Int, srcWidth:Int, srcHeight:Int,
			leftMargin:Int, rightMargin:Int, topMargin:Int, bottomMargin:Int,
			'rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1,
			drawTopLeft:Bool = True, drawTop:Bool = True, drawTopRight:Bool = True,
			drawLeft:Bool = True, drawCenter:Bool = True, drawRight:Bool = True,
			drawBottomLeft:Bool = True, drawBottom:Bool = True, drawBottomRight:Bool = True)
		' draw the corners
		If drawTopLeft Then DrawImageRect(Self.image,
				x, y, srcX, srcY,
				leftMargin, topMargin)', rotation, scaleX, scaleY)
		If drawTopRight Then DrawImageRect(Self.image,
				x+width-rightMargin, y, srcX+srcWidth-rightMargin, srcY,
				rightMargin, topMargin)', rotation, scaleX, scaleY)
		If drawBottomLeft Then DrawImageRect(Self.image,
				x, y+height-bottomMargin, srcX, srcY+srcHeight-bottomMargin,
				leftMargin, bottomMargin)', rotation, scaleX, scaleY)
		If drawBottomRight Then DrawImageRect(Self.image,
				x+width-rightMargin, y+height-bottomMargin, srcX+srcWidth-rightMargin, srcY+srcHeight-bottomMargin,
				rightMargin, bottomMargin)', rotation, scaleX, scaleY)

		' scales for stretching
		Local stretchScaleX:Float = (width-leftMargin-rightMargin) / (srcWidth-leftMargin-rightMargin)
		Local stretchScaleY:Float = (height-topMargin-bottomMargin) / (srcHeight-topMargin-bottomMargin)

		' draw edges
		If drawLeft Then DrawImageRect(Self.image,
				x, y+topMargin, srcX, srcY+topMargin,
				leftMargin, srcHeight-topMargin-bottomMargin,
				0, 1, stretchScaleY)
		If drawRight Then DrawImageRect(Self.image,
				x+width-rightMargin, y+topMargin, srcX+srcWidth-rightMargin, srcY+topMargin,
				rightMargin, srcHeight-topMargin-bottomMargin,
				0, 1, stretchScaleY)
		If drawTop Then DrawImageRect(Self.image,
				x+leftMargin, y, srcX+leftMargin, srcY,
				srcWidth-leftMargin-rightMargin, topMargin,
				0, stretchScaleX, 1)
		If drawBottom Then DrawImageRect(Self.image,
				x+leftMargin, y+height-bottomMargin, srcX+leftMargin, srcY+srcHeight-bottomMargin,
				srcWidth-leftMargin-rightMargin, bottomMargin,
				0, stretchScaleX, 1)

		' draw center
		If drawCenter Then DrawImageRect(Self.image,
				x+leftMargin, y+topMargin, srcX+leftMargin, srcY+topMargin,
				srcWidth-leftMargin-rightMargin, srcHeight-topMargin-bottomMargin,
				0, stretchScaleX, stretchScaleY)
	End

	Method PreCache:Void()
		DrawImage self.image, -self.w-50, -self.h-50
	End
End

Class SoundBank Extends StringMap<GameSound>

	Global path$ = "sounds/"

	Method Load:GameSound(name:String, nameoverride:String = "", ignoreCache:Bool = False)
		' check if we already have the sound in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old sound if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).sound.Discard()

		Local s:GameSound = New GameSound
		s.Load(name)
		s.name = storeKey
		Self.Set(s.name, s)
		Return s
	End

	Method Find:GameSound(name:String)
		name = name.ToUpper()

		' debug: print all keys in the map
	'	For Local key:String = EachIn self.Keys()
	'		Print key + " is stored in the map."
	'	Next

		Local i:GameSound =  Self.Get(name)
		AssertNotNull(i, "Sound '" + name + "' not found in the SoundBank")
		Return i
	End
End

Class GameSound
	Field name:String
	Field sound:Sound
	Field rate:Float = 1
	Field pan:Float = 0
	Field volume:Float = 1
	Field loop:Int = 0
	Field channel:Int
	Field loopChannelList:IntArrayList = New IntArrayList

	Method Load:Void(file$)

		If file.Contains(".wav") Or file.Contains(".ogg") Or file.Contains(".mp3") Then
			sound = LoadSoundSample(SoundBank.path + file)
		Else
			#if TARGET="flash"
				sound = LoadSoundSample(SoundBank.path + file +".mp3")
			#else If TARGET="android"
				sound = LoadSoundSample(SoundBank.path + file +".ogg")
			#else
				sound = LoadSoundSample(SoundBank.path + file +".wav")
			#endif
		End

		name = StripAll(file.ToUpper())
	End

	Method Play:Void(playChannel:Int = -1)
		channel = SoundPlayer.PlayFx(sound, pan, rate, volume * (game.soundVolume / 100.0), loop, playChannel)
		if loop = 1
			loopChannelList.Add(channel)
		End
	End

	Method Stop:Void()
		SoundPlayer.PlayerStopChannel(channel)
		if loopChannelList.Size > 0
			Local ch:Int
			For Local i:Int = 0 Until loopChannelList.Size
				ch = loopChannelList.GetInt(i)
				SoundPlayer.PlayerStopChannel(ch)
			Next
			loopChannelList.Clear()
		End
	End

End

Class SoundPlayer
	Global channel:Int
	Const MAX_CHANNELS:Int = 31
	Global playerChannelState:Int[] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ' 32 indexes - (0 to 31)

	Function StopChannels:Void()
		For Local i:Int = 0 to MAX_CHANNELS
			StopChannel(i)
			playerChannelState[i] = 0
		Next
	End

	Function PlayerStopChannel:Void(playerChannel:Int)
		StopChannel(playerChannel)
		playerChannelState[playerChannel] = 0
	End

	Function PlayFx:Int(s:Sound, pan:Float=0, rate:Float=1, volume:Float=1, loop:Int = 0, playChannel:Int = -1)
		if playChannel = -1
			Local cnt:Int = 0
			channel += 1
			If (channel > MAX_CHANNELS) Then channel = 0
			While playerChannelState[channel] = 1 ' channel State doesnt work with Flash
				channel += 1
				If (channel > MAX_CHANNELS) Then channel = 0
				cnt=+1
				if cnt > MAX_CHANNELS * 2 Then Exit ' stop infinite loop if case all channels are playing
			Wend
		Else
			channel = playChannel
			playerChannelState[playChannel] = 0
		End

		StopChannel(channel)
		PlaySound(s, channel, loop)
		SetChannelPan(channel, pan)
		SetChannelRate(channel, rate)
		SetChannelVolume(channel, volume)
		if loop
			playerChannelState[channel] = 1
		End
		Return channel
	End

End

Class Sprite
	Field name$
	Field visible?
	Field x:Float, y:Float
	Field ox:Float, oy:Float
	Field dx:Float, dy:Float
	Field speedX:Float, speedY:Float, speed:Float
	Field maxXSpeed:Float, maxYSpeed:Float
	Field image:GameImage
	Field scaleX:Float = 1, scaleY:Float = 1

	Field red% = 255, green% = 255, blue% = 255, alpha# = 1
	Field hitBoxX:Int = 0
	Field hitBoxY:Int = 0
	Field hitBoxWidth:Int
	Field hitBoxHeight:Int

	' Animation
	Field frame:Int
	Field frameTimer:Int
	Field frameStart:Int
	Field frameEnd:Int
	Field frameSpeed:Int = 0
	Field reverse:Bool = false
	Field pingPong:Bool = false
	Field loop:Bool = true
	Field ping:Int

	' Scale
	Field scaleCounter:Float = 0
	Field scaleXSpeed:Float = 0.1
	Field scaleYSpeed:Float = 0.1
	Field ygravity:Float
	Field maxFrame:Int

	' Rotation
	Field rotationCounter:Float = 0
	Field rotationLength:Int = 1000
	Field rotationLoop:Int = 0
	Field rotation:Float
	Field rotationSpeed:Float = 1

	Method New(img:GameImage, x:Float, y:Float)
		Self.image = img
		Self.x = x
		Self.y = y
		self.alpha = 1
		self.SetHitBox(-img.w2, -img.h2, img.w, img.h)
		Self.visible = True
	End

	Method SetImage:Void(gi:GameImage)
		image = gi
		GetSize()
	End

	Method GetSize:Void()
		'Sets w and h based on the current image.
		If image <> Null Then
			image.w = image.image.Width()
			image.h = image.image.Height()
			image.w2 = image.w/2
			image.h2 = image.h/2
		End
	End

	Method Precache:Void()
		DrawImage image.image, -image.w-50, -image.h-50
	End

	Method SetRGB:Void(r:Int, g:Int ,b:Int)
		Self.red = r
		Self.green = g
		Self.blue = b
	End

	Method SetScaleXY:Void(sx:Float, sy:Float)
		self.scaleX = sx
		self.scaleY = sy
	End

	Method Move:Void()
		Self.x+=Self.dx * dt.delta
		Self.y+=Self.dy * dt.delta

		If ygravity > 0 Then dy += ygravity * dt.delta
	End

	Method ManageScale:Void()
		If scaleCounter>0 Then
			scaleCounter-=1*dt.delta
			scaleX+=scaleXSpeed*dt.delta
			scaleY+=scaleYSpeed*dt.delta
		End
	End

	Method ManageRotation:Void()
		If rotationLoop Then
			rotation+=rotationSpeed*dt.delta
			If rotation >= 360 Then rotation-=360
			If rotation <0 Then rotation+=360
		Else
			If rotationCounter>0 Then
				rotationCounter-=1 * dt.delta
				rotation+=rotationSpeed * dt.delta
			End
		End
	End

	Method MoveForward:Void()
		dx = -Sin(rotation) * speed
        dy = -Cos(rotation) * speed

		move()
	End

	Method SetFrame:Void(startFrame:Int=0, endFrame:Int=0, speed:Int=125, pingPong:Bool = false, loop:Bool = true)
		frame = startFrame
		frameStart = startFrame
		frameEnd = endFrame
		if startFrame > endFrame
			reverse = true
		else
			reverse = false
		End
		self.pingPong = pingPong
		self.loop = loop
		frameSpeed = speed
		frameTimer = Millisecs()
		ping = 0
	End

	Method UpdateAnimation:Void()
		if frameSpeed > 0
			If Millisecs() > frameTimer + frameSpeed
				if not reverse
					frame+=1
					If frame > frameEnd
						ResetAnim()
					End
				else
					frame-=1
					If frame < frameEnd
						ResetAnim()
					End
				End
				frameTimer = Millisecs()
			End
		end
	End

	Method ResetAnim:Void()
		if loop then
			if pingPong
				reverse = Not reverse
				frame = frameEnd
				local ts% = frameStart
				frameStart = frameEnd
				frameEnd = ts
			else
				frame = frameStart
			End
		else
			if pingPong and ping <1
				reverse = Not reverse
				frame = frameEnd
				local ts% = frameStart
				frameStart = frameEnd
				frameEnd = ts
				ping+=1
			else
				frame = frameEnd
			End
		End
	End

	Method Draw:Void()
		Draw(0,0)
	End

	Method Draw:Void(offsetx:Float = 0, offsety:Float = 0)
		If x - offsetx + image.w < 0 Or x - offsetx - image.w >= SCREEN_WIDTH Or y - offsety + image.h < 0 Or y - offsety - image.h >= SCREEN_HEIGHT Then Return
		If Self.alpha > 1 Then Self.alpha = 1
		If Self.alpha < 0 Then Self.alpha = 0
		SetAlpha Self.alpha
		SetColor red, green, blue ' doesnt work with images!?!??!
		DrawImage(image.image, x - offsetx, y - offsety, rotation, scaleX, scaleY, frame)
		SetColor 255, 255, 255
		SetAlpha 1

	'	if debug
	'		drawRectOutline(x + hitBoxX, y + hitBoxY, hitBoxWidth, hitBoxHeight)
	'	End
	End

	Method SetupRotation:Void(rotationSpeed:Float, length:Int, loop:Bool = False, rndPosition:Bool = False)
		Self.rotationSpeed = rotationSpeed
		If loop Then
			rotationLoop = 1
			If rndPosition Then rotation = Rnd(0,360)
		Else
			rotationLength = length
			rotationCounter = rotationLength
		End
	End

	Method Collide:Int(sprite:Sprite)
		Return RectsOverlap(x + hitBoxX, y + hitBoxY, hitBoxWidth, hitBoxHeight,
							sprite.x + sprite.hitBoxX, sprite.y + sprite.hitBoxY, sprite.hitBoxWidth, sprite.hitBoxHeight)
	End

	Method SetHitBox:Void(hitX:Int, hitY:Int, hitWidth:Int, hitHeight:Int)
		hitBoxX = hitX
		hitBoxY = hitY
		hitBoxWidth = hitWidth
		hitBoxHeight = hitHeight
	End
End

Class Particle Extends Sprite
	Global MAX_PARTICLES:Int = 800
	Global particles:Particle[MAX_PARTICLES]
	Global lastDeath:Int = 0
	Global maxIndex:Int = -1
	Global minIndex:Int = -1
	Global particleCount:Int = 0
	Field lifeCounter:Float = 0
	Field fadeIn:Float = 0
	Field fadeCounter:Float
	Field fadeInLength:Float = 0
	Field fadeLength:Float = 0
	Field active:Int = 0

	Function Cache:Void()
		For Local i:Int = 0 to MAX_PARTICLES - 1
			particles[i] = New Particle()
		Next
	End

	Function Create:Particle(gi:GameImage, x:Float, y:Float, dx:Float = 0, dy:Float = 0, gravity:Float = 0, fadeLength:Float = 0, lifeCounter:Int = 0)
		Local i%=lastDeath
		Repeat
			If particles[i] = Null Then particles[i] = New Particle()
			If Not particles[i].active Then
				particles[i].SetImage(gi)
				particles[i].x = x
				particles[i].y = y
				particles[i].dx = dx
				particles[i].dy = dy

				particles[i].ygravity = gravity
				particles[i].fadeLength = fadeLength / 10
				particles[i].fadeCounter = particles[i].fadeLength
				If lifeCounter>0 Then particles[i].lifeCounter = lifeCounter / 10
				particles[i].active = 1
				If maxIndex < 0 Or i > maxIndex Then maxIndex = i
				If minIndex < 0 Or i < minIndex Then minIndex = i
				particleCount += 1
				Return particles[i]
			End
			i += 1
			If i >= MAX_PARTICLES Then i = 0
		Until i = lastDeath
		Return Null
	End

	Function Clear:Void()
		For Local i:Int = 0 to MAX_PARTICLES - 1
			particles[i].alpha = 0
			particles[i].active = False
		Next
		minIndex = -1
		maxIndex = -1
		particleCount = 0
		lastDeath = 0
	End

	Function DrawAll:Void()
		If minIndex < 0 Or maxIndex < 0 Then Return
		For Local i% = minIndex to maxIndex
			if particles[i] <> null And particles[i].image <> null
				If particles[i].fadeCounter > 0 and particles[i].active Then
					If particles[i].fadeIn Then
						particles[i].alpha = particles[i].fadeCounter/particles[i].fadeInLength
					Else
						particles[i].alpha = particles[i].fadeCounter/particles[i].fadeLength
					End
				End
				particles[i].Draw()
			End
		Next
	End

	Function UpdateAll:Void()
		If minIndex < 0 Or maxIndex < 0 Then Return
		Local newMinIndex:Int = -1
		Local newMaxIndex:Int = -1
		For Local i:Int = minIndex to maxIndex
			if particles[i] <> null And particles[i].image <> null
				If particles[i].active
					particles[i].Update()
					If particles[i].active Then
						If newMinIndex < 0 Then newMinIndex = i
						newMaxIndex = i
					Else
						lastDeath = i
						particleCount -= 1
					End
				End
			End
		Next
		minIndex = newMinIndex
		maxIndex = newMaxIndex
	End

	Method Update:Void()
		Super.Move()

		If fadeIn Then
			fadeCounter+=dt.delta

			If fadeCounter >= fadeInLength Then
				fadeCounter = fadeLength
				fadeIn = 0
				alpha = 1
			End
		ElseIf fadeCounter>0 Then
			fadeCounter-=dt.delta
			If fadeCounter <= 0 Then
				alpha = 0
				active = 0
			End
		End
	End

End

Strict

Import mojo
Import functions
Import collections

'Device width and height
Global DEVICE_WIDTH:Int
Global DEVICE_HEIGHT:Int

' Screen width and height
Global SCREEN_WIDTH:Float
Global SCREEN_HEIGHT:Float

' Half of SCREEN_WIDTH and HEIGHT
Global SCREEN_WIDTH2:Float
Global SCREEN_HEIGHT2:Float

' Used for Virtual Res
Global SCREENX_RATIO:Float = 1
Global SCREENY_RATIO:Float = 1

' THE GAME!!
Global game:DiddyApp

' Used for delta timing movement
Global dt:DeltaTimer

Class DiddyApp Extends App

	Field debugKeyOn:Bool = False
	Field debugOn:Bool = False
	Field drawFPSOn:Bool = False
	Field debugKey:Int = KEY_F1

	Field virtualResOn:Bool = True

	Field FPS% = 60

	' current Screen
	Field currentScreen:Screen
	' next Screen
	Field nextScreen:Screen
	' exit Screen
	Field exitScreen:ExitScreen = new ExitScreen()
	' used for fading
	Field screenFade:ScreenFade = New ScreenFade

	' mouse
	Field mouseX:Int, mouseY:Int
	Field mouseHit:Int

	' Store the images here
	Field images:ImageBank = New ImageBank
	' Store the sounds here
	Field sounds:SoundBank = New SoundBank
	' volume control
	Field musicFile:String = ""
	Field soundVolume:Int = 100
	Field musicVolume:Int = 100
	Field mojoMusicVolume:Float = 1.0
	Field musicOkay:Int

	Field clickSound:GameSound

	Method OnCreate:Int()
		' Store the device width and height
		DEVICE_WIDTH = DeviceWidth()
		DEVICE_HEIGHT = DeviceHeight()

		SetScreenSize(DEVICE_WIDTH, DEVICE_HEIGHT)

		' set the mouse x,y
		mouseX = MouseX() / SCREENX_RATIO
		mouseY = MouseY() / SCREENY_RATIO

		' Set the Random seed
		Seed = RealMillisecs()
		' Create the delta timer
		dt = New DeltaTimer(FPS)
		' Set the update rate
		SetUpdateRate FPS

		'create all the particles
		Particle.Cache()

		Return 0
	End

	Method SetScreenSize:Void(w:Float, h:Float)
		SCREEN_WIDTH = w
		SCREEN_HEIGHT = h
		SCREEN_WIDTH2 = SCREEN_WIDTH / 2
		SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2

		SCREENX_RATIO = DEVICE_WIDTH/SCREEN_WIDTH
		SCREENY_RATIO = DEVICE_HEIGHT/SCREEN_HEIGHT

		If SCREENX_RATIO <> 1 Or SCREENY_RATIO <> 1
			virtualResOn = True
		End
	End

	Method OnRender:Int()
		FPSCounter.Update()
		If virtualResOn
			PushMatrix
			Scale SCREENX_RATIO, SCREENY_RATIO
		End
			currentScreen.Render()
		if virtualResOn
			PopMatrix
		End
		currentScreen.ExtraRender()
		If screenFade.active then screenFade.Render()
		currentScreen.DebugRender()
		If debugOn
			DrawDebug()
		End
		If drawFPSOn
			DrawFPS()
		End
		Return 0
	End

	Method OnUpdate:Int()
		dt.UpdateDelta()

		If debugKeyOn
			If KeyHit(debugKey)
				debugOn = Not debugOn
			End
		End

		mouseX = MouseX() / SCREENX_RATIO
		mouseY = MouseY() / SCREENY_RATIO
		mouseHit = MouseHit()

		If screenFade.active then screenFade.Update()
		currentScreen.Update()

		Return 0
	End

	Method DrawDebug:Void()
		SetColor 255, 255, 255
		FPSCounter.Draw(0,0)
		Local y:Int = 10
		Local gap:Int = 10
		DrawText "Screen             = "+currentScreen.name, 0, y
		y += gap
		DrawText "Delta              = "+FormatNumber(dt.delta, 2) , 0, y
		y += gap
		DrawText "Frame Time         = "+dt.frametime , 0, y
		y += gap
		DrawText "Screen Width       = "+SCREEN_WIDTH, 0, y
		y += gap
		DrawText "Screen Height      = "+SCREEN_HEIGHT, 0, y
		y += gap
		DrawText "VMouseX            = "+Self.mouseX, 0, y
		y += gap
		DrawText "VMouseY            = "+Self.mouseY, 0, y
		y += gap
		DrawText "MouseX             = "+MouseX(), 0, y
		y += gap
		DrawText "MouseY             = "+MouseY(), 0, y
		y += gap
		DrawText "Music File         = "+musicFile, 0, y
		y += gap
		DrawText "MusicOkay          = "+musicOkay, 0, y
		y += gap
		DrawText "Music State        = "+MusicState(), 0, y
		y += gap
		DrawText "Music Volume       = "+Self.musicVolume, 0, y
		y += gap
		DrawText "Mojo Music Volume  = "+Self.mojoMusicVolume, 0, y
		y += gap
		DrawText "Sound Volume       = "+Self.soundVolume, 0, y
		y += gap
		DrawText "Sound Channel      = "+SoundPlayer.channel, 0, y
		y += gap
	End

	Method DrawFPS:Void()
		DrawText FPSCounter.totalFPS, 0, 0
	End

	Method MusicPlay:Void(file:String, flags:Int=1)
		musicFile = file

		musicOkay = PlayMusic("music/"+musicFile, flags)
		if musicOkay = -1
			Print "Error Playing Music - Music must be in the data\music folder"
		End
	End

	Method MusicSetVolume:Void(volume:Int)
		If volume < 0 Then volume = 0
		If volume > 100 Then volume = 100
		Self.musicVolume = volume
		SetMojoMusicVolume(musicVolume/100.0)
	End

	Method SetMojoMusicVolume:Void(volume:Float)
		If volume < 0 Then volume = 0
		If volume > 1 Then volume = 1
		mojoMusicVolume = volume
		SetMusicVolume(mojoMusicVolume)
	End

	Method SoundSetVolume:Void(volume:Int)
		If volume < 0 Then volume = 0
		If volume > 100 Then volume = 100
		Self.soundVolume = volume
		For Local i% = 0 To SoundPlayer.MAX_CHANNELS
			SetChannelVolume(i, game.soundVolume / 100.0)
		Next
	End

	Method CalcAnimLength:Float(ms:Int)
		Return ms / (1000.0 / FPS)
	End
End

Class ScreenFade
	Field fadeTime:Float
	Field fadeOut:Bool
	Field ratio:Float = 0
	Field active:Bool
	Field counter:Float
	Field fadeMusic:Bool
	Field fadeSound:Bool

	Method Start:Void(fadeTime:Float, fadeOut:Bool, fadeSound:Bool = False, fadeMusic:Bool = False)
		If active Then Return
		active = True
		Self.fadeTime = fadeTime
		Self.fadeOut = fadeOut
		Self.fadeMusic = fadeMusic
		Self.fadeSound = fadeSound
		If fadeOut Then
			ratio = 1
		Else
			ratio = 0
			' set the music volume to zero if fading in the music
			if Self.fadeMusic
				game.SetMojoMusicVolume(0)
			End
		End
		counter = 0
	End

	Method Update:Void()
		If Not active Return
		counter += dt.delta
		CalcRatio()
		If fadeSound Then
			For Local i% = 0 To SoundPlayer.MAX_CHANNELS
				SetChannelVolume(i, (ratio) * (game.soundVolume / 100.0))
			Next
		End
		If fadeMusic Then
			game.SetMojoMusicVolume((ratio) * (game.musicVolume / 100.0))
		End
		if counter > fadeTime
			active = false
			if fadeOut
				game.currentScreen.PostFadeOut()
			else
				game.currentScreen.PostFadeIn()
			End
		End
	End

	Method CalcRatio:Void()
		ratio = counter/fadeTime
		If ratio < 0
			ratio = 0
			if fadeMusic
				game.SetMojoMusicVolume(0)
			End
		End
		If ratio > 1
			ratio = 1
			if fadeMusic
				game.SetMojoMusicVolume(game.musicVolume / 100.0)
			End
		End
		If fadeOut Then
			ratio = 1 - ratio
		End
	End

	Method Render:Void()
		If Not active Return

		SetAlpha 1 - ratio
		SetColor 0, 0, 0
		DrawRect 0, 0, DEVICE_WIDTH, DEVICE_HEIGHT
		SetAlpha 1
		SetColor 255, 255, 255
	End

End

Class ExitScreen Extends Screen
	Method New()
		name = "exit"
	End

	Method Start:Void()
		ExitApp()
	End

	Method Render:Void()
	End

	Method Update:Void()
	End
End

Class Screen Abstract
	Field name$ = ""

	Method PreStart:Void()
		game.currentScreen = self
		Start()
	End

	Method Start:Void() Abstract

	Method Render:Void() Abstract

	Method Update:Void() Abstract

	Method PostFadeOut:Void()
		game.nextScreen.PreStart()
	End

	Method PostFadeIn:Void()
	End

	Method ExtraRender:Void()
	End

	Method DebugRender:Void()
	End

End Class

Class FPSCounter Abstract
	Global fpsCount:Int
	Global startTime:Int
	Global totalFPS:Int

	Function Update:Void()
		If Millisecs() - startTime >= 1000
			totalFPS = fpsCount
			fpsCount = 0
			startTime = Millisecs()
		Else
			fpsCount+=1
		End
	End

	Function Draw:Void(x% = 0, y% = 0, ax# = 0, ay# = 0)
		DrawText("FPS: " + totalFPS, x, y, ax, ay)
	End
End

' From James Boyd
Class DeltaTimer
	Field targetfps:Float = 60
	Field currentticks:Float
	Field lastticks:Float
	Field frametime:Float
	Field delta:Float

	Method New (fps:Float)
		targetfps = fps
		lastticks = Millisecs()
	End

	Method UpdateDelta:Void()
		currentticks = Millisecs()
		frametime = currentticks - lastticks
		delta = frametime / (1000.0 / targetfps)
		lastticks = currentticks
	End
End

Class ImageBank Extends StringMap<GameImage>

	Field path$ = "graphics/"

	Method Load:GameImage(name:String, nameoverride:String = "", midhandle:Bool=true, ignoreCache:Bool=False)
		' check if we already have the image in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old image if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).image.Discard()

		Local i:GameImage = New GameImage
		i.Load(path + name, midhandle)
		i.name = storeKey
		Self.Set(i.name, i)
		Return i
	End

	Method LoadAnim:GameImage(name:String, w%, h%, total%, tmpImage:Image, midhandle:Bool=true, ignoreCache:Bool=False, nameoverride:String = "")
		' check if we already have the image in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old image if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).image.Discard()

		Local i:GameImage = New GameImage
		i.LoadAnim(path + name, w, h, total, tmpImage, midhandle)
		i.name = storeKey
		Self.Set(i.name, i)
		Return i
	End

	Method LoadTileset:GameImage(name:String, tileWidth%, tileHeight%, tileMargin% = 0, tileSpacing% = 0, nameoverride:String = "", midhandle:Bool=False, ignoreCache:Bool=False)
		' check if we already have the image in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old image if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).image.Discard()

		' load the new one
		Local i:GameImage = New GameImage
		i.LoadTileset(path + name, tileWidth, tileHeight, tileMargin, tileSpacing, midhandle)
		i.name = storeKey
		Self.Set(i.name, i)
		Return i
	End

	Method Find:GameImage(name:String)
		name = name.ToUpper()

		' debug: print all keys in the map
	'	For Local key:String = EachIn self.Keys()
	'		Print key + " is stored in the map."
	'	Next

		Local i:GameImage = Self.Get(name)
		AssertNotNull(i, "Image '" + name + "' not found in the ImageBank")
		Return i
	End

	Method PreCache:Void()
		Local gi:GameImage
		For Local key:String = EachIn self.Keys()
			gi = Self.Get(key)
			gi.PreCache()
		Next
	End

End

Class GameImage
	Field name:String
	Field image:Image
	Field w:Int
	Field h:Int
	Field w2:Float
	Field h2:Float
	Field midhandled:Int = 0

	Field leftMargin:Int = 0
	Field rightMargin:Int = 0
	Field topMargin:Int = 0
	Field bottomMargin:Int = 0

	Field tileWidth:Int, tileHeight:Int
	Field tileCountX:Int, tileCountY:Int
	Field tileCount:Int
	Field tileSpacing:Int, tileMargin:Int

	Method Load:Void(file$, midhandle:bool=true)
		name = StripAll(file.ToUpper())
		image = LoadBitmap(file)
		CalcSize()
		MidHandle(midhandle)
	End

	Method LoadAnim:Void(file:String, w:Int, h:Int, total%, tmpImage:Image, midhandle:Bool=true)
		name = StripAll(file.ToUpper())
		image = LoadAnimBitmap(file, w, h, total, tmpImage)
		CalcSize()
		MidHandle(midhandle)
	End

	Method LoadTileset:Void(file:String, tileWidth:Int, tileHeight:Int, tileMargin:Int = 0, tileSpacing:Int = 0, midhandle:Bool=False)
		Load(file, midhandle)
		Self.tileWidth = tileWidth
		Self.tileHeight = tileHeight
		Self.tileMargin = tileMargin
		Self.tileSpacing = tileSpacing
		tileCountX = (w - tileMargin) / (tileWidth + tileSpacing)
		tileCountY = (h - tileMargin) / (tileHeight + tileSpacing)
		tileCount = tileCountX * tileCountY
	End

	Method CalcSize:Void()
		If image <> Null Then
			w = image.Width()
			h = image.Height()
			w2 = w/2
			h2 = h/2
		End
	End

	Method MidHandle:Void(On:Bool)
		If On Then
			image.SetHandle(w2, h2)
			midhandled=1
		Else
			image.SetHandle(0, 0)
			midhandled=0
		End
	End

	Method Draw:Void(x:Float, y:Float, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1, frame:Int = 0)
		DrawImage(self.image, x, y, rotation, scaleX, scaleY, frame)
	End

	Method DrawSubImage:Void(destX:Float, destY:Float, srcX:Int, srcY:Int, srcWidth:Int, srcHeight:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1, frame:Int = 0)
		DrawImageRect(Self.image, destX, destY, srcX, srcY, srcWidth, srcHeight, rotation, scaleX, scaleY, frame)
	End

	Method DrawTile:Void(x:Float, y:Float, tile:Int = 0, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		Local srcX% = tileMargin + (tileWidth + tileSpacing) * (tile Mod tileCountX)
		Local srcY% = tileMargin + (tileHeight + tileSpacing) * (tile / tileCountX)
		DrawImageRect(Self.image, x, y, srcX, srcY, tileWidth, tileHeight, rotation, scaleX, scaleY)
	End

	Method DrawGrid:Void(x:Float, y:Float, rw:Float, rh:Float, frame:Int = 0)
		' draw top left corner
		DrawImageRect(self.image, x, y, 0, 0, leftMargin, topMargin, frame)
		' draw top right corner
		DrawImageRect(self.image, x+rw-rightMargin, y, w-rightMargin, 0, rightMargin, topMargin, frame)
		' draw bottom left corner
		DrawImageRect(self.image, x, y+rh-bottomMargin, 0, h-bottomMargin, leftMargin, bottomMargin, frame)
		' draw bottom right corner
		DrawImageRect(self.image, x+rw-rightMargin, y+rh-bottomMargin, w-rightMargin, h-bottomMargin, rightMargin, bottomMargin, frame)

		' work out how many horizontal and vertical tiles
		Local tileWidth% = (w-leftMargin-rightMargin)
		Local tileHeight% = (h-topMargin-bottomMargin)
		Local tileXCount% = (rw-leftMargin-rightMargin) / tileWidth
		Local tileYCount% = (rh-topMargin-bottomMargin) / tileHeight
		Local tileXOverflow% = (rw-leftMargin-rightMargin) Mod tileWidth
		Local tileYOverflow% = (rh-topMargin-bottomMargin) Mod tileHeight

		' tile top and bottom edges
		For Local i% = 0 Until tileXCount
			DrawImageRect(self.image, leftMargin+i*tileWidth,0,leftMargin,0,tileWidth,topMargin,frame)
			DrawImageRect(self.image, leftMargin+i*tileWidth,rh-bottomMargin,leftMargin,h-bottomMargin,tileWidth,bottomMargin,frame)
		Next
		If tileXOverflow > 0 Then
			DrawImageRect(self.image, leftMargin+tileXCount*tileWidth,0,leftMargin,0,tileXOverflow,topMargin,frame)
			DrawImageRect(self.image, leftMargin+tileXCount*tileWidth,rh-bottomMargin,leftMargin,h-bottomMargin,tileXOverflow,bottomMargin,frame)
		End

		' tile left and right edges
		For Local i% = 0 Until tileYCount
			DrawImageRect(self.image, 0, topMargin+i*tileHeight,0,topMargin,leftMargin,tileHeight,frame)
			DrawImageRect(self.image, rw-rightMargin,topMargin+i*tileHeight,w-rightMargin,topMargin,rightMargin,tileHeight,frame)
		Next
		If tileYOverflow > 0 Then
			DrawImageRect(self.image, 0, topMargin+tileYCount*tileHeight,0,topMargin,leftMargin,tileYOverflow,frame)
			DrawImageRect(self.image, rw-rightMargin,topMargin+tileYCount*tileHeight,w-rightMargin,topMargin,rightMargin,tileYOverflow,frame)
		End

		' tile centre
		' TODO
	End

	Method DrawSubStretched:Void(destX:Float, destY:Float, destWidth:Float, destHeight:Float, srcX:Int, srcY:Int, srcWidth:Int, srcHeight:Int,
			rotation:Float = 0)', scaleX:Float = 1, scaleY:Float = 1, frame:Int = 0)
		' scales for stretching
		Local stretchScaleX:Float = destWidth / srcWidth
		Local stretchScaleY:Float = destHeight / srcHeight
		DrawImageRect(Self.image, destX, destY, srcX, srcY, srcWidth, srcHeight, rotation, stretchScaleX, stretchScaleY)', frame)
	End

	' Yes, a crapload of parameters.  Thankfully most of them are optional, and this will usually only be called internally by the GUI.
	' TODO: apply scale to parameters
	Method DrawSubGrid:Void(x:Float, y:Float, width:Float, height:Float,
			srcX:Int, srcY:Int, srcWidth:Int, srcHeight:Int,
			leftMargin:Int, rightMargin:Int, topMargin:Int, bottomMargin:Int,
			'rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1,
			drawTopLeft:Bool = True, drawTop:Bool = True, drawTopRight:Bool = True,
			drawLeft:Bool = True, drawCenter:Bool = True, drawRight:Bool = True,
			drawBottomLeft:Bool = True, drawBottom:Bool = True, drawBottomRight:Bool = True)
		' draw the corners
		If drawTopLeft Then DrawImageRect(Self.image,
				x, y, srcX, srcY,
				leftMargin, topMargin)', rotation, scaleX, scaleY)
		If drawTopRight Then DrawImageRect(Self.image,
				x+width-rightMargin, y, srcX+srcWidth-rightMargin, srcY,
				rightMargin, topMargin)', rotation, scaleX, scaleY)
		If drawBottomLeft Then DrawImageRect(Self.image,
				x, y+height-bottomMargin, srcX, srcY+srcHeight-bottomMargin,
				leftMargin, bottomMargin)', rotation, scaleX, scaleY)
		If drawBottomRight Then DrawImageRect(Self.image,
				x+width-rightMargin, y+height-bottomMargin, srcX+srcWidth-rightMargin, srcY+srcHeight-bottomMargin,
				rightMargin, bottomMargin)', rotation, scaleX, scaleY)

		' scales for stretching
		Local stretchScaleX:Float = (width-leftMargin-rightMargin) / (srcWidth-leftMargin-rightMargin)
		Local stretchScaleY:Float = (height-topMargin-bottomMargin) / (srcHeight-topMargin-bottomMargin)

		' draw edges
		If drawLeft Then DrawImageRect(Self.image,
				x, y+topMargin, srcX, srcY+topMargin,
				leftMargin, srcHeight-topMargin-bottomMargin,
				0, 1, stretchScaleY)
		If drawRight Then DrawImageRect(Self.image,
				x+width-rightMargin, y+topMargin, srcX+srcWidth-rightMargin, srcY+topMargin,
				rightMargin, srcHeight-topMargin-bottomMargin,
				0, 1, stretchScaleY)
		If drawTop Then DrawImageRect(Self.image,
				x+leftMargin, y, srcX+leftMargin, srcY,
				srcWidth-leftMargin-rightMargin, topMargin,
				0, stretchScaleX, 1)
		If drawBottom Then DrawImageRect(Self.image,
				x+leftMargin, y+height-bottomMargin, srcX+leftMargin, srcY+srcHeight-bottomMargin,
				srcWidth-leftMargin-rightMargin, bottomMargin,
				0, stretchScaleX, 1)

		' draw center
		If drawCenter Then DrawImageRect(Self.image,
				x+leftMargin, y+topMargin, srcX+leftMargin, srcY+topMargin,
				srcWidth-leftMargin-rightMargin, srcHeight-topMargin-bottomMargin,
				0, stretchScaleX, stretchScaleY)
	End

	Method PreCache:Void()
		DrawImage self.image, -self.w-50, -self.h-50
	End
End

Class SoundBank Extends StringMap<GameSound>

	Global path$ = "sounds/"

	Method Load:GameSound(name:String, nameoverride:String = "", ignoreCache:Bool = False)
		' check if we already have the sound in the bank!
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Not ignoreCache And Self.Contains(storeKey) Then Return Self.Get(storeKey)

		' discard the old sound if it's there
		If Self.Contains(storeKey) Then Self.Get(storeKey).sound.Discard()

		Local s:GameSound = New GameSound
		s.Load(name)
		s.name = storeKey
		Self.Set(s.name, s)
		Return s
	End

	Method Find:GameSound(name:String)
		name = name.ToUpper()

		' debug: print all keys in the map
	'	For Local key:String = EachIn self.Keys()
	'		Print key + " is stored in the map."
	'	Next

		Local i:GameSound =  Self.Get(name)
		AssertNotNull(i, "Sound '" + name + "' not found in the SoundBank")
		Return i
	End
End

Class GameSound
	Field name:String
	Field sound:Sound
	Field rate:Float = 1
	Field pan:Float = 0
	Field volume:Float = 1
	Field loop:Int = 0
	Field channel:Int
	Field loopChannelList:IntArrayList = New IntArrayList

	Method Load:Void(file$)

		If file.Contains(".wav") Or file.Contains(".ogg") Or file.Contains(".mp3") Then
			sound = LoadSoundSample(SoundBank.path + file)
		Else
			#if TARGET="flash"
				sound = LoadSoundSample(SoundBank.path + file +".mp3")
			#else If TARGET="android"
				sound = LoadSoundSample(SoundBank.path + file +".ogg")
			#else
				sound = LoadSoundSample(SoundBank.path + file +".wav")
			#endif
		End

		name = StripAll(file.ToUpper())
	End

	Method Play:Void(playChannel:Int = -1)
		channel = SoundPlayer.PlayFx(sound, pan, rate, volume * (game.soundVolume / 100.0), loop, playChannel)
		if loop = 1
			loopChannelList.Add(channel)
		End
	End

	Method Stop:Void()
		SoundPlayer.PlayerStopChannel(channel)
		if loopChannelList.Size > 0
			Local ch:Int
			For Local i:Int = 0 Until loopChannelList.Size
				ch = loopChannelList.GetInt(i)
				SoundPlayer.PlayerStopChannel(ch)
			Next
			loopChannelList.Clear()
		End
	End

End

Class SoundPlayer
	Global channel:Int
	Const MAX_CHANNELS:Int = 31
	Global playerChannelState:Int[] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ' 32 indexes - (0 to 31)

	Function StopChannels:Void()
		For Local i:Int = 0 to MAX_CHANNELS
			StopChannel(i)
			playerChannelState[i] = 0
		Next
	End

	Function PlayerStopChannel:Void(playerChannel:Int)
		StopChannel(playerChannel)
		playerChannelState[playerChannel] = 0
	End

	Function PlayFx:Int(s:Sound, pan:Float=0, rate:Float=1, volume:Float=1, loop:Int = 0, playChannel:Int = -1)
		if playChannel = -1
			Local cnt:Int = 0
			channel += 1
			If (channel > MAX_CHANNELS) Then channel = 0
			While playerChannelState[channel] = 1 ' channel State doesnt work with Flash
				channel += 1
				If (channel > MAX_CHANNELS) Then channel = 0
				cnt=+1
				if cnt > MAX_CHANNELS * 2 Then Exit ' stop infinite loop if case all channels are playing
			Wend
		Else
			channel = playChannel
			playerChannelState[playChannel] = 0
		End

		StopChannel(channel)
		PlaySound(s, channel, loop)
		SetChannelPan(channel, pan)
		SetChannelRate(channel, rate)
		SetChannelVolume(channel, volume)
		if loop
			playerChannelState[channel] = 1
		End
		Return channel
	End

End

Class Sprite
	Field name$
	Field visible?
	Field x:Float, y:Float
	Field ox:Float, oy:Float
	Field dx:Float, dy:Float
	Field speedX:Float, speedY:Float, speed:Float
	Field maxXSpeed:Float, maxYSpeed:Float
	Field image:GameImage
	Field scaleX:Float = 1, scaleY:Float = 1

	Field red% = 255, green% = 255, blue% = 255, alpha# = 1
	Field hitBoxX:Int = 0
	Field hitBoxY:Int = 0
	Field hitBoxWidth:Int
	Field hitBoxHeight:Int

	' Animation
	Field frame:Int
	Field frameTimer:Int
	Field frameStart:Int
	Field frameEnd:Int
	Field frameSpeed:Int = 0
	Field reverse:Bool = false
	Field pingPong:Bool = false
	Field loop:Bool = true
	Field ping:Int

	' Scale
	Field scaleCounter:Float = 0
	Field scaleXSpeed:Float = 0.1
	Field scaleYSpeed:Float = 0.1
	Field ygravity:Float
	Field maxFrame:Int

	' Rotation
	Field rotationCounter:Float = 0
	Field rotationLength:Int = 1000
	Field rotationLoop:Int = 0
	Field rotation:Float
	Field rotationSpeed:Float = 1

	Method New(img:GameImage, x:Float, y:Float)
		Self.image = img
		Self.x = x
		Self.y = y
		self.alpha = 1
		self.SetHitBox(-img.w2, -img.h2, img.w, img.h)
		Self.visible = True
	End

	Method SetImage:Void(gi:GameImage)
		image = gi
		GetSize()
	End

	Method GetSize:Void()
		'Sets w and h based on the current image.
		If image <> Null Then
			image.w = image.image.Width()
			image.h = image.image.Height()
			image.w2 = image.w/2
			image.h2 = image.h/2
		End
	End

	Method Precache:Void()
		DrawImage image.image, -image.w-50, -image.h-50
	End

	Method SetRGB:Void(r:Int, g:Int ,b:Int)
		Self.red = r
		Self.green = g
		Self.blue = b
	End

	Method SetScaleXY:Void(sx:Float, sy:Float)
		self.scaleX = sx
		self.scaleY = sy
	End

	Method Move:Void()
		Self.x+=Self.dx * dt.delta
		Self.y+=Self.dy * dt.delta

		If ygravity > 0 Then dy += ygravity * dt.delta
	End

	Method ManageScale:Void()
		If scaleCounter>0 Then
			scaleCounter-=1*dt.delta
			scaleX+=scaleXSpeed*dt.delta
			scaleY+=scaleYSpeed*dt.delta
		End
	End

	Method ManageRotation:Void()
		If rotationLoop Then
			rotation+=rotationSpeed*dt.delta
			If rotation >= 360 Then rotation-=360
			If rotation <0 Then rotation+=360
		Else
			If rotationCounter>0 Then
				rotationCounter-=1 * dt.delta
				rotation+=rotationSpeed * dt.delta
			End
		End
	End

	Method MoveForward:Void()
		dx = -Sin(rotation) * speed
        dy = -Cos(rotation) * speed

		move()
	End

	Method SetFrame:Void(startFrame:Int=0, endFrame:Int=0, speed:Int=125, pingPong:Bool = false, loop:Bool = true)
		frame = startFrame
		frameStart = startFrame
		frameEnd = endFrame
		if startFrame > endFrame
			reverse = true
		else
			reverse = false
		End
		self.pingPong = pingPong
		self.loop = loop
		frameSpeed = speed
		frameTimer = Millisecs()
		ping = 0
	End

	Method UpdateAnimation:Void()
		if frameSpeed > 0
			If Millisecs() > frameTimer + frameSpeed
				if not reverse
					frame+=1
					If frame > frameEnd
						ResetAnim()
					End
				else
					frame-=1
					If frame < frameEnd
						ResetAnim()
					End
				End
				frameTimer = Millisecs()
			End
		end
	End

	Method ResetAnim:Void()
		if loop then
			if pingPong
				reverse = Not reverse
				frame = frameEnd
				local ts% = frameStart
				frameStart = frameEnd
				frameEnd = ts
			else
				frame = frameStart
			End
		else
			if pingPong and ping <1
				reverse = Not reverse
				frame = frameEnd
				local ts% = frameStart
				frameStart = frameEnd
				frameEnd = ts
				ping+=1
			else
				frame = frameEnd
			End
		End
	End

	Method Draw:Void()
		Draw(0,0)
	End

	Method Draw:Void(offsetx:Float = 0, offsety:Float = 0)
		If x - offsetx + image.w < 0 Or x - offsetx - image.w >= SCREEN_WIDTH Or y - offsety + image.h < 0 Or y - offsety - image.h >= SCREEN_HEIGHT Then Return
		If Self.alpha > 1 Then Self.alpha = 1
		If Self.alpha < 0 Then Self.alpha = 0
		SetAlpha Self.alpha
		SetColor red, green, blue ' doesnt work with images!?!??!
		DrawImage(image.image, x - offsetx, y - offsety, rotation, scaleX, scaleY, frame)
		SetColor 255, 255, 255
		SetAlpha 1

	'	if debug
	'		drawRectOutline(x + hitBoxX, y + hitBoxY, hitBoxWidth, hitBoxHeight)
	'	End
	End

	Method SetupRotation:Void(rotationSpeed:Float, length:Int, loop:Bool = False, rndPosition:Bool = False)
		Self.rotationSpeed = rotationSpeed
		If loop Then
			rotationLoop = 1
			If rndPosition Then rotation = Rnd(0,360)
		Else
			rotationLength = length
			rotationCounter = rotationLength
		End
	End

	Method Collide:Int(sprite:Sprite)
		Return RectsOverlap(x + hitBoxX, y + hitBoxY, hitBoxWidth, hitBoxHeight,
							sprite.x + sprite.hitBoxX, sprite.y + sprite.hitBoxY, sprite.hitBoxWidth, sprite.hitBoxHeight)
	End

	Method SetHitBox:Void(hitX:Int, hitY:Int, hitWidth:Int, hitHeight:Int)
		hitBoxX = hitX
		hitBoxY = hitY
		hitBoxWidth = hitWidth
		hitBoxHeight = hitHeight
	End
End

Class Particle Extends Sprite
	Global MAX_PARTICLES:Int = 800
	Global particles:Particle[MAX_PARTICLES]
	Global lastDeath:Int = 0
	Global maxIndex:Int = -1
	Global minIndex:Int = -1
	Global particleCount:Int = 0
	Field lifeCounter:Float = 0
	Field fadeIn:Float = 0
	Field fadeCounter:Float
	Field fadeInLength:Float = 0
	Field fadeLength:Float = 0
	Field active:Int = 0

	Function Cache:Void()
		For Local i:Int = 0 to MAX_PARTICLES - 1
			particles[i] = New Particle()
		Next
	End

	Function Create:Particle(gi:GameImage, x:Float, y:Float, dx:Float = 0, dy:Float = 0, gravity:Float = 0, fadeLength:Float = 0, lifeCounter:Int = 0)
		Local i%=lastDeath
		Repeat
			If particles[i] = Null Then particles[i] = New Particle()
			If Not particles[i].active Then
				particles[i].SetImage(gi)
				particles[i].x = x
				particles[i].y = y
				particles[i].dx = dx
				particles[i].dy = dy

				particles[i].ygravity = gravity
				particles[i].fadeLength = fadeLength / 10
				particles[i].fadeCounter = particles[i].fadeLength
				If lifeCounter>0 Then particles[i].lifeCounter = lifeCounter / 10
				particles[i].active = 1
				If maxIndex < 0 Or i > maxIndex Then maxIndex = i
				If minIndex < 0 Or i < minIndex Then minIndex = i
				particleCount += 1
				Return particles[i]
			End
			i += 1
			If i >= MAX_PARTICLES Then i = 0
		Until i = lastDeath
		Return Null
	End

	Function Clear:Void()
		For Local i:Int = 0 to MAX_PARTICLES - 1
			particles[i].alpha = 0
			particles[i].active = False
		Next
		minIndex = -1
		maxIndex = -1
		particleCount = 0
		lastDeath = 0
	End

	Function DrawAll:Void()
		If minIndex < 0 Or maxIndex < 0 Then Return
		For Local i% = minIndex to maxIndex
			if particles[i] <> null And particles[i].image <> null
				If particles[i].fadeCounter > 0 and particles[i].active Then
					If particles[i].fadeIn Then
						particles[i].alpha = particles[i].fadeCounter/particles[i].fadeInLength
					Else
						particles[i].alpha = particles[i].fadeCounter/particles[i].fadeLength
					End
				End
				particles[i].Draw()
			End
		Next
	End

	Function UpdateAll:Void()
		If minIndex < 0 Or maxIndex < 0 Then Return
		Local newMinIndex:Int = -1
		Local newMaxIndex:Int = -1
		For Local i:Int = minIndex to maxIndex
			if particles[i] <> null And particles[i].image <> null
				If particles[i].active
					particles[i].Update()
					If particles[i].active Then
						If newMinIndex < 0 Then newMinIndex = i
						newMaxIndex = i
					Else
						lastDeath = i
						particleCount -= 1
					End
				End
			End
		Next
		minIndex = newMinIndex
		maxIndex = newMaxIndex
	End

	Method Update:Void()
		Super.Move()

		If fadeIn Then
			fadeCounter+=dt.delta

			If fadeCounter >= fadeInLength Then
				fadeCounter = fadeLength
				fadeIn = 0
				alpha = 1
			End
		ElseIf fadeCounter>0 Then
			fadeCounter-=dt.delta
			If fadeCounter <= 0 Then
				alpha = 0
				active = 0
			End
		End
	End

End


AssetLoader.LoadImage is just an alias for GetImage

Strict

Private

Import bono
Import mojo
Import os

Public

Class AssetLoader Implements ImageLoader
    Private

    Field imageLoader:ImageLoader
    Field names := New StringMap<Int>
    Field textures := New StringMap<TexturePacker>

    Global preloadColor := New Color(0, 0, 0, 0)
    Const TYPE_IMAGE := 0
    Const TYPE_TEXTURE := 1

    Public

    Field ignoreCase := True
    Field ignoreExt := True

    Method New(imageLoader:ImageLoader=Null)
        If Not imageLoader
            imageLoader = New CachedImageLoader(New MojoImageLoader())
        End

        Self.imageLoader = imageLoader
    End

    Method Contains:Bool(name:String)
        Try
            ResolveNameToFile(name)
            Return True
        Catch ex:InvalidArgumentException
        End
        Return False
    End

    Method GetNames:MapKeys<String, Int>()
        Return names.Keys()
    End

    Method GetImage:Image(name:String)
        Local realName := ResolveNameToFile(name)
        Local mode := names.Get(realName)

        If mode = TYPE_IMAGE Then Return GetImageImage(realName)
        Return GetImageTexture(realName)
    End

    Method GetSprite:Sprite(name:String)
        Return New Sprite(GetImage(name))
    End

    ' --- Data import

    Method Add:Void(file:String)
        ' TODO already exists?
        names.Set(file, TYPE_IMAGE)
    End

    Method Add:Void(atlas:TexturePacker, namespace:String="")
        ' TODO already exists?
        Local base := TrimExt(atlas.GetFilename())
        textures.Set(base, atlas)

        For Local name := EachIn atlas.GetNames()
            names.Set(base + "/" + name, TYPE_TEXTURE)
        End
    End

    ' --- ImageLoader interface

    Method LoadImage:Image(file:String)
        Return GetImage(file)
    End

    ' --- Preload

    Method Preload:Void(img:Image)
        preloadColor.Activate()
        DrawImage(img, 0, 0)
        preloadColor.Deactivate()
    End

    Method PreloadAll:Void()
        For Local name := EachIn GetNames()
            If names.Get(name) <> TYPE_IMAGE Then Continue
            Preload(GetImage(name))
        End

        For Local name := EachIn textures.Keys()
            Preload(textures.Get(name).GetRootImage())
        End
    End

    ' --- Configure

    Method ConfigureAll:Void()
        ConfigureSprite()
        ConfigureTexturePacker()
    End

    Method ConfigureSprite:Void()
        Sprite.imageLoader = Self
    End

    Method ConfigureTexturePacker:Void()
        TexturePacker.defaultImageLoader = Self
    End

    Private

    Method ResolveNameToFile:String(name:String)
        name = ConvertCase(name)
        For Local rawImageName := EachIn GetNames()
            Local imageName := ConvertCase(rawImageName)
            If imageName = name Then Return rawImageName
            If ignoreExt And TrimExt(imageName) = name Then Return rawImageName
        End

        Throw New InvalidArgumentException("There is no asset named " + name)
    End

    Method GetImageImage:Image(name:String)
        Return LoadImageRaw(name)
    End

    Method GetImageTexture:Image(name:String)
        For Local node := EachIn textures
            If Not name.StartsWith(node.Key()) Then Continue

            Local atlas := node.Value()
            Local atlasName := name[node.Key().Length() + 1..]
            If atlas.Contains(atlasName) Then Return atlas.Get(atlasName)
        End

        Return Null
    End

    Method ConvertCase:String(in:String)
        If ignoreCase Then Return in.ToLower()
        Return in
    End

    Function TrimExt:String(in:String)
        Local pos := in.FindLast(".")
        If pos <> -1 Then Return in[..pos]
        Return in
    End

    Method LoadImageRaw:Image(file:String)
        Return imageLoader.LoadImage(file)
    End
End

Strict

Private

Import bono
Import mojo
Import os

Public

Class AssetLoader Implements ImageLoader
    Private

    Field imageLoader:ImageLoader
    Field names := New StringMap<Int>
    Field textures := New StringMap<TexturePacker>

    Global preloadColor := New Color(0, 0, 0, 0)
    Const TYPE_IMAGE := 0
    Const TYPE_TEXTURE := 1

    Public

    Field ignoreCase := True
    Field ignoreExt := True

    Method New(imageLoader:ImageLoader=Null)
        If Not imageLoader
            imageLoader = New CachedImageLoader(New MojoImageLoader())
        End

        Self.imageLoader = imageLoader
    End

    Method Contains:Bool(name:String)
        Try
            ResolveNameToFile(name)
            Return True
        Catch ex:InvalidArgumentException
        End
        Return False
    End

    Method GetNames:MapKeys<String, Int>()
        Return names.Keys()
    End

    Method GetImage:Image(name:String)
        Local realName := ResolveNameToFile(name)
        Local mode := names.Get(realName)

        If mode = TYPE_IMAGE Then Return GetImageImage(realName)
        Return GetImageTexture(realName)
    End

    Method GetSprite:Sprite(name:String)
        Return New Sprite(GetImage(name))
    End

    ' --- Data import

    Method Add:Void(file:String)
        ' TODO already exists?
        names.Set(file, TYPE_IMAGE)
    End

    Method Add:Void(atlas:TexturePacker, namespace:String="")
        ' TODO already exists?
        Local base := TrimExt(atlas.GetFilename())
        textures.Set(base, atlas)

        For Local name := EachIn atlas.GetNames()
            names.Set(base + "/" + name, TYPE_TEXTURE)
        End
    End

    ' --- ImageLoader interface

    Method LoadImage:Image(file:String)
        Return imageLoader.LoadImage(file)
    End

    ' --- Preload

    Method Preload:Void(img:Image)
        preloadColor.Activate()
        DrawImage(img, 0, 0)
        preloadColor.Deactivate()
    End

    Method PreloadAll:Void()
        For Local name := EachIn GetNames()
            If names.Get(name) <> TYPE_IMAGE Then Continue
            Preload(GetImage(name))
        End

        For Local name := EachIn textures.Keys()
            Preload(textures.Get(name).GetRootImage())
        End
    End

    ' --- Configure

    Method ConfigureAll:Void()
        ConfigureSprite()
        ConfigureTexturePacker()
    End

    Method ConfigureSprite:Void()
        Sprite.imageLoader = Self
    End

    Method ConfigureTexturePacker:Void()
        TexturePacker.defaultImageLoader = Self
    End

    Private

    Method ResolveNameToFile:String(name:String)
        name = ConvertCase(name)
        For Local rawImageName := EachIn GetNames()
            Local imageName := ConvertCase(rawImageName)
            If imageName = name Then Return rawImageName
            If ignoreExt And TrimExt(imageName) = name Then Return rawImageName
        End

        Throw New InvalidArgumentException("There is no asset named " + name)
    End

    Method GetImageImage:Image(name:String)
        Return LoadImage(name)
    End

    Method GetImageTexture:Image(name:String)
        For Local node := EachIn textures
            If Not name.StartsWith(node.Key()) Then Continue

            Local atlas := node.Value()
            Local atlasName := name[node.Key().Length() + 1..]
            If atlas.Contains(atlasName) Then Return atlas.Get(atlasName)
        End

        Return Null
    End

    Method ConvertCase:String(in:String)
        If ignoreCase Then Return in.ToLower()
        Return in
    End

    Function TrimExt:String(in:String)
        Local pos := in.FindLast(".")
        If pos <> -1 Then Return in[..pos]
        Return in
    End
End


FlxQuadTree null error fixed #65

FlxQuadTree null error fixed #65

Strict

Import flixel.flxbasic
Import flixel.flxgroup
Import flixel.flxobject
Import flixel.flxrect

Import flxlist

Class FlxQuadTree Extends FlxRect

	Const A_LIST:Int = 0

	Const B_LIST:Int = 1

	Global Divisions:Int

	Field exists:Bool

Private
	Field _canSubdivide:Bool

	Field _headA:FlxList

	Field _tailA:FlxList

	Field _headB:FlxList

	Field _tailB:FlxList

	Field _northWestTree:FlxQuadTree

	Field _northEastTree:FlxQuadTree

	Field _southEastTree:FlxQuadTree

	Field _southWestTree:FlxQuadTree

	Field _leftEdge:Float

	Field _rightEdge:Float

	Field _topEdge:Float

	Field _bottomEdge:Float

	Field _halfWidth:Float

	Field _halfHeight:Float

	Field _midpointX:Float

	Field _midpointY:Float

	Field _listsCache:FlxListsCache

	Global _Min:Int

	Global _Object:FlxObject

	Global _ObjectLeftEdge:Float

	Global _ObjectTopEdge:Float

	Global _ObjectRightEdge:Float

	Global _ObjectBottomEdge:Float

	Global _List:Int

	Global _UseBothLists:Bool

	Global _ProcessingCallback:FlxOverlapProcessListener

	Global _NotifyCallback:FlxOverlapNotifyListener

	Global _Iterator:FlxList

	Global _ObjectHullX:Float

	Global _ObjectHullY:Float

	Global _ObjectHullWidth:Float

	Global _ObjectHullHeight:Float

	Global _CheckObjectHullX:Float

	Global _CheckObjectHullY:Float

	Global _CheckObjectHullWidth:Float

	Global _CheckObjectHullHeight:Float

	Global _TreesCache:FlxQuadTreesCache = New FlxQuadTreesCache()

Public
	Method New(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		_listsCache = New FlxListsCache()
		_Reset(x, y, width, height, parent)
	End Method

	Method Destroy:Void()
		_headA.Destroy()
		_headA = Null
		_tailA.Destroy()
		_tailA = Null
		_headB.Destroy()
		_headB = Null
		_tailB.Destroy()
		_tailB = Null

		If (_northWestTree <> Null) _northWestTree.Destroy()
		_northWestTree = Null

		If (_northEastTree <> Null) _northEastTree.Destroy()
		_northEastTree = Null

		If (_southEastTree <> Null) _southEastTree.Destroy()
		_southEastTree = Null

		If (_southWestTree <> Null) _southWestTree.Destroy()
		_southWestTree = Null

		_Object = Null
		_ProcessingCallback = Null
		_NotifyCallback = Null

		exists = False
	End Method

	Method Load:Void(objectOrGroup1:FlxBasic, objectOrGroup2:FlxBasic = Null, notifyCallback:FlxOverlapNotifyListener, processCallback:FlxOverlapProcessListener = Null)
		Add(objectOrGroup1, A_LIST)

		If (objectOrGroup2 <> Null) Then
			Add(objectOrGroup2, B_LIST)
			_UseBothLists = True
		Else
			_UseBothLists = False
		End If

		_NotifyCallback = notifyCallback
		_ProcessingCallback = processCallback
	End Method

	Method Add:Void(objectOrGroup:FlxBasic, list:Int)
		_List = list

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

			While (i < l)
				basic = members[i]

				If (basic <> Null And basic.exists) Then
					If (FlxGroup(basic) <> Null) Then
						Add(basic, list)

					ElseIf (FlxObject(basic) <> Null)
						_Object = FlxObject(basic)

						If (_Object.exists And _Object.allowCollisions) Then
							_ObjectLeftEdge = _Object.x
							_ObjectTopEdge = _Object.y
							_ObjectRightEdge = _Object.x + _Object.width
							_ObjectBottomEdge = _Object.y + _Object.height
							_AddObject()
						End If
					End If
				End If
				i += 1
			Wend
		Else
			_Object = FlxObject(objectOrGroup)

			If (_Object.exists And _Object.allowCollisions) Then
				_ObjectLeftEdge = _Object.x
				_ObjectTopEdge = _Object.y
				_ObjectRightEdge = _Object.x + _Object.width
				_ObjectBottomEdge = _Object.y + _Object.height
				_AddObject()
			End If
		End If
	End Method

	Method Execute:Bool()
		Local overlapProcessed:Bool = False
		Local iterator:FlxList

		If (_headA.object <> Null) Then
			iterator = _headA

			While (iterator <> Null)
				_Object = iterator.object

				If (_UseBothLists) Then
					_Iterator = _headB
				Else
					_Iterator = iterator.nextLink
				End If

				If (_Object.exists And _Object.allowCollisions > 0 And _Iterator <> Null And _Iterator.object <> Null And _Iterator.object.exists And _OverlapNode()) Then
					overlapProcessed = True
				End If

				iterator = iterator.nextLink
			Wend
		End If

		If (_northWestTree <> Null And _northWestTree.Execute()) Then
			overlapProcessed = True
		End If

		If (_northEastTree <> Null And _northEastTree.Execute()) Then
			overlapProcessed = True
		End If

		If (_southEastTree <> Null And _southEastTree.Execute()) Then
			overlapProcessed = True
		End If

		If (_southWestTree <> Null And _southWestTree.Execute()) Then
			overlapProcessed = True
		End If

		Return overlapProcessed
	End Method

	Function Recycle:FlxQuadTree(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		Return _TreesCache.Recycle(x, y, width, height, parent)
	End Function

Private
	Method _Reset:Void(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		exists = True

		Make(x, y, width, height)

		_tailA = _listsCache.Recycle()
		_headA = _tailA

		_tailB = _listsCache.Recycle()
		_headB = _tailB

		If (parent <> Null) Then
			Local iterator:FlxList
			Local ot:FlxList

			If (parent._headA.object <> Null) Then
				iterator = parent._headA

				While (iterator <> Null)
					If (_tailA.object <> Null) Then
						ot = _tailA
						_tailA = _listsCache.Recycle()
						ot.nextLink = _tailA
					End If

					_tailA.object = iterator.object
					iterator = iterator.nextLink
				Wend
			End If

			If (parent._headB.object <> Null) Then
				iterator = parent._headB

				While (iterator <> Null)
					If (_tailB.object <> Null) Then
						ot = _tailB
						_tailB = _listsCache.Recycle()
						ot.nextLink = _tailB
					End If

					_tailB.object = iterator.object
					iterator = iterator.nextLink
				Wend
			End If
		Else
			_Min = (width + height) / (2 * Divisions)
		End If

		_canSubdivide = (width > _Min Or height > _Min)

		_northWestTree = Null
		_northEastTree = Null
		_southEastTree = Null
		_southWestTree = Null
		_leftEdge = x
		_rightEdge = x + width
		_halfWidth = width / 2
		_midpointX = _leftEdge + _halfWidth
		_topEdge = y
		_bottomEdge = y + height
		_halfHeight = height / 2
		_midpointY = _topEdge + _halfHeight
	End Method

	Method _AddObject:Void()
		If (Not _canSubdivide Or (_leftEdge >= _ObjectLeftEdge And _rightEdge <= _ObjectRightEdge And _topEdge >= _ObjectTopEdge And _bottomEdge <= _ObjectBottomEdge)) Then
			_AddToList()
			Return
		End If

		If (_ObjectLeftEdge > _leftEdge And _ObjectRightEdge < _midpointX) Then
			If (_ObjectTopEdge > _topEdge And _ObjectBottomEdge < _midpointY) Then
				If (_northWestTree = Null) Then
					_northWestTree = _TreesCache.Recycle(_leftEdge, _topEdge, _halfWidth, _halfHeight, Self)
				End If

				_northWestTree._AddObject()
				Return
			End If

			If (_ObjectTopEdge > _midpointY And _ObjectBottomEdge < _bottomEdge) Then
				If (_southWestTree = Null) Then
					_southWestTree = _TreesCache.Recycle(_leftEdge, _midpointY, _halfWidth, _halfHeight, Self)
				End If

				_southWestTree._AddObject()
				Return
			End If
		End If

		If (_ObjectLeftEdge > _midpointX And _ObjectRightEdge < _rightEdge) Then
			If (_ObjectTopEdge > _topEdge And _ObjectBottomEdge < _midpointY) Then
				If (_northEastTree = Null) Then
					_northEastTree = _TreesCache.Recycle(_midpointX, _topEdge, _halfWidth, _halfHeight, Self)
				End If

				_northEastTree._AddObject()
				Return
			End If

			If (_ObjectTopEdge > _midpointY And _ObjectBottomEdge < _bottomEdge) Then
				If (_southEastTree = Null) Then
					_southEastTree = _TreesCache.Recycle(_midpointX, _midpointY, _halfWidth, _halfHeight, Self)
				End If

				_southEastTree._AddObject()
				Return
			End If
		End If

		If (_ObjectRightEdge > _leftEdge And _ObjectLeftEdge < _midpointX And _ObjectBottomEdge > _topEdge And _ObjectTopEdge < _midpointY) Then
			If (_northWestTree = Null) Then
				_northWestTree = _TreesCache.Recycle(_leftEdge, _topEdge, _halfWidth, _halfHeight, Self)
			End If

			_northWestTree._AddObject()
		End If

		If (_ObjectRightEdge > _midpointX And _ObjectLeftEdge < _rightEdge And _ObjectBottomEdge > _topEdge And _ObjectTopEdge < _midpointY) Then
			If (_northEastTree = Null) Then
				_northEastTree = _TreesCache.Recycle(_midpointX, _topEdge, _halfWidth, _halfHeight, Self)
			End If

			_northEastTree._AddObject()
		End If

		If (_ObjectRightEdge > _midpointX And _ObjectLeftEdge < _rightEdge And _ObjectBottomEdge > _midpointY And _ObjectTopEdge < _bottomEdge) Then
			If (_southEastTree = Null) Then
				_southEastTree = _TreesCache.Recycle(_midpointX, _midpointY, _halfWidth, _halfHeight, Self)
			End If

			_southEastTree._AddObject()
		End If

		If (_ObjectRightEdge > _leftEdge And _ObjectLeftEdge < _midpointX And _ObjectBottomEdge > _midpointY And _ObjectTopEdge < _bottomEdge) Then
			If (_southWestTree = Null) Then
				_southWestTree = _TreesCache.Recycle(_leftEdge, _midpointY, _halfWidth, _halfHeight, Self)
			End If

			_southWestTree._AddObject()
		End If
	End Method

	Method _AddToList:Void()
		Local ot:FlxList

		If (_List = A_LIST) Then
			If (_tailA.object <> Null) Then
				ot = _tailA
				_tailA = _listsCache.Recycle()
				ot.nextLink = _tailA
			End If

			_tailA.object = _Object
		Else
			If (_tailB.object <> Null) Then
				ot = _tailB
				_tailB = _listsCache.Recycle()
				ot.nextLink = _tailB
			End If

			_tailB.object = _Object
		End If

		If (Not _canSubdivide) Return

		If (_northWestTree <> Null) _northWestTree._AddToList()
		If (_northEastTree <> Null) _northEastTree._AddToList()
		If (_southEastTree <> Null) _southEastTree._AddToList()
		If (_southWestTree <> Null) _southWestTree._AddToList()
	End Method

	Method _OverlapNode:Bool()
		Local overlapProcessed:Bool = False
		Local checkObject:FlxObject

		While (_Iterator <> Null)
			If (Not _Object.exists Or _Object.allowCollisions <= 0) Exit

			checkObject = _Iterator.object

			If (_Object = checkObject Or Not checkObject.exists Or checkObject.allowCollisions <= 0) Then
				_Iterator = _Iterator.nextLink
				Continue
			End If

			If (_Object.x < _Object.last.x) Then
				_ObjectHullX = _Object.x
			Else
				_ObjectHullX = _Object.last.x
			End If

			If (_Object.y < _Object.last.y) Then
				_ObjectHullY = _Object.y
			Else
				_ObjectHullY = _Object.last.y
			End If

			_ObjectHullWidth = _Object.x - _Object.last.x

			If (_ObjectHullWidth > 0) Then
				_ObjectHullWidth = _Object.width + _ObjectHullWidth
			Else
				_ObjectHullWidth = _Object.width - _ObjectHullWidth
			End If

			_ObjectHullHeight = _Object.y - _Object.last.y

			If (_ObjectHullHeight > 0) Then
				_ObjectHullHeight = _Object.height + _ObjectHullHeight
			Else
				_ObjectHullHeight = _Object.height - _ObjectHullHeight
			End If

			If (checkObject.x < checkObject.last.x) Then
				_CheckObjectHullX = checkObject.x
			Else
				_CheckObjectHullX = checkObject.last.x
			End If

			If (checkObject.y < checkObject.last.y) Then
				_CheckObjectHullY = checkObject.y
			Else
				_CheckObjectHullY = checkObject.last.y
			End If

			_CheckObjectHullWidth = checkObject.x - checkObject.last.x

			If (_CheckObjectHullWidth > 0) Then
				_CheckObjectHullWidth = checkObject.width + _CheckObjectHullWidth
			Else
				_CheckObjectHullWidth = checkObject.width - _CheckObjectHullWidth
			End If

			_CheckObjectHullHeight = checkObject.y - checkObject.last.y

			If (_CheckObjectHullHeight > 0) Then
				_CheckObjectHullHeight = checkObject.height + _CheckObjectHullHeight
			Else
				_CheckObjectHullHeight = checkObject.height - _CheckObjectHullHeight
			End If

			If (_ObjectHullX + _ObjectHullWidth > _CheckObjectHullX And _ObjectHullX < _CheckObjectHullX + _CheckObjectHullWidth And _ObjectHullY + _ObjectHullHeight > _CheckObjectHullY And _ObjectHullY < _CheckObjectHullY + _CheckObjectHullHeight) Then
				If (_ProcessingCallback = Null Or _ProcessingCallback.OnOverlapProcess(_Object, checkObject)) Then
					overlapProcessed = True
				End If

				If (overlapProcessed And _NotifyCallback <> Null) Then
					_NotifyCallback.OnOverlapNotify(_Object, checkObject)
				End If
			End If

			If (_Iterator <> Null) Then
				_Iterator = _Iterator.nextLink
			End If
		Wend

		Return overlapProcessed
	End Method

End Class

Interface FlxOverlapNotifyListener

	Method OnOverlapNotify:Void(object1:FlxObject, object2:FlxObject)

End Interface

Interface FlxOverlapProcessListener

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

End Interface

Private

Class FlxListsCache

Private
	Field _lists:FlxList[]

	Field _length:Int

Public
	Method New()
		_length = 0
	End Method

	Method Add:FlxList(list:FlxList)
		If (_length = _lists.Length()) Then
			_lists = _lists.Resize(_length * 2 + 10)
		End If

		_lists[_length] = list
		_length += 1

		Return list
	End Method

	Method Recycle:FlxList()
		Local list:FlxList
		Local i:Int = 0

		While(i < _length)
			list = _lists[i]
			If (Not list.exists) Then
				list.exists = True
				Return list
			End If
			i+=1
		Wend

		Return Add(New FlxList())
	End Method

End Class

Class FlxQuadTreesCache

Private
	Field _trees:FlxQuadTree[]

	Field _length:Int

Public
	Method New()
		_length = 0
	End Method

	Method Add:FlxQuadTree(tree:FlxQuadTree)
		If (_length = _trees.Length()) Then
			_trees = _trees.Resize(_length * 2 + 10)
		End If

		_trees[_length] = tree
		_length += 1

		Return tree
	End Method

	Method Recycle:FlxQuadTree(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		Local tree:FlxQuadTree
		Local i:Int = 0

		While(i < _length)
			tree = _trees[i]
			If (Not tree.exists) Then
				tree._Reset(x, y, width, height, parent)
				Return tree
			End If
			i+=1
		Wend

		Return Add(New FlxQuadTree(x, y, width, height, parent))
	End Method

End Class

Strict

Import flixel.flxbasic
Import flixel.flxgroup
Import flixel.flxobject
Import flixel.flxrect

Import flxlist

Class FlxQuadTree Extends FlxRect

	Const A_LIST:Int = 0

	Const B_LIST:Int = 1

	Global Divisions:Int

	Field exists:Bool

Private
	Field _canSubdivide:Bool

	Field _headA:FlxList

	Field _tailA:FlxList

	Field _headB:FlxList

	Field _tailB:FlxList

	Field _northWestTree:FlxQuadTree

	Field _northEastTree:FlxQuadTree

	Field _southEastTree:FlxQuadTree

	Field _southWestTree:FlxQuadTree

	Field _leftEdge:Float

	Field _rightEdge:Float

	Field _topEdge:Float

	Field _bottomEdge:Float

	Field _halfWidth:Float

	Field _halfHeight:Float

	Field _midpointX:Float

	Field _midpointY:Float

	Field _listsCache:FlxListsCache

	Global _Min:Int

	Global _Object:FlxObject

	Global _ObjectLeftEdge:Float

	Global _ObjectTopEdge:Float

	Global _ObjectRightEdge:Float

	Global _ObjectBottomEdge:Float

	Global _List:Int

	Global _UseBothLists:Bool

	Global _ProcessingCallback:FlxOverlapProcessListener

	Global _NotifyCallback:FlxOverlapNotifyListener

	Global _Iterator:FlxList

	Global _ObjectHullX:Float

	Global _ObjectHullY:Float

	Global _ObjectHullWidth:Float

	Global _ObjectHullHeight:Float

	Global _CheckObjectHullX:Float

	Global _CheckObjectHullY:Float

	Global _CheckObjectHullWidth:Float

	Global _CheckObjectHullHeight:Float

	Global _TreesCache:FlxQuadTreesCache = New FlxQuadTreesCache()

Public
	Method New(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		_listsCache = New FlxListsCache()
		_Reset(x, y, width, height, parent)
	End Method

	Method Destroy:Void()
		_headA.Destroy()
		_headA = Null
		_tailA.Destroy()
		_tailA = Null
		_headB.Destroy()
		_headB = Null
		_tailB.Destroy()
		_tailB = Null

		If (_northWestTree <> Null) _northWestTree.Destroy()
		_northWestTree = Null

		If (_northEastTree <> Null) _northEastTree.Destroy()
		_northEastTree = Null

		If (_southEastTree <> Null) _southEastTree.Destroy()
		_southEastTree = Null

		If (_southWestTree <> Null) _southWestTree.Destroy()
		_southWestTree = Null

		_Object = Null
		_ProcessingCallback = Null
		_NotifyCallback = Null

		exists = False
	End Method

	Method Load:Void(objectOrGroup1:FlxBasic, objectOrGroup2:FlxBasic = Null, notifyCallback:FlxOverlapNotifyListener, processCallback:FlxOverlapProcessListener = Null)
		Add(objectOrGroup1, A_LIST)

		If (objectOrGroup2 <> Null) Then
			Add(objectOrGroup2, B_LIST)
			_UseBothLists = True
		Else
			_UseBothLists = False
		End If

		_NotifyCallback = notifyCallback
		_ProcessingCallback = processCallback
	End Method

	Method Add:Void(objectOrGroup:FlxBasic, list:Int)
		_List = list

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

			While (i < l)
				basic = members[i]

				If (basic <> Null And basic.exists) Then
					If (FlxGroup(basic) <> Null) Then
						Add(basic, list)

					ElseIf (FlxObject(basic) <> Null)
						_Object = FlxObject(basic)

						If (_Object.exists And _Object.allowCollisions) Then
							_ObjectLeftEdge = _Object.x
							_ObjectTopEdge = _Object.y
							_ObjectRightEdge = _Object.x + _Object.width
							_ObjectBottomEdge = _Object.y + _Object.height
							_AddObject()
						End If
					End If
				End If
				i += 1
			Wend
		Else
			_Object = FlxObject(objectOrGroup)

			If (_Object.exists And _Object.allowCollisions) Then
				_ObjectLeftEdge = _Object.x
				_ObjectTopEdge = _Object.y
				_ObjectRightEdge = _Object.x + _Object.width
				_ObjectBottomEdge = _Object.y + _Object.height
				_AddObject()
			End If
		End If
	End Method

	Method Execute:Bool()
		Local overlapProcessed:Bool = False
		Local iterator:FlxList

		If (_headA.object <> Null) Then
			iterator = _headA

			While (iterator <> Null)
				_Object = iterator.object

				If (_UseBothLists) Then
					_Iterator = _headB
				Else
					_Iterator = iterator.nextLink
				End If

				If (_Object.exists And _Object.allowCollisions > 0 And _Iterator <> Null And _Iterator.object <> Null And _Iterator.object.exists And _OverlapNode()) Then
					overlapProcessed = True
				End If

				iterator = iterator.nextLink
			Wend
		End If

		If (_northWestTree <> Null And _northWestTree.Execute()) Then
			overlapProcessed = True
		End If

		If (_northEastTree <> Null And _northEastTree.Execute()) Then
			overlapProcessed = True
		End If

		If (_southEastTree <> Null And _southEastTree.Execute()) Then
			overlapProcessed = True
		End If

		If (_southWestTree <> Null And _southWestTree.Execute()) Then
			overlapProcessed = True
		End If

		Return overlapProcessed
	End Method

	Function Recycle:FlxQuadTree(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		Return _TreesCache.Recycle(x, y, width, height, parent)
	End Function

Private
	Method _Reset:Void(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		exists = True

		Make(x, y, width, height)

		_tailA = _listsCache.Recycle()
		_headA = _tailA

		_tailB = _listsCache.Recycle()
		_headB = _tailB

		If (parent <> Null) Then
			Local iterator:FlxList
			Local ot:FlxList

			If (parent._headA.object <> Null) Then
				iterator = parent._headA

				While (iterator <> Null)
					If (_tailA.object <> Null) Then
						ot = _tailA
						_tailA = _listsCache.Recycle()
						ot.nextLink = _tailA
					End If

					_tailA.object = iterator.object
					iterator = iterator.nextLink
				Wend
			End If

			If (parent._headB.object <> Null) Then
				iterator = parent._headB

				While (iterator <> Null)
					If (_tailB.object <> Null) Then
						ot = _tailB
						_tailB = _listsCache.Recycle()
						ot.nextLink = _tailB
					End If

					_tailB.object = iterator.object
					iterator = iterator.nextLink
				Wend
			End If
		Else
			_Min = (width + height) / (2 * Divisions)
		End If

		_canSubdivide = (width > _Min Or height > _Min)

		_northWestTree = Null
		_northEastTree = Null
		_southEastTree = Null
		_southWestTree = Null
		_leftEdge = x
		_rightEdge = x + width
		_halfWidth = width / 2
		_midpointX = _leftEdge + _halfWidth
		_topEdge = y
		_bottomEdge = y + height
		_halfHeight = height / 2
		_midpointY = _topEdge + _halfHeight
	End Method

	Method _AddObject:Void()
		If (Not _canSubdivide Or (_leftEdge >= _ObjectLeftEdge And _rightEdge <= _ObjectRightEdge And _topEdge >= _ObjectTopEdge And _bottomEdge <= _ObjectBottomEdge)) Then
			_AddToList()
			Return
		End If

		If (_ObjectLeftEdge > _leftEdge And _ObjectRightEdge < _midpointX) Then
			If (_ObjectTopEdge > _topEdge And _ObjectBottomEdge < _midpointY) Then
				If (_northWestTree = Null) Then
					_northWestTree = _TreesCache.Recycle(_leftEdge, _topEdge, _halfWidth, _halfHeight, Self)
				End If

				_northWestTree._AddObject()
				Return
			End If

			If (_ObjectTopEdge > _midpointY And _ObjectBottomEdge < _bottomEdge) Then
				If (_southWestTree = Null) Then
					_southWestTree = _TreesCache.Recycle(_leftEdge, _midpointY, _halfWidth, _halfHeight, Self)
				End If

				_southWestTree._AddObject()
				Return
			End If
		End If

		If (_ObjectLeftEdge > _midpointX And _ObjectRightEdge < _rightEdge) Then
			If (_ObjectTopEdge > _topEdge And _ObjectBottomEdge < _midpointY) Then
				If (_northEastTree = Null) Then
					_northEastTree = _TreesCache.Recycle(_midpointX, _topEdge, _halfWidth, _halfHeight, Self)
				End If

				_northEastTree._AddObject()
				Return
			End If

			If (_ObjectTopEdge > _midpointY And _ObjectBottomEdge < _bottomEdge) Then
				If (_southEastTree = Null) Then
					_southEastTree = _TreesCache.Recycle(_midpointX, _midpointY, _halfWidth, _halfHeight, Self)
				End If

				_southEastTree._AddObject()
				Return
			End If
		End If

		If (_ObjectRightEdge > _leftEdge And _ObjectLeftEdge < _midpointX And _ObjectBottomEdge > _topEdge And _ObjectTopEdge < _midpointY) Then
			If (_northWestTree = Null) Then
				_northWestTree = _TreesCache.Recycle(_leftEdge, _topEdge, _halfWidth, _halfHeight, Self)
			End If

			_northWestTree._AddObject()
		End If

		If (_ObjectRightEdge > _midpointX And _ObjectLeftEdge < _rightEdge And _ObjectBottomEdge > _topEdge And _ObjectTopEdge < _midpointY) Then
			If (_northEastTree = Null) Then
				_northEastTree = _TreesCache.Recycle(_midpointX, _topEdge, _halfWidth, _halfHeight, Self)
			End If

			_northEastTree._AddObject()
		End If

		If (_ObjectRightEdge > _midpointX And _ObjectLeftEdge < _rightEdge And _ObjectBottomEdge > _midpointY And _ObjectTopEdge < _bottomEdge) Then
			If (_southEastTree = Null) Then
				_southEastTree = _TreesCache.Recycle(_midpointX, _midpointY, _halfWidth, _halfHeight, Self)
			End If

			_southEastTree._AddObject()
		End If

		If (_ObjectRightEdge > _leftEdge And _ObjectLeftEdge < _midpointX And _ObjectBottomEdge > _midpointY And _ObjectTopEdge < _bottomEdge) Then
			If (_southWestTree = Null) Then
				_southWestTree = _TreesCache.Recycle(_leftEdge, _midpointY, _halfWidth, _halfHeight, Self)
			End If

			_southWestTree._AddObject()
		End If
	End Method

	Method _AddToList:Void()
		Local ot:FlxList

		If (_List = A_LIST) Then
			If (_tailA.object <> Null) Then
				ot = _tailA
				_tailA = _listsCache.Recycle()
				ot.nextLink = _tailA
			End If

			_tailA.object = _Object
		Else
			If (_tailB.object <> Null) Then
				ot = _tailB
				_tailB = _listsCache.Recycle()
				ot.nextLink = _tailB
			End If

			_tailB.object = _Object
		End If

		If (Not _canSubdivide) Return

		If (_northWestTree <> Null) _northWestTree._AddToList()
		If (_northEastTree <> Null) _northEastTree._AddToList()
		If (_southEastTree <> Null) _southEastTree._AddToList()
		If (_southWestTree <> Null) _southWestTree._AddToList()
	End Method

	Method _OverlapNode:Bool()
		Local overlapProcessed:Bool = False
		Local checkObject:FlxObject

		While (_Iterator <> Null)
			If (Not _Object.exists Or _Object.allowCollisions <= 0) Exit

			checkObject = _Iterator.object

			If (_Object = checkObject Or Not checkObject.exists Or checkObject.allowCollisions <= 0) Then
				_Iterator = _Iterator.nextLink
				Continue
			End If

			If (_Object.x < _Object.last.x) Then
				_ObjectHullX = _Object.x
			Else
				_ObjectHullX = _Object.last.x
			End If

			If (_Object.y < _Object.last.y) Then
				_ObjectHullY = _Object.y
			Else
				_ObjectHullY = _Object.last.y
			End If

			_ObjectHullWidth = _Object.x - _Object.last.x

			If (_ObjectHullWidth > 0) Then
				_ObjectHullWidth = _Object.width + _ObjectHullWidth
			Else
				_ObjectHullWidth = _Object.width - _ObjectHullWidth
			End If

			_ObjectHullHeight = _Object.y - _Object.last.y

			If (_ObjectHullHeight > 0) Then
				_ObjectHullHeight = _Object.height + _ObjectHullHeight
			Else
				_ObjectHullHeight = _Object.height - _ObjectHullHeight
			End If

			If (checkObject.x < checkObject.last.x) Then
				_CheckObjectHullX = checkObject.x
			Else
				_CheckObjectHullX = checkObject.last.x
			End If

			If (checkObject.y < checkObject.last.y) Then
				_CheckObjectHullY = checkObject.y
			Else
				_CheckObjectHullY = checkObject.last.y
			End If

			_CheckObjectHullWidth = checkObject.x - checkObject.last.x

			If (_CheckObjectHullWidth > 0) Then
				_CheckObjectHullWidth = checkObject.width + _CheckObjectHullWidth
			Else
				_CheckObjectHullWidth = checkObject.width - _CheckObjectHullWidth
			End If

			_CheckObjectHullHeight = checkObject.y - checkObject.last.y

			If (_CheckObjectHullHeight > 0) Then
				_CheckObjectHullHeight = checkObject.height + _CheckObjectHullHeight
			Else
				_CheckObjectHullHeight = checkObject.height - _CheckObjectHullHeight
			End If

			If (_ObjectHullX + _ObjectHullWidth > _CheckObjectHullX And _ObjectHullX < _CheckObjectHullX + _CheckObjectHullWidth And _ObjectHullY + _ObjectHullHeight > _CheckObjectHullY And _ObjectHullY < _CheckObjectHullY + _CheckObjectHullHeight) Then
				If (_ProcessingCallback = Null Or _ProcessingCallback.OnOverlapProcess(_Object, checkObject)) Then
					overlapProcessed = True
				End If

				If (overlapProcessed And _NotifyCallback <> Null) Then
					_NotifyCallback.OnOverlapNotify(_Object, checkObject)
				End If
			End If

			_Iterator = _Iterator.nextLink
		Wend

		Return overlapProcessed
	End Method

End Class

Interface FlxOverlapNotifyListener

	Method OnOverlapNotify:Void(object1:FlxObject, object2:FlxObject)

End Interface

Interface FlxOverlapProcessListener

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

End Interface

Private

Class FlxListsCache

Private
	Field _lists:FlxList[]

	Field _length:Int

Public
	Method New()
		_length = 0
	End Method

	Method Add:FlxList(list:FlxList)
		If (_length = _lists.Length()) Then
			_lists = _lists.Resize(_length * 2 + 10)
		End If

		_lists[_length] = list
		_length += 1

		Return list
	End Method

	Method Recycle:FlxList()
		Local list:FlxList
		Local i:Int = 0

		While(i < _length)
			list = _lists[i]
			If (Not list.exists) Then
				list.exists = True
				Return list
			End If
			i+=1
		Wend

		Return Add(New FlxList())
	End Method

End Class

Class FlxQuadTreesCache

Private
	Field _trees:FlxQuadTree[]

	Field _length:Int

Public
	Method New()
		_length = 0
	End Method

	Method Add:FlxQuadTree(tree:FlxQuadTree)
		If (_length = _trees.Length()) Then
			_trees = _trees.Resize(_length * 2 + 10)
		End If

		_trees[_length] = tree
		_length += 1

		Return tree
	End Method

	Method Recycle:FlxQuadTree(x:Float, y:Float, width:Float, height:Float, parent:FlxQuadTree = Null)
		Local tree:FlxQuadTree
		Local i:Int = 0

		While(i < _length)
			tree = _trees[i]
			If (Not tree.exists) Then
				tree._Reset(x, y, width, height, parent)
				Return tree
			End If
			i+=1
		Wend

		Return Add(New FlxQuadTree(x, y, width, height, parent))
	End Method

End Class