Added MusicPlay and music debugging

Added MusicPlay and music debugging

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

Strict

Import mojo
Import functions

'Device width and height
Global DEVICE_WIDTH%
Global DEVICE_HEIGHT%

' Screen width and height
Global SCREEN_WIDTH#
Global SCREEN_HEIGHT#

' Half of SCREEN_WIDTH and HEIGHT
Global SCREEN_WIDTH2#
Global SCREEN_HEIGHT2#

' Used for Virtual Res
Global SCREENX_RATIO# = 1
Global SCREENY_RATIO# = 1

' THE GAME!!
Global game:DiddyApp

' Used for delta timing movement
Global dt:DeltaTimer

Class DiddyApp Extends App

	Field debugOn:Bool = False
	Field drawFPSOn:Bool = False

	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
	Global images:ImageBank = New ImageBank
	' Store the sounds here
	Global sounds:SoundBank = New SoundBank
	' volume control
	Field soundVolume:Int = 100
	Field musicVolume:Int = 100
	Field musicOkay:Int

	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)
		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 screenFade.active then screenFade.Render()
		if virtualResOn
			PopMatrix
		End
		currentScreen.ExtraRender()
		If debugOn
			DrawDebug()
		End
		If drawFPSOn
			DrawFPS()
		End
		Return 0
	End

	Method OnUpdate:Int()
		dt.UpdateDelta()
		ScreenLogic()

		Return 0
	End

	Method ScreenLogic:Int()
		mouseX = MouseX() / SCREENX_RATIO
		mouseY = MouseY() / SCREENY_RATIO
		mouseHit = MouseHit()

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

	Method DrawDebug:Void()
		SetColor 255, 255, 255
		FPSCounter.Draw(0,0)
		DrawText "Screen         = "+currentScreen.name, 0, 10
		DrawText "Delta          = "+FormatNumber(dt.delta, 2) , 0, 20
		DrawText "Screen Width   = "+SCREEN_WIDTH, 0, 30
		DrawText "Screen Height  = "+SCREEN_HEIGHT, 0, 40
		DrawText "VMouseX        = "+Self.mouseX, 0, 50
		DrawText "VMouseY        = "+Self.mouseY, 0, 60
		DrawText "MouseX         = "+MouseX(), 0, 70
		DrawText "MouseY         = "+MouseY(), 0, 80
		DrawText "MusicOkay      = "+musicOkay, 0, 90
		DrawText "Music State    = "+MusicState(), 0, 100
		DrawText "Music Volume   = "+Self.musicVolume, 0, 110
		DrawText "Sound Volume   = "+Self.soundVolume, 0, 120
	End

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

	Method MusicPlay:Void(file:String, flags:Int=1)
		SetMusicVolume(musicVolume/100)
		musicOkay = PlayMusic("music/"+file, flags)
	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
		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))
			Next
		End
		If fadeMusic Then
			SetMusicVolume((ratio) * (game.musicVolume / 100))
		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 Then ratio = 0
		If ratio > 1 Then ratio = 1
		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, SCREEN_WIDTH, SCREEN_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

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:Void(name:String, nameoverride:String = "", midhandle:bool=true)
		Local i:GameImage = New GameImage
		i.Load(path + name, midhandle)

		If nameoverride <> "" Then i.name = nameoverride.ToUpper()
		Self.Set(i.name , i)
	End

	Method LoadAnim:Void(name:String, w%, h%, total%, tmpImage:Image, midhandle:Bool=true)
		Local i:GameImage = New GameImage
		i.LoadAnim(path + name, w, h, total, tmpImage, midhandle)
		Self.Set(i.name, 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$
	Field image:Image
	Field w%
	Field h%
	Field w2#
	Field h2#
	Field midhandled%=0

	Field leftMargin%=0
	Field rightMargin%=0
	Field topMargin%=0
	Field bottomMargin%=0

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

	Method LoadAnim:Void(file$, w%, h%, total%, tmpImage:Image, midhandle:Bool=true)
		name = StripAll(file.ToUpper())
		image = LoadAnimBitmap(file, w, h, total, tmpImage)
		CalcSize()
		MidHandle(midhandle)
	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#, y#, rotation# = 0, scaleX# = 1, scaleY# = 1, frame% = 0)
		DrawImage(self.image, x, y, rotation, scaleX, scaleY, frame)
	End

	Method DrawTiled:Void(x#, y#, w#, h#, scaleX# = 1, scaleY# = 1, frame% = 0)
	End

	Method DrawStretched:Void(x#, y#, rw#, rh#, frame% = 0)
	End

	Method DrawGrid:Void(x#, y#, rw#, rh#, frame% = 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 PreCache:Void()
		DrawImage self.image, -self.w-50, -self.h-50
	End
End

Class SoundBank Extends StringMap<GameSound>

	Global path$ = "sounds/"

	Method Load:Void(name:String, nameoverride:String = "")
		Local i:GameSound = New GameSound
		i.Load(name)

		If nameoverride <> "" Then i.name = nameoverride.ToUpper()
		Self.Set(i.name , i)
	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$
	Field sound:Sound
	Field rate# = 1
	Field pan# = 0
	Field volume# = 1
	Field loop% = 0

	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()
		SoundPlayer.PlayFx(sound, pan, rate, volume * (game.soundVolume / 100), loop)
	End
End

Class SoundPlayer
	Global channel:Int
	Const MAX_CHANNELS:Int = 31

	Function PlayFx:Void(s:Sound, pan#=0, rate#=1, volume#=1, loop% = 0)
		channel += 1
		If (channel > MAX_CHANNELS) Then channel = 0

		StopChannel(channel)
		PlaySound(s, channel, loop)
		SetChannelPan(channel, pan)
		SetChannelRate(channel, rate)
		SetChannelVolume(channel, volume)
	End
End

Class Sprite
	Field name$
	Field visible?
	Field x#, y#
	Field ox#, oy#
	Field dx#, dy#
	Field speedX#, speedY#, speed#
	Field maxXSpeed#, maxYSpeed#
	Field image:GameImage
	Field scaleX# = 1, scaleY# = 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%

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

	' Rotation
	Field rotationCounter#=0
	Field rotationLength%=1000
	Field rotationLoop% = 0
	Field rotation#, rotationSpeed# = 1

	Method New(img:GameImage,x#, y#)
		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%,g%,b%)
		Self.red = r
		Self.green = g
		Self.blue = b
	End

	Method SetScaleXY:Void(sx#, sy#)
		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#=0, offsety#=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% = 800
	Global particles:Particle[MAX_PARTICLES]
	Global lastDeath% = 0
	Global maxIndex% = -1
	Global minIndex% = -1
	Global particleCount% = 0
	Field lifeCounter# = 0
	Field fadeIn# = 0
	Field fadeCounter#
	Field fadeInLength# = 0
	Field fadeLength#=0
	Field active% = 0

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

	Function Create:Particle(gi:GameImage, x#, y#, dx#=0, dy#=0, gravity#=0, fadeLength#=0, lifeCounter%=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% = 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% = -1
		Local newMaxIndex% = -1
		For Local i% = 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

'Device width and height
Global DEVICE_WIDTH%
Global DEVICE_HEIGHT%

' Screen width and height
Global SCREEN_WIDTH#
Global SCREEN_HEIGHT#

' Half of SCREEN_WIDTH and HEIGHT
Global SCREEN_WIDTH2#
Global SCREEN_HEIGHT2#

' Used for Virtual Res
Global SCREENX_RATIO# = 1
Global SCREENY_RATIO# = 1

' THE GAME!!
Global game:DiddyApp

' Used for delta timing movement
Global dt:DeltaTimer

Class DiddyApp Extends App

	Field debugOn:Bool = False
	Field drawFPSOn:Bool = False

	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
	Global images:ImageBank = New ImageBank
	' Store the sounds here
	Global sounds:SoundBank = New SoundBank
	' volume control
	Field soundVolume:Int = 100
	Field musicVolume:Int = 100

	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)
		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 screenFade.active then screenFade.Render()
		if virtualResOn
			PopMatrix
		End
		currentScreen.ExtraRender()
		If debugOn
			DrawDebug()
		End
		If drawFPSOn
			DrawFPS()
		End
		Return 0
	End

	Method OnUpdate:Int()
		dt.UpdateDelta()
		ScreenLogic()

		Return 0
	End

	Method ScreenLogic:Int()
		mouseX = MouseX() / SCREENX_RATIO
		mouseY = MouseY() / SCREENY_RATIO
		mouseHit = MouseHit()

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

	Method DrawDebug:Void()
		SetColor 0, 0, 0
		DrawRect 0, 0, 200, 100
		SetColor 255, 255, 255
		FPSCounter.Draw(0,0)
		DrawText "Screen         = "+currentScreen.name,0, 10
		DrawText "Delta          = "+FormatNumber(dt.delta, 2) , 0, 20
		DrawText "Screen Width   = "+SCREEN_WIDTH,0, 30
		DrawText "Screen Height  = "+SCREEN_HEIGHT,0, 40
		DrawText "VMouseX        = "+Self.mouseX,0, 50
		DrawText "VMouseY        = "+Self.mouseY,0, 60
		DrawText "MouseX         = "+MouseX(),0, 70
		DrawText "MouseY         = "+MouseY(),0, 80
	End

	Method DrawFPS:Void()
		DrawText FPSCounter.totalFPS, 0, 0
	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
		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))
			Next
		End
		If fadeMusic Then
			SetMusicVolume((ratio) * (game.musicVolume / 100))
		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 Then ratio = 0
		If ratio > 1 Then ratio = 1
		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, SCREEN_WIDTH, SCREEN_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

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:Void(name:String, nameoverride:String = "", midhandle:bool=true)
		Local i:GameImage = New GameImage
		i.Load(path + name, midhandle)

		If nameoverride <> "" Then i.name = nameoverride.ToUpper()
		Self.Set(i.name , i)
	End

	Method LoadAnim:Void(name:String, w%, h%, total%, tmpImage:Image, midhandle:Bool=true)
		Local i:GameImage = New GameImage
		i.LoadAnim(path + name, w, h, total, tmpImage, midhandle)
		Self.Set(i.name, 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$
	Field image:Image
	Field w%
	Field h%
	Field w2#
	Field h2#
	Field midhandled%=0

	Field leftMargin%=0
	Field rightMargin%=0
	Field topMargin%=0
	Field bottomMargin%=0

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

	Method LoadAnim:Void(file$, w%, h%, total%, tmpImage:Image, midhandle:Bool=true)
		name = StripAll(file.ToUpper())
		image = LoadAnimBitmap(file, w, h, total, tmpImage)
		CalcSize()
		MidHandle(midhandle)
	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#, y#, rotation# = 0, scaleX# = 1, scaleY# = 1, frame% = 0)
		DrawImage(self.image, x, y, rotation, scaleX, scaleY, frame)
	End

	Method DrawTiled:Void(x#, y#, w#, h#, scaleX# = 1, scaleY# = 1, frame% = 0)
	End

	Method DrawStretched:Void(x#, y#, rw#, rh#, frame% = 0)
	End

	Method DrawGrid:Void(x#, y#, rw#, rh#, frame% = 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 PreCache:Void()
		DrawImage self.image, -self.w-50, -self.h-50
	End
End

Class SoundBank Extends StringMap<GameSound>

	Global path$ = "sounds/"

	Method Load:Void(name:String, nameoverride:String = "")
		Local i:GameSound = New GameSound
		i.Load(name)

		If nameoverride <> "" Then i.name = nameoverride.ToUpper()
		Self.Set(i.name , i)
	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$
	Field sound:Sound
	Field rate# = 1
	Field pan# = 0
	Field volume# = 1
	Field loop% = 0

	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()
		SoundPlayer.PlayFx(sound, pan, rate, volume * (game.soundVolume / 100), loop)
	End
End

Class SoundPlayer
	Global channel:Int
	Const MAX_CHANNELS:Int = 31

	Function PlayFx:Void(s:Sound, pan#=0, rate#=1, volume#=1, loop% = 0)
		channel += 1
		If (channel > MAX_CHANNELS) Then channel = 0

		StopChannel(channel)
		PlaySound(s, channel, loop)
		SetChannelPan(channel, pan)
		SetChannelRate(channel, rate)
		SetChannelVolume(channel, volume)
	End
End

Class Sprite
	Field name$
	Field visible?
	Field x#, y#
	Field ox#, oy#
	Field dx#, dy#
	Field speedX#, speedY#, speed#
	Field maxXSpeed#, maxYSpeed#
	Field image:GameImage
	Field scaleX# = 1, scaleY# = 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%

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

	' Rotation
	Field rotationCounter#=0
	Field rotationLength%=1000
	Field rotationLoop% = 0
	Field rotation#, rotationSpeed# = 1

	Method New(img:GameImage,x#, y#)
		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%,g%,b%)
		Self.red = r
		Self.green = g
		Self.blue = b
	End

	Method SetScaleXY:Void(sx#, sy#)
		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#=0, offsety#=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% = 800
	Global particles:Particle[MAX_PARTICLES]
	Global lastDeath% = 0
	Global maxIndex% = -1
	Global minIndex% = -1
	Global particleCount% = 0
	Field lifeCounter# = 0
	Field fadeIn# = 0
	Field fadeCounter#
	Field fadeInLength# = 0
	Field fadeLength#=0
	Field active% = 0

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

	Function Create:Particle(gi:GameImage, x#, y#, dx#=0, dy#=0, gravity#=0, fadeLength#=0, lifeCounter%=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% = 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% = -1
		Local newMaxIndex% = -1
		For Local i% = 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


Added SetMouse for GLFW

Added SetMouse for GLFW

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

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy::setMouse"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy.setMouse"
	#End

Public

Function ExitApp:Void()
	Error ""
End

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

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

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

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

   	Return pointer
End

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

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

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

   	Return pointer
End

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
	#End

Public

Function ExitApp:Void()
	Error ""
End

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

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

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

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

   	Return pointer
End

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

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

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

   	Return pointer
End

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Call DebugLog in payment restore on all targets

Strict

Private

Import paymentprovider
Import paymentprovideralias

Public

Class PaymentManager
    Private

    Field provider:PaymentProvider
    Global instance:PaymentManager

    Public

    Field idPrefix:String = ""
    Field idAlias:StringMap<String> = New StringMap<String>()

    Method New(provider:PaymentProvider)
        Self.provider = provider
    End

    Method IsPurchased:Bool(id:String)
        Local result:Bool = GetProvider().IsPurchased(ResolveId(id))

        If result
            DebugLog("IsPurchased", ResolveId(id) + " => True")
        Else
            DebugLog("IsPurchased", ResolveId(id) + " => False")
        End

        Return result
    End

    Method Purchase:Void(id:String)
        DebugLog("Purchase", ResolveId(id))
        GetProvider().Purchase(ResolveId(id))
    End

    Method IsProcessing:Bool()
        Return GetProvider().IsProcessing()
    End

    Method RestorePreviousPurchases:Void()
        DebugLog("RestorePreviousPurchases")
        #If TARGET="ios"
        PaymentProviderAlias(GetProvider()).RestorePreviousPurchases()
        #End
    End

    Method GetProvider:PaymentProvider()
        Return provider
    End

    Function GetInstance:PaymentManager()
        If Not instance
            instance = New PaymentManager(New PaymentProviderAlias())
        End

        Return instance
    End

    Private

    Method ResolveId:String(id:String)
        If idAlias.Contains(id)
            Return idPrefix + idAlias.Get(id)
        Else
            Return idPrefix + id
        End
    End

    Method DebugLog:Void(func:String, message:String="")
        #If CONFIG="debug"
        Print "[PaymentManager " + func + "] " + message
        #End
    End
End

Strict

Private

Import paymentprovider
Import paymentprovideralias

Public

Class PaymentManager
    Private

    Field provider:PaymentProvider
    Global instance:PaymentManager

    Public

    Field idPrefix:String = ""
    Field idAlias:StringMap<String> = New StringMap<String>()

    Method New(provider:PaymentProvider)
        Self.provider = provider
    End

    Method IsPurchased:Bool(id:String)
        Local result:Bool = GetProvider().IsPurchased(ResolveId(id))

        If result
            DebugLog("IsPurchased", ResolveId(id) + " => True")
        Else
            DebugLog("IsPurchased", ResolveId(id) + " => False")
        End

        Return result
    End

    Method Purchase:Void(id:String)
        DebugLog("Purchase", ResolveId(id))
        GetProvider().Purchase(ResolveId(id))
    End

    Method IsProcessing:Bool()
        Return GetProvider().IsProcessing()
    End

    Method RestorePreviousPurchases:Void()
        #If TARGET="ios"
        DebugLog("RestorePreviousPurchases")
        PaymentProviderAlias(GetProvider()).RestorePreviousPurchases()
        #End
    End

    Method GetProvider:PaymentProvider()
        Return provider
    End

    Function GetInstance:PaymentManager()
        If Not instance
            instance = New PaymentManager(New PaymentProviderAlias())
        End

        Return instance
    End

    Private

    Method ResolveId:String(id:String)
        If idAlias.Contains(id)
            Return idPrefix + idAlias.Get(id)
        Else
            Return idPrefix + id
        End
    End

    Method DebugLog:Void(func:String, message:String="")
        #If CONFIG="debug"
        Print "[PaymentManager " + func + "] " + message
        #End
    End
End


Alignment added back to Text

Alignment added back to Text

Strict

Private

Import bono
Import bono.vendor.angelfont

Public

Class Text Extends BaseDisplayObject Implements Renderable
    Private

    Global angelFontStore:StringMap<AngelFont> = New StringMap<AngelFont>()
    Field name:String
    Field _text:String = ""
    Field renderPos:Vector2D = New Vector2D()

    Public

    Field halign:Int = Align.LEFT
    Field valign:Int = Align.TOP

    Method New(name:String, pos:Vector2D=Null)
        Self.name = name
        If Not (pos = Null) Then SetPosition(pos)

        If Not angelFontStore.Contains(name)
            angelFontStore.Set(name, New AngelFont())
            angelFontStore.Get(name).LoadFont(name)
        End
    End

    Method OnRender:Void()
        renderPos.Set(GetPosition())
        Align.Horizontal(renderPos, Self, halign)
        Align.Vertical(renderPos, Self, valign)

        GetColor().Activate()
        angelFont.DrawText(_text, renderPos.x, renderPos.y)
        GetColor().Deactivate()
    End

    Method GetTextWidth:Int(char:String)
        Return angelFont.TextWidth(char)
    End

    Method GetTextHeight:Int(char:String)
        Return angelFont.TextHeight(char)
    End

    Method text:Void(newText:String) Property
        _text = newText

        Local width:Float = angelFont.TextWidth(newText)
        Local height:Float = angelFont.TextHeight(newText)
        SetSize(New Vector2D(width, height))
    End

    Method text:String() Property
        Return _text
    End

    Private

    Method angelFont:AngelFont() Property
        Return angelFontStore.Get(name)
    End
End

Strict

Private

Import bono
Import bono.vendor.angelfont

Public

Class Text Extends BaseDisplayObject Implements Renderable
    Private

    Global angelFontStore:StringMap<AngelFont> = New StringMap<AngelFont>()
    Field name:String
    Field _text:String = ""

    Public

    Method New(name:String, pos:Vector2D=Null)
        Self.name = name
        If Not (pos = Null) Then SetPosition(pos)

        If Not angelFontStore.Contains(name)
            angelFontStore.Set(name, New AngelFont())
            angelFontStore.Get(name).LoadFont(name)
        End
    End

    Method OnRender:Void()
        GetColor().Activate()
        angelFont.DrawText(_text, GetPosition().x, GetPosition().y)
        GetColor().Deactivate()
    End

    Method GetTextWidth:Int(char:String)
        Return angelFont.TextWidth(char)
    End

    Method GetTextHeight:Int(char:String)
        Return angelFont.TextHeight(char)
    End

    Method text:Void(newText:String) Property
        _text = newText

        Local width:Float = angelFont.TextWidth(newText)
        Local height:Float = angelFont.TextHeight(newText)
        SetSize(New Vector2D(width, height))
    End

    Method text:String() Property
        Return _text
    End

    Private

    Method angelFont:AngelFont() Property
        Return angelFontStore.Get(name)
    End
End


AngleFont text driver fixed

AngleFont text driver fixed

Strict

Import mojo

Import flixel.flxextern
Import flixel.flxtext
Import flixel.flxtext.driver

Import flixel.system.flxfont
Import flixel.system.flxassetsmanager
Import flixel.system.flxresourcesmanager

Import "../../data/system_angel_font_8_flx.png"
Import "../../data/system_angel_font_8_flx.txt"
Import "../../data/system_angel_font_9_flx.png"
Import "../../data/system_angel_font_9_flx.txt"
Import "../../data/system_angel_font_10_flx.png"
Import "../../data/system_angel_font_10_flx.txt"
Import "../../data/system_angel_font_11_flx.png"
Import "../../data/system_angel_font_11_flx.txt"
Import "../../data/system_angel_font_12_flx.png"
Import "../../data/system_angel_font_12_flx.txt"
Import "../../data/system_angel_font_13_flx.png"
Import "../../data/system_angel_font_13_flx.txt"
Import "../../data/system_angel_font_14_flx.png"
Import "../../data/system_angel_font_14_flx.txt"
Import "../../data/system_angel_font_15_flx.png"
Import "../../data/system_angel_font_15_flx.txt"
Import "../../data/system_angel_font_16_flx.png"
Import "../../data/system_angel_font_16_flx.txt"
Import "../../data/system_angel_font_17_flx.png"
Import "../../data/system_angel_font_17_flx.txt"
Import "../../data/system_angel_font_18_flx.png"
Import "../../data/system_angel_font_18_flx.txt"
Import "../../data/system_angel_font_19_flx.png"
Import "../../data/system_angel_font_19_flx.txt"
Import "../../data/system_angel_font_20_flx.png"
Import "../../data/system_angel_font_20_flx.txt"
Import "../../data/system_angel_font_21_flx.png"
Import "../../data/system_angel_font_21_flx.txt"
Import "../../data/system_angel_font_22_flx.png"
Import "../../data/system_angel_font_22_flx.txt"
Import "../../data/system_angel_font_23_flx.png"
Import "../../data/system_angel_font_23_flx.txt"
Import "../../data/system_angel_font_24_flx.png"
Import "../../data/system_angel_font_24_flx.txt"

Global AngelfontTextDriver:FlxClass = New FlxAFDriverClass()

Class FlxTextAngelFontDriver Extends FlxTextDriver

Private
	Global _FontLoader:FlxAFDriverLoader = New FlxAFDriverLoader()
	Global _FontsManager:FlxResourcesManager<AngelFont> = New FlxResourcesManager<AngelFont>()

	Field _font:AngelFont
	Field _fontHeight:Int

Public
	Function Init:Void()
		Local minSize:Int = FlxAssetsManager.GetFont(FlxText.SYSTEM_FONT).MinSize
		Local maxSize:Int = FlxAssetsManager.GetFont(FlxText.SYSTEM_FONT).MaxSize
		Local fontPathPrefix:String = FlxText.SYSTEM_FONT + "_angel_font_"

		Local angelSystemFont:FlxFont = FlxAssetsManager.AddFont(FlxText.SYSTEM_FONT, FlxText.DRIVER_ANGELFONT)

		For Local size:Int = minSize To maxSize
			angelSystemFont.SetPath(size, fontPathPrefix +  size + FlxG.DATA_SUFFIX + ".txt")
		Next
	End Function

	Method GetTextWidth:Int(text:String)
		Return _font.TextWidth(text)
	End Method

	Method GetTextHeight:Int()
		Return _countLines * _fontHeight
	End Method

	Method Draw:Void(x:Float, y:Float)
		If (_countLines <= 1) Then
			_font.DrawText(_text, x + _offsetX, y)
		Else
			For Local line:Int = 0 Until _countLines
				_font.DrawText(_textLines.Get(line).text, x + _textLines.Get(line).offsetX, y + line * _fontHeight)
			Next
		End If
	End Method

	Method Destroy:Void()
		_font = Null
		Super.Destroy()
	End Method

	Method Reset:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _size

		_font = _FontsManager.GetResource(_fontFamily + _size, _FontLoader)

		If (_text.Length() > 0) Then
			_fontHeight = _font.TextHeight(_text) * 1.5
		Else
			_fontHeight = _font.TextHeight(" ") * 1.5
		End If
	End Method

	Method ID:Int() Property
		Return FlxText.DRIVER_ANGELFONT
	End Method

	Method GetFontObject:Object()
		Return _font
	End Method

End Class

Private
Class FlxAFDriverClass Implements FlxClass

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

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

End Class

Class FlxAFDriverLoader Extends FlxResourceLoader<AngelFont>

	Field fontFamily:String = FlxText.SYSTEM_FONT
	Field fontSize:Int

	Method Load:T(name:String)
		Return New AngelFont(FlxAssetsManager.GetFont(fontFamily, FlxText.DRIVER_ANGELFONT).GetPath(fontSize))
	End Method

End Class

#Rem
	AngelFont ported source

	PC: http://www.angelcode.com/products/bmfont/
	Mac/PC: http://slick.cokeandcode.com/demos/hiero.jnlp
#End

Class AngelFont

Private
	Field image:Image
	Field chars:Char[256]
	Field kernPairs:StringMap<KernPair> = New StringMap<KernPair>
	Field iniText:String

	Field xOffset:Int
	Field yOffset:Int

	Field prevMouseDown:Bool = False

Public
	Field useKerning:Bool = True

	Field lineGap:Int = 5
	Field height:Int = 0
	Field heightOffset:Int = 9999
	Field scrollY:Int = 0

	Field italicSkew:Float = 0.25

	Method New(url:String="")
		If url <> ""
			Self.LoadFont(url)
		Endif
	End Method

	Method GetChars:Char[]()
		Return chars
	End

	Method LoadFont:Void(url:String)

		iniText = LoadString(url)
		Local lines:= iniText.Split(String.FromChar(10))
		For Local line:= Eachin lines

			line=line.Trim()

			If line.StartsWith("id,") Or line = "" Continue
			If line.StartsWith("first,")
				Continue
			Endif
			Local data$[] = line.Split(",")

			For Local i:=0 Until data.Length
				data[i]=data[i].Trim()
			Next

			If data.Length > 0
				If data.Length = 3
					kernPairs.Insert(String.FromChar(Int(data[0]))+"_"+String.FromChar(Int(data[1])), New KernPair(Int(data[0]), Int(data[1]), Int(data[2])))
				Else
					If data.Length >= 8
						chars[Int(data[0])] = New Char(Int(data[1]), Int(data[2]), Int(data[3]), Int(data[4]),  Int(data[5]),  Int(data[6]),  Int(data[7]))
						Local ch := chars[Int(data[0])]
						If ch.height > Self.height Self.height = ch.height
						If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
					Endif
				Endif
			Endif
		Next

		image = LoadImage(url.Replace(".txt", ".png"))
	End Method

	Method DrawText:Void(txt:String, x:Int, y:Int)
		Local prevChar:String = ""
		xOffset = 0

		For Local i:= 0 Until txt.Length
			Local asc:Int = txt[i]
			Local ac:Char = chars[asc]
			Local thisChar:String = String.FromChar(asc)
			If ac  <> Null
				If useKerning
					Local key:String = prevChar+"_"+thisChar
					If kernPairs.Contains(key)
						xOffset += kernPairs.Get(key).amount
					Endif
				Endif
				ac.Draw(image, x+xOffset,y)
				xOffset += ac.xAdvance
				prevChar = thisChar
			Endif
		Next
	End Method

	Method TextWidth:Int(txt:String)
		Local prevChar:String = ""
		Local width:Int = 0
		For Local i:= 0 Until txt.Length
			Local asc:Int = txt[i]
			Local ac:Char = chars[asc]
			Local thisChar:String = String.FromChar(asc)
			If ac  <> Null
				If useKerning
					Local key:String = prevChar+"_"+thisChar
					If kernPairs.Contains(key)
						width += kernPairs.Get(key).amount
					Endif
				Endif
				width += ac.xAdvance
				prevChar = thisChar
			Endif
		Next
		Return width
	End Method

	Method TextHeight:Int(txt:String)
		Local h:Int = 0
		For Local i:= 0 Until txt.Length
			Local asc:Int = txt[i]
			Local ac:Char = chars[asc]
			If (ac <> Null And ac.height > h) h = ac.height
		Next
		Return h
	End

End Class

Class Char
	Field asc:Int
	Field x:Int
	Field y:Int

	Field width:Int
	Field height:Int = 0

	Field xOffset:Int = 0
	Field yOffset:Int = 0
	Field xAdvance:Int = 0

	Method New(x:Int,y:Int, w:Int, h:Int, xoff:Int=0, yoff:Int=0, xadv:Int=0)
		Self.x = x
		Self.y = y
		Self.width = w
		Self.height = h

		Self.xOffset = xoff
		Self.yOffset = yoff
		Self.xAdvance = xadv
	End

	Method Draw:Void(fontImage:Image, linex:Int, liney:Int)
		DrawImageRect(fontImage, linex+xOffset,liney+yOffset, x,y, width,height)
	End Method

	Method toString:String()
		Return String.FromChar(asc)+"="+asc
	End Method
End Class

Class KernPair
	Field first:String
	Field second:String
	Field amount:Int

	Method New(first:Int, second:Int, amount:Int)
		Self.first = first
		Self.second = second
		Self.amount = amount
	End

	Method toString:String()
		Return "first="+String.FromChar(first)+" second="+String.FromChar(second)+" amount="+amount
	End Method
End Class

Strict

Import mojo

Import flixel.flxextern
Import flixel.flxtext
Import flixel.flxtext.driver

Import flixel.system.flxfont
Import flixel.system.flxassetsmanager
Import flixel.system.flxresourcesmanager

Import "../../data/system_angel_font_8_flx.png"
Import "../../data/system_angel_font_8_flx.txt"
Import "../../data/system_angel_font_9_flx.png"
Import "../../data/system_angel_font_9_flx.txt"
Import "../../data/system_angel_font_10_flx.png"
Import "../../data/system_angel_font_10_flx.txt"
Import "../../data/system_angel_font_11_flx.png"
Import "../../data/system_angel_font_11_flx.txt"
Import "../../data/system_angel_font_12_flx.png"
Import "../../data/system_angel_font_12_flx.txt"
Import "../../data/system_angel_font_13_flx.png"
Import "../../data/system_angel_font_13_flx.txt"
Import "../../data/system_angel_font_14_flx.png"
Import "../../data/system_angel_font_14_flx.txt"
Import "../../data/system_angel_font_15_flx.png"
Import "../../data/system_angel_font_15_flx.txt"
Import "../../data/system_angel_font_16_flx.png"
Import "../../data/system_angel_font_16_flx.txt"
Import "../../data/system_angel_font_17_flx.png"
Import "../../data/system_angel_font_17_flx.txt"
Import "../../data/system_angel_font_18_flx.png"
Import "../../data/system_angel_font_18_flx.txt"
Import "../../data/system_angel_font_19_flx.png"
Import "../../data/system_angel_font_19_flx.txt"
Import "../../data/system_angel_font_20_flx.png"
Import "../../data/system_angel_font_20_flx.txt"
Import "../../data/system_angel_font_21_flx.png"
Import "../../data/system_angel_font_21_flx.txt"
Import "../../data/system_angel_font_22_flx.png"
Import "../../data/system_angel_font_22_flx.txt"
Import "../../data/system_angel_font_23_flx.png"
Import "../../data/system_angel_font_23_flx.txt"
Import "../../data/system_angel_font_24_flx.png"
Import "../../data/system_angel_font_24_flx.txt"

Global AngelfontTextDriver:FlxClass = New FlxAFDriverClass()

Class FlxTextAngelFontDriver Extends FlxTextDriver

Private
	Global _FontLoader:FlxAFDriverLoader = New FlxAFDriverLoader()
	Global _FontsManager:FlxResourcesManager<AngelFont> = New FlxResourcesManager<AngelFont>()

	Field _font:AngelFont
	Field _fontHeight:Int

Public
	Function Init:Void()
		Local minSize:Int = FlxAssetsManager.GetFont(FlxText.SYSTEM_FONT).MinSize
		Local maxSize:Int = FlxAssetsManager.GetFont(FlxText.SYSTEM_FONT).MaxSize
		Local fontPathPrefix:String = FlxText.SYSTEM_FONT + "_angel_font_"

		Local angelSystemFont:FlxFont = FlxAssetsManager.AddFont(FlxText.SYSTEM_FONT, FlxText.DRIVER_ANGELFONT)

		For Local size:Int = minSize To maxSize
			angelSystemFont.SetPath(size, fontPathPrefix +  size + FlxG.DATA_SUFFIX + ".txt")
		Next
	End Function

	Method GetTextWidth:Int(text:String)
		Return _font.TextWidth(text)
	End Method

	Method GetTextHeight:Int()
		Return _countLines * _fontHeight
	End Method

	Method Draw:Void(x:Float, y:Float)
		If (_countLines <= 1) Then
			_font.DrawText(_text, x + _offsetX, y)
		Else
			For Local line:Int = 0 Until _countLines
				_font.DrawText(_textLines.Get(line).text, x + _textLines.Get(line).offsetX, y + line * _fontHeight)
			Next
		End If
	End Method

	Method Destroy:Void()
		_font = Null
		Super.Destroy()
	End Method

	Method Reset:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _size

		_font = _FontsManager.GetResource(_fontFamily + _size, _FontLoader)

		If (_text.Length() > 0) Then
			_fontHeight = _font.TextHeight(_text) * 1.5
		Else
			_fontHeight = _font.TextHeight(" ") * 1.5
		End If
	End Method

	Method ID:Int() Property
		Return FlxText.DRIVER_ANGELFONT
	End Method

	Method GetFontObject:Object()
		Return _font
	End Method

End Class

Private
Class FlxAFDriverClass Implements FlxClass

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

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

End Class

Class FlxAFDriverLoader Extends FlxResourceLoader<AngelFont>

	Field fontFamily:String = FlxText.SYSTEM_FONT
	Field fontSize:Int

	Method Load:T(name:String)
		Return New AngelFont(FlxAssetsManager.GetFont(fontFamily, FlxText.DRIVER_ANGELFONT).GetPath(fontSize))
	End Method

End Class

#Rem
	AngelFont ported source

	PC: http://www.angelcode.com/products/bmfont/
	Mac/PC: http://slick.cokeandcode.com/demos/hiero.jnlp
#End

Class AngelFont

Private
	Field image:Image
	Field chars:Char[256]
	Field kernPairs:StringMap<KernPair> = New StringMap<KernPair>
	Field iniText:String

	Field xOffset:Int
	Field yOffset:Int

	Field prevMouseDown:Bool = False

Public
	Field useKerning:Bool = True

	Field lineGap:Int = 5
	Field height:Int = 0
	Field heightOffset:Int = 9999
	Field scrollY:Int = 0

	Field italicSkew:Float = 0.25

	Method New(url:String="")
		If url <> ""
			Self.LoadFont(url)
		Endif
	End Method

	Method GetChars:Char[]()
		Return chars
	End

	Method LoadFont:Void(url:String)

		iniText = LoadString(url)
		Local lines:= iniText.Split(String.FromChar(10))
		For Local line:= Eachin lines

			line=line.Trim()

			If line.StartsWith("id,") Or line = "" Continue
			If line.StartsWith("first,")
				Continue
			Endif
			Local data$[] = line.Split(",")

			For Local i:=0 Until data.Length
				data[i]=data[i].Trim()
			Next

			If data.Length > 0
				If data.Length = 3
					kernPairs.Insert(String.FromChar(Int(data[0]))+"_"+String.FromChar(Int(data[1])), New KernPair(Int(data[0]), Int(data[1]), Int(data[2])))
				Else
					If data.Length >= 8
						chars[Int(data[0])] = New Char(Int(data[1]), Int(data[2]), Int(data[3]), Int(data[4]),  Int(data[5]),  Int(data[6]),  Int(data[7]))
						Local ch := chars[Int(data[0])]
						If ch.height > Self.height Self.height = ch.height
						If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
					Endif
				Endif
			Endif
		Next

		image = LoadImage(url.Replace("_flx.txt", "_flx.png"))
	End Method

	Method DrawText:Void(txt:String, x:Int, y:Int)
		Local prevChar:String = ""
		xOffset = 0

		For Local i:= 0 Until txt.Length
			Local asc:Int = txt[i]
			Local ac:Char = chars[asc]
			Local thisChar:String = String.FromChar(asc)
			If ac  <> Null
				If useKerning
					Local key:String = prevChar+"_"+thisChar
					If kernPairs.Contains(key)
						xOffset += kernPairs.Get(key).amount
					Endif
				Endif
				ac.Draw(image, x+xOffset,y)
				xOffset += ac.xAdvance
				prevChar = thisChar
			Endif
		Next
	End Method

	Method TextWidth:Int(txt:String)
		Local prevChar:String = ""
		Local width:Int = 0
		For Local i:= 0 Until txt.Length
			Local asc:Int = txt[i]
			Local ac:Char = chars[asc]
			Local thisChar:String = String.FromChar(asc)
			If ac  <> Null
				If useKerning
					Local key:String = prevChar+"_"+thisChar
					If kernPairs.Contains(key)
						width += kernPairs.Get(key).amount
					Endif
				Endif
				width += ac.xAdvance
				prevChar = thisChar
			Endif
		Next
		Return width
	End Method

	Method TextHeight:Int(txt:String)
		Local h:Int = 0
		For Local i:= 0 Until txt.Length
			Local asc:Int = txt[i]
			Local ac:Char = chars[asc]
			If (ac <> Null And ac.height > h) h = ac.height
		Next
		Return h
	End

End Class

Class Char
	Field asc:Int
	Field x:Int
	Field y:Int

	Field width:Int
	Field height:Int = 0

	Field xOffset:Int = 0
	Field yOffset:Int = 0
	Field xAdvance:Int = 0

	Method New(x:Int,y:Int, w:Int, h:Int, xoff:Int=0, yoff:Int=0, xadv:Int=0)
		Self.x = x
		Self.y = y
		Self.width = w
		Self.height = h

		Self.xOffset = xoff
		Self.yOffset = yoff
		Self.xAdvance = xadv
	End

	Method Draw:Void(fontImage:Image, linex:Int, liney:Int)
		DrawImageRect(fontImage, linex+xOffset,liney+yOffset, x,y, width,height)
	End Method

	Method toString:String()
		Return String.FromChar(asc)+"="+asc
	End Method
End Class

Class KernPair
	Field first:String
	Field second:String
	Field amount:Int

	Method New(first:Int, second:Int, amount:Int)
		Self.first = first
		Self.second = second
		Self.amount = amount
	End

	Method toString:String()
		Return "first="+String.FromChar(first)+" second="+String.FromChar(second)+" amount="+amount
	End Method
End Class


fix compile error in StringHelper.Randomize()

Strict

Private

Import arrayhelper

Public

Class StringHelper Abstract
    Function Randomize:String(input:String)
        Local split:String[] = input.Split("")
        ArrayHelper<String>.Randomize(split)
        Return "".Join(split)
    End

    Function CountLines:Int(input:String)
        Return input.Split("~n").Length()
    End
End

Strict

Private

Import arrayhelper

Public

Class StringHelper Abstract
    Function Randomize:String(input:String)
        Local split:String[] = input.Split("")
        ArrayHelper.Randomize(split)
        Return "".Join(split)
    End

    Function CountLines:Int(input:String)
        Return input.Split("~n").Length()
    End
End


Fixed a bug with aliens being added to the list twice.

' Strict code mode please
Strict

' The modules
Import mojo
Import diddy

' Starting Point
Function Main:Int()
	game = new MyGame()
End

' Screens in the game
Global titleScreen:Screen = New TitleScreen()
Global gameScreen:Screen = New GameScreen()
Global nextLevelScreen:Screen = New NextLevelScreen()
Global gameOverScreen:Screen = New GameOverScreen()

' The Game
Class MyGame Extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()

		' Set the font
		SetFont(LoadImage("graphics/font_16.png",16,16,64))

		LoadImages()
		LoadSounds()

		titleScreen.PreStart()

		Return 0
	End

	'***********************
	'* Load Images
	'***********************
	Method LoadImages:Void()
		' create tmpImage for animations
		Local tmpImage:Image

		images.Load("galaxy2.png", "", False)
		images.LoadAnim("Ship1.png", 64, 64, 7, tmpImage)
		images.LoadAnim("rusher.png", 64, 32, 24, tmpImage)
		images.LoadAnim("missile.png", 16, 16, 2, tmpImage)

	End

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

Class TitleScreen Extends Screen
	Field background:GameImage

	Method New()
		name = "Title"
	End

	Method Start:Void()
		background = game.images.Find("galaxy2")
		game.MusicPlay("ShowYourMoves.ogg", True)
		game.screenFade.Start(50, false)
	End

	Method Render:Void()
		DrawImage background.image, 0, 0
		Scale 2, 2
		DrawText "ATTACK OF THE", SCREEN_WIDTH2 / 2, (SCREEN_HEIGHT2-30) / 2, 0.5, 0.5
		DrawText "SPACE-BUGS", SCREEN_WIDTH2 / 2, (SCREEN_HEIGHT2) / 2, 0.5, 0.5
		Scale .5, .5
		DrawText "SPACE TO PLAY!", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 60, 0.5, 0.5
		DrawText "GRAPHICS FROM XENON 2000: PROJECT PCF", SCREEN_WIDTH2, SCREEN_HEIGHT - 40, 0.5, 0.5
		DrawText "MUSIC BY KEVIN MACLEOD", SCREEN_WIDTH2, SCREEN_HEIGHT - 20, 0.5, 0.5
	End

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

Class GameScreen Extends Screen
	Field background:GameImage
	Field player:Player
	Field lifeImage:GameImage
	Field missileImage:GameImage

	Method New()
		name = "Game Screen"
	End

	Method Start:Void()
		background = game.images.Find("galaxy2")
		Local gi:GameImage = game.images.Find("Ship1")
		player = New Player(gi, SCREEN_WIDTH2, SCREEN_HEIGHT - gi.h, game.images.Find("missile"))

		StartLevel()
		game.MusicPlay("SpaceFighterLoop.ogg", True)
		'start fade
		game.screenFade.Start(50, False, True, True)
	End

	Method ClearLevel:Void()
		Enemy.list.Clear()
	End

	Method StartLevel:Void(level% = 1)
		ClearLevel()

		'create a few enemies
		Local maxAliensAcross%
		Local maxAliensDown%

		#if TARGET="android"
			maxAliensAcross = 2
			maxAliensDown = 2
		#else
			maxAliensAcross = 7
			maxAliensDown = 4
		#endif
		Local gi:GameImage = game.images.Find("rusher")

		Local e:Enemy
		for Local i% = 0 to maxAliensAcross
			for Local j% = 0 to maxAliensDown
				e = new Alien(gi, 64 * i, (40 * j) + 34)
				e.frame = 0
				e.maxFrame = 23
				e.dy = 0
				e.dx = 0.2 + (level/5)
				e.movement = 1
				e.SetFrame(0, 23, 80, True)
				e.score = 10
			next
		next
	End

	Method Render:Void()
		DrawImage background.image, 0, 0
		Alien.DrawAll()
		Bullet.DrawAll()
		player.Draw()
		DrawGUI()
	End

	Method DrawGUI:Void()
		DrawText "SCORE: "+ player.score, 0, 0
	End

	Method Update:Void()
		player.Update()
		Alien.UpdateAll()
		Bullet.UpdateAll()
		CheckCollisions()
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, True, True, True)
			game.nextScreen = titleScreen
		End
	End

	Method CheckCollisions:Void()
		Local b:Bullet
		Local e:Enemy
		Local hit:Bool = False

		Enemy.enum.Reset()
		While Enemy.enum.HasNext()
			e = Enemy.enum.NextObject()
			Bullet.enum.Reset()
			While Bullet.enum.HasNext()
				b = Bullet.enum.NextObject()
				If b.Collide(e)
					player.score+=e.score
					Bullet.enum.Remove()
					Enemy.enum.Remove()
					Exit
				End
			End
		End
	End
End

Class Player Extends Sprite
	Field score:Int
	Field lives:Int
	Field level:Int
	Field frame:Int
	Field frameDelay:Int
	Field maxFrameDelay:Int = 3
	Field missileImage:GameImage

	Method New(img:GameImage, x#, y#, missileImage:GameImage)
		Super.New(img, x, y)
		score = 0
		lives = 3
		level = 1
		frame = 3
		speedX = 1
		maxXSpeed = 5
		Self.missileImage = missileImage
	End

	Method Update:Void()
		If KeyDown(KEY_LEFT)
			Self.dx-=Self.speedX
			RollLeft()
		Else If KeyDown(KEY_RIGHT)
			Self.dx+=Self.speedX
			RollRight()
		Else
			SlowShip()
		End
		If KeyHit(KEY_SPACE)
			New Bullet(missileImage, x, y)
		End
		If dx > Self.maxXSpeed
			dx = Self.maxXSpeed
		End
		If dx < -Self.maxXSpeed
			dx = -Self.maxXSpeed
		End
		Self.Move()
		' limit the player to the screen
		If x < 0 + image.w2
			x = image.w2
		End
		If x > SCREEN_WIDTH - image.w2
			x = SCREEN_WIDTH - image.w2
		End
	End

	Method RollLeft:Void()
		If frame > 0
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame-=1
				frameDelay = 0
			End
		End
	End

	Method RollRight:Void()
		If frame < 6
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame+=1
				frameDelay = 0
			End
		End
	End

	Method SlowShip:Void()
		If frame < 3
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame+=1
				frameDelay = 0
			End
		Else If frame > 3
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame-=1
				frameDelay = 0
			End
		End
		If dx > 0
			dx-=Self.speedX/4
		Else If dx < 0
			dx+=Self.speedX/4
		End
	End
End

Class Alien Extends Enemy

	Method New(img:GameImage, x#, y#)
		Super.New(img, x, y)
		'Self.frame = 0
		'Self.dy = Rnd(1, 3)
		'Self.list.Add(Self)
	End Method

	Method Update:Void()
		Super.Update()
		Select movement
			Case 1
				if x > SCREEN_WIDTH
					dx = -dx
					x = SCREEN_WIDTH - 1
					moveCounter = 50

				else if x < 0
					dx = -dx
					x = 0 + 1
					moveCounter = 50

				EndIf
				if moveCounter > 1
					dy = 1
					moveCounter-=1 * dt.delta
				else
					dy = 0
					moveCounter = 0
				end if
		End
	End
End

Class Enemy Extends Sprite
	Global list:ArrayList<Enemy> = New ArrayList<Enemy>
	Global enum:AbstractEnumerator<Enemy> = list.Enumerator()

	Field movement:Int
	Field moveCounter:Float
	Field type:Int
	Field score:Int

	Method New(img:GameImage, x#, y#)
		Super.New(img, x, y)
		Self.frame = 0
		Self.dy = Rnd(1, 3)
		list.Add(Self)
	End

	Function UpdateAll:Void()
		enum.Reset()
		While enum.HasNext()
			Local e:Enemy = enum.NextObject()
			e.Update()
			If e.OutOfBounds() Then enum.Remove()
		End
	End

	Method Update:Void()
		UpdateAnimation()
		Move()
	End

	Method OutOfBounds:Bool()
		Return y > SCREEN_HEIGHT + image.h
	End

	Function DrawAll:Void()
		For Local i% = 0 Until list.Size
			Local e:Enemy = list.Get(i)
			e.Draw()
		Next
	End
End

Class Bullet Extends Sprite
	Global list:ArrayList<Bullet> = New ArrayList<Bullet>
	Global enum:AbstractEnumerator<Bullet> = list.Enumerator()

	Method New(img:GameImage, x#, y#)
		Super.New(img, x, y)
		Self.speedY = -6
		list.Add(Self)
	End

	Function UpdateAll:Void()
		enum.Reset()
		While enum.HasNext()
			Local b:Bullet = enum.NextObject()
			b.Update()
			If b.OutOfBounds() Then enum.Remove()
		End
	End

	Method Update:Void()
		Self.UpdateAnimation()
		dy = Self.speedY
		Move()
	End

	Method OutOfBounds:Bool()
		Return y < -image.h
	End

	Function DrawAll:Void()
		For Local i% = 0 Until list.Size
			Local b:Bullet = list.Get(i)
			b.Draw()
		Next
	End

End

Class NextLevelScreen Extends Screen
	Method New()
		name = "Next Level"
	End

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

	Method Render:Void()
		Cls
	End

	Method Update:Void()

	End
End

Class GameOverScreen Extends Screen
	Method New()
		name = "Game Over"
	End

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

	Method Render:Void()
		Cls
	End

	Method Update:Void()
	End
End

' Strict code mode please
Strict

' The modules
Import mojo
Import diddy

' Starting Point
Function Main:Int()
	game = new MyGame()
End

' Screens in the game
Global titleScreen:Screen = New TitleScreen()
Global gameScreen:Screen = New GameScreen()
Global nextLevelScreen:Screen = New NextLevelScreen()
Global gameOverScreen:Screen = New GameOverScreen()

' The Game
Class MyGame Extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()

		' Set the font
		SetFont(LoadImage("graphics/font_16.png",16,16,64))

		LoadImages()
		LoadSounds()

		titleScreen.PreStart()

		Return 0
	End

	'***********************
	'* Load Images
	'***********************
	Method LoadImages:Void()
		' create tmpImage for animations
		Local tmpImage:Image

		images.Load("galaxy2.png", "", False)
		images.LoadAnim("Ship1.png", 64, 64, 7, tmpImage)
		images.LoadAnim("rusher.png", 64, 32, 24, tmpImage)
		images.LoadAnim("missile.png", 16, 16, 2, tmpImage)

	End

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

Class TitleScreen Extends Screen
	Field background:GameImage

	Method New()
		name = "Title"
	End

	Method Start:Void()
		background = game.images.Find("galaxy2")
		game.MusicPlay("ShowYourMoves.ogg", True)
		game.screenFade.Start(50, false)
	End

	Method Render:Void()
		DrawImage background.image, 0, 0
		Scale 2, 2
		DrawText "ATTACK OF THE", SCREEN_WIDTH2 / 2, (SCREEN_HEIGHT2-30) / 2, 0.5, 0.5
		DrawText "SPACE-BUGS", SCREEN_WIDTH2 / 2, (SCREEN_HEIGHT2) / 2, 0.5, 0.5
		Scale .5, .5
		DrawText "SPACE TO PLAY!", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 60, 0.5, 0.5
		DrawText "GRAPHICS FROM XENON 2000: PROJECT PCF", SCREEN_WIDTH2, SCREEN_HEIGHT - 40, 0.5, 0.5
		DrawText "MUSIC BY KEVIN MACLEOD", SCREEN_WIDTH2, SCREEN_HEIGHT - 20, 0.5, 0.5
	End

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

Class GameScreen Extends Screen
	Field background:GameImage
	Field player:Player
	Field lifeImage:GameImage
	Field missileImage:GameImage

	Method New()
		name = "Game Screen"
	End

	Method Start:Void()
		background = game.images.Find("galaxy2")
		Local gi:GameImage = game.images.Find("Ship1")
		player = New Player(gi, SCREEN_WIDTH2, SCREEN_HEIGHT - gi.h, game.images.Find("missile"))

		StartLevel()
		game.MusicPlay("SpaceFighterLoop.ogg", True)
		'start fade
		game.screenFade.Start(50, False, True, True)
	End

	Method ClearLevel:Void()
		Enemy.list.Clear()
	End

	Method StartLevel:Void(level% = 1)
		ClearLevel()

		'create a few enemies
		Local maxAliensAcross%
		Local maxAliensDown%

		#if TARGET="android"
			maxAliensAcross = 2
			maxAliensDown = 2
		#else
			maxAliensAcross = 7
			maxAliensDown = 4
		#endif
		Local gi:GameImage = game.images.Find("rusher")

		Local e:Enemy
		for Local i% = 0 to maxAliensAcross
			for Local j% = 0 to maxAliensDown
				e = new Alien(gi, 64 * i, (40 * j) + 34)
				e.frame = 0
				e.maxFrame = 23
				e.dy = 0
				e.dx = 0.2 + (level/5)
				e.movement = 1
				e.SetFrame(0, 23, 80, True)
				e.score = 10
			next
		next
	End

	Method Render:Void()
		DrawImage background.image, 0, 0
		Alien.DrawAll()
		Bullet.DrawAll()
		player.Draw()
		DrawGUI()
	End

	Method DrawGUI:Void()
		DrawText "SCORE: "+ player.score, 0, 0
	End

	Method Update:Void()
		player.Update()
		Alien.UpdateAll()
		Bullet.UpdateAll()
		CheckCollisions()
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, True, True, True)
			game.nextScreen = titleScreen
		End
	End

	Method CheckCollisions:Void()
		Local b:Bullet
		Local e:Enemy
		Local hit:Bool = False

		For Local i% = 0 Until Enemy.list.Size
			e = Enemy.list.Get(i)
			For Local j% = 0 Until Bullet.list.Size
				b = Bullet.list.Get(j)
				if b.Collide(e)
					player.score+=e.score
					Bullet.list.Remove(b)
					Enemy.list.Remove(e)
				End
			Next
		Next
	End
End

Class Player Extends Sprite
	Field score:Int
	Field lives:Int
	Field level:Int
	Field frame:Int
	Field frameDelay:Int
	Field maxFrameDelay:Int = 3
	Field missileImage:GameImage

	Method New(img:GameImage, x#, y#, missileImage:GameImage)
		Super.New(img, x, y)
		score = 0
		lives = 3
		level = 1
		frame = 3
		speedX = 1
		maxXSpeed = 5
		Self.missileImage = missileImage
	End

	Method Update:Void()
		If KeyDown(KEY_LEFT)
			Self.dx-=Self.speedX
			RollLeft()
		Else If KeyDown(KEY_RIGHT)
			Self.dx+=Self.speedX
			RollRight()
		Else
			SlowShip()
		End
		If KeyHit(KEY_SPACE)
			New Bullet(missileImage, x, y)
		End
		If dx > Self.maxXSpeed
			dx = Self.maxXSpeed
		End
		If dx < -Self.maxXSpeed
			dx = -Self.maxXSpeed
		End
		Self.Move()
		' limit the player to the screen
		If x < 0 + image.w2
			x = image.w2
		End
		If x > SCREEN_WIDTH - image.w2
			x = SCREEN_WIDTH - image.w2
		End
	End

	Method RollLeft:Void()
		If frame > 0
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame-=1
				frameDelay = 0
			End
		End
	End

	Method RollRight:Void()
		If frame < 6
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame+=1
				frameDelay = 0
			End
		End
	End

	Method SlowShip:Void()
		If frame < 3
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame+=1
				frameDelay = 0
			End
		Else If frame > 3
			frameDelay+=1
			If frameDelay > maxFrameDelay
				frame-=1
				frameDelay = 0
			End
		End
		If dx > 0
			dx-=Self.speedX/4
		Else If dx < 0
			dx+=Self.speedX/4
		End
	End
End

Class Alien Extends Enemy

	Method New(img:GameImage, x#, y#)
		Super.New(img, x, y)
		Self.frame = 0
		Self.dy = Rnd(1, 3)
		Self.list.Add(Self)
	End Method

	Method Update:Void()
		Super.Update()
		Select movement
			Case 1
				if x > SCREEN_WIDTH
					dx = -dx
					x = SCREEN_WIDTH - 1
					moveCounter = 50

				else if x < 0
					dx = -dx
					x = 0 + 1
					moveCounter = 50

				EndIf
				if moveCounter > 1
					dy = 1
					moveCounter-=1 * dt.delta
				else
					dy = 0
					moveCounter = 0
				end if
		End
	End
End

Class Enemy Extends Sprite
	Global list:ArrayList<Enemy> = New ArrayList<Enemy>
	Field movement:Int
	Field moveCounter:Float
	Field type:Int
	Field score:Int

	Method New(img:GameImage, x#, y#)
		Super.New(img, x, y)
		Self.frame = 0
		Self.dy = Rnd(1, 3)
		list.Add(Self)
	End

	Function UpdateAll:Void()
		For Local i% = 0 Until list.Size
			Local e:Enemy = list.Get(i)
			e.Update()
		Next
	End

	Method Update:Void()
		UpdateAnimation()
		Move()
		if y > SCREEN_HEIGHT + image.h
			list.Remove(self)
		EndIf
	End

	Function DrawAll:Void()
		For Local i% = 0 Until list.Size
			Local e:Enemy = list.Get(i)
			e.Draw()
		Next
	End
End

Class Bullet Extends Sprite
	Global list:ArrayList<Bullet> = New ArrayList<Bullet>

	Method New(img:GameImage, x#, y#)
		Super.New(img, x, y)
		Self.speedY = -6
		list.Add(Self)
	End

	Function UpdateAll:Void()
		For Local i% = 0 Until list.Size
			Local b:Bullet = list.Get(i)
			b.Update()
		Next
	End

	Method Update:Void()
		Self.UpdateAnimation()
		dy = Self.speedY
		Move()
		if y < -image.h then
			list.Remove(Self)
		End
	End

	Function DrawAll:Void()
		For Local i% = 0 Until list.Size
			Local b:Bullet = list.Get(i)
			b.Draw()
		Next
	End

End

Class NextLevelScreen Extends Screen
	Method New()
		name = "Next Level"
	End

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

	Method Render:Void()
		Cls
	End

	Method Update:Void()

	End
End

Class GameOverScreen Extends Screen
	Method New()
		name = "Game Over"
	End

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

	Method Render:Void()
		Cls
	End

	Method Update:Void()
	End
End