Removed/refactored some for-loops in ParticleSystem

Strict

Private

Import bono.graphics
Import bono.kernel
Import bono.utils
Import particle
Import particleemitter

Public

Class ParticleSystem Implements AppObserver
    Private

    Field sprite:Sprite
    Field emitters:Stack<ParticleEmitter> = New Stack<ParticleEmitter>()
    Field particles:StringMap<List<Particle>> = New StringMap<List<Particle>>()
    Field pool:Pool<Particle> = New Pool<Particle>()
    Field oldParticlePos:Vector2D = New Vector2D()

    Public

    Method New(sprite:Sprite)
        Self.sprite = sprite
        sprite.SetHandle(sprite.GetCenter())
    End

    Method Add:Void(emitter:ParticleEmitter)
        CheckEmitterName(emitter)
        emitters.Push(emitter)
        particles.Add(emitter.GetName(), New List<Particle>())
    End

    Method OnLoading:Void()
    End

    Method OnUpdate:Void(deltatimer:DeltaTimer)
        For Local emitter:ParticleEmitter = EachIn emitters
            If emitter.CanBeRemoved()
                RemoveFinishedEmitter(emitter)
            Else
                LaunchNewParticles(deltatimer, emitter)
                UpdateParticles(deltatimer, emitter)
            End
        End
    End

    Method OnRender:Void()
        For Local emitter:ParticleEmitter = EachIn emitters
            For Local particle:Particle = EachIn GetParticlesForEmitter(emitter)
                particle.Apply(sprite)
                sprite.OnRender()
            End
        End
    End

    Method OnResume:Void()
    End

    Method OnSuspend:Void()
    End

    Method GetStats:String()
        Local result:String = ""
        result += "Emitter size: " + emitters.Length() + "~n"
        result += "   Pool size: " + pool.Length() + "~n"
        result += "-------------~n"

        For Local emitter:ParticleEmitter = EachIn emitters
            result += emitter.GetName()
            result += ": "
            result += GetParticlesForEmitter(emitter).Count()
            result += "~n"
        End

        Return result
    End

    Private

    Method CheckEmitterName:Void(emitter:ParticleEmitter)
        Local name:String = emitter.GetName()

        If name.Trim().Length() = 0
            Error("Emitter name is empty")
        End

        If particles.Contains(name)
            Error("There is already an emitter named " + name)
        End
    End

    Method RemoveFinishedEmitter:Void(emitter:ParticleEmitter)
        For Local particle:Particle = EachIn GetParticlesForEmitter(emitter)
            pool.Put(particle)
        End
        emitters.RemoveEach(emitter)
    End

    Method LaunchNewParticles:Void(deltatimer:DeltaTimer, emitter:ParticleEmitter)
        For Local i:Int = 0 To emitter.GetLaunchAmount(deltatimer)
            Local newParticle:Particle = pool.Get()
            GetParticlesForEmitter(emitter).AddLast(newParticle)

            newParticle.Reset()
            emitter.OnParticleLaunch(deltatimer, newParticle)
        End
    End

    Method UpdateParticles:Void(deltatimer:DeltaTimer, emitter:ParticleEmitter)
        For Local particle:Particle = EachIn GetParticlesForEmitter(emitter)
            oldParticlePos.x = particle.position.x
            oldParticlePos.y = particle.position.y
            emitter.OnParticleUpdate(deltatimer, particle)

            If particle.active
                particle.lifetime += deltatimer.frameTime
                particle.previousPosition.x = oldParticlePos.x
                particle.previousPosition.y = oldParticlePos.y
            Else
                GetParticlesForEmitter(emitter).Remove(particle)
                pool.Put(particle)
            End
        End
    End

    Method GetParticlesForEmitter:List<Particle>(emitter:ParticleEmitter)
        Return particles.Get(emitter.GetName())
    End
End

Strict

Private

Import bono.graphics
Import bono.kernel
Import bono.utils
Import particle
Import particleemitter

Public

Class ParticleSystem Implements AppObserver
    Private

    Field sprite:Sprite
    Field emitters:Stack<ParticleEmitter> = New Stack<ParticleEmitter>()
    Field particles:StringMap<List<Particle>> = New StringMap<List<Particle>>()
    Field pool:Pool<Particle> = New Pool<Particle>()
    Field oldParticlePos:Vector2D = New Vector2D()

    Public

    Method New(sprite:Sprite)
        Self.sprite = sprite
        sprite.SetHandle(sprite.GetCenter())
    End

    Method Add:Void(emitter:ParticleEmitter)
        CheckEmitterName(emitter)
        emitters.Push(emitter)
        particles.Add(emitter.GetName(), New List<Particle>())
    End

    Method OnLoading:Void()
    End

    Method OnUpdate:Void(deltatimer:DeltaTimer)
        RemoveFinishedEmitter()
        LaunchNewParticles(deltatimer)
        UpdateParticles(deltatimer)
    End

    Method OnRender:Void()
        For Local emitter:ParticleEmitter = EachIn emitters
            For Local particle:Particle = EachIn GetParticlesForEmitter(emitter)
                particle.Apply(sprite)
                sprite.OnRender()
            End
        End
    End

    Method OnResume:Void()
    End

    Method OnSuspend:Void()
    End

    Method GetStats:String()
        Local result:String = ""
        result += "Emitter size: " + emitters.Length() + "~n"
        result += "   Pool size: " + pool.Length() + "~n"
        result += "-------------~n"

        For Local emitter:ParticleEmitter = EachIn emitters
            result += emitter.GetName()
            result += ": "
            result += GetParticlesForEmitter(emitter).Count()
            result += "~n"
        End

        Return result
    End

    Private

    Method CheckEmitterName:Void(emitter:ParticleEmitter)
        Local name:String = emitter.GetName()

        If name.Trim().Length() = 0
            Error("Emitter name is empty")
        End

        If particles.Contains(name)
            Error("There is already an emitter named " + name)
        End
    End

    Method RemoveFinishedEmitter:Void()
        For Local emitter:ParticleEmitter = EachIn emitters
            If Not emitter.CanBeRemoved() Then Continue

            For Local particle:Particle = EachIn GetParticlesForEmitter(emitter)
                pool.Put(particle)
            End
            emitters.RemoveEach(emitter)
        End
    End

    Method LaunchNewParticles:Void(deltatimer:DeltaTimer)
        For Local emitter:ParticleEmitter = EachIn emitters
            For Local i:Int = 0 To emitter.GetLaunchAmount(deltatimer)
                Local newParticle:Particle = pool.Get()
                GetParticlesForEmitter(emitter).AddLast(newParticle)

                newParticle.Reset()
                emitter.OnParticleLaunch(deltatimer, newParticle)
            End
        End
    End

    Method UpdateParticles:Void(deltatimer:DeltaTimer)
        For Local emitter:ParticleEmitter = EachIn emitters
            For Local particle:Particle = EachIn GetParticlesForEmitter(emitter)
                oldParticlePos.x = particle.position.x
                oldParticlePos.y = particle.position.y
                emitter.OnParticleUpdate(deltatimer, particle)

                If particle.active
                    particle.lifetime += deltatimer.frameTime
                    particle.previousPosition.x = oldParticlePos.x
                    particle.previousPosition.y = oldParticlePos.y
                Else
                    GetParticlesForEmitter(emitter).Remove(particle)
                    pool.Put(particle)
                End
            End
        End
    End

    Method GetParticlesForEmitter:List<Particle>(emitter:ParticleEmitter)
        Return particles.Get(emitter.GetName())
    End
End


Sprite.SetHandle(Vector2D) added

Sprite.SetHandle(Vector2D) added

Strict

Private

Import align
Import basedisplayobject
Import bono.kernel
Import bono.utils
Import mojo.graphics

Public

Class Sprite Extends BaseDisplayObject
    Private

    Field currentFrame:Int
    Field frameCount:Int
    Field frameSize:Vector2D
    Field frameTimer:Int
    Field image:Image
    Field imageName:String
    Field _scale:Vector2D = New Vector2D(1, 1)
    Field _halign:Int = Align.LEFT
    Field _valign:Int = Align.TOP
    Field renderPos:Vector2D

    Public

    Field frameSpeed:Int
    Field loopAnimation:Bool
    Field rotation:Float

    Method New(imageName:String, pos:Vector2D=Null)
        Self.imageName = imageName

        LoadImage()
        If pos Then SetPosition(pos)
    End

    Method New(imageName:String, frameSize:Vector2D, frameCount:Int, frameSpeed:Int, pos:Vector2D=Null)
        Self.imageName = imageName
        Self.frameSize = frameSize
        Self.frameCount = frameCount
        Self.frameSpeed = frameSpeed

        LoadImage()
        If pos Then SetPosition(pos)
    End

    Method OnRender:Void()
        If Not renderPos Then CalculateRenderPos()
        GetColor().Activate()
        DrawImage(image, renderPos.x, renderPos.y, rotation, scale.x, scale.y, currentFrame)
        GetColor().Deactivate()
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If frameCount <= 0 Then Return
        If animationIsDone Then Return

        If frameTimer < frameSpeed
            frameTimer += deltaTimer.frameTime
            Return
        End

        If (currentFrame + 1) = frameCount
            If loopAnimation
                currentFrame = 1
            End
        Else
            currentFrame += 1
        End
        frameTimer = 0
    End

    Method Restart:Void()
        currentFrame = 0
    End

    Method animationIsDone:Bool() Property
        If loopAnimation Then Return False
        Return (currentFrame = frameCount)
    End

    Method scale:Vector2D() Property
        Return _scale
    End

    Method scale:Void(newScale:Vector2D) Property
        If image Then GetSize().Div(_scale).Mul(newScale)
        _scale = newScale
        renderPos = Null
    End

    Method filepath:String() Property
        Return imageName
    End

    Method halign:Int() Property
        Return _halign
    End

    Method halign:Void(newAlign:Int) Property
        _halign = newAlign
        renderPos = Null
    End

    Method valign:Int() Property
        Return _valign
    End

    Method valign:Void(newAlign:Int) Property
        _valign = newAlign
        renderPos = Null
    End

    Method SetPosition:Void(newPos:Vector2D)
        Super.SetPosition(newPos)
        renderPos = Null
    End

    Method Collide:Bool(checkPos:Vector2D)
        Local offset:Vector2D = New Vector2D()
        Align.AdjustHorizontal(offset, Self, halign)
        Align.AdjustVertical(offset, Self, valign)

        Return Super.Collide(checkPos.Copy().Sub(offset))
    End

    Method SetHandle:Void(pos:Vector2D)
        image.SetHandle(pos.x, pos.y)
    End

    Private

    Method CalculateRenderPos:Void()
        renderPos = GetPosition().Copy()
        Align.AdjustHorizontal(renderPos, Self, halign)
        Align.AdjustVertical(renderPos, Self, valign)
    End

    Method LoadImage:Void()
        If frameSize
            image = graphics.LoadImage(imageName, frameSize.x, frameSize.y, frameCount)
            SetSize(frameSize.Copy())
        Else
            image = graphics.LoadImage(imageName)
            SetSize(New Vector2D(image.Width(), image.Height()))
        End

        If Not image Then Error("Unable to load: " + imageName)
        GetSize().Mul(scale)
    End
End

Strict

Private

Import align
Import basedisplayobject
Import bono.kernel
Import bono.utils
Import mojo.graphics

Public

Class Sprite Extends BaseDisplayObject
    Private

    Field currentFrame:Int
    Field frameCount:Int
    Field frameSize:Vector2D
    Field frameTimer:Int
    Field image:Image
    Field imageName:String
    Field _scale:Vector2D = New Vector2D(1, 1)
    Field _halign:Int = Align.LEFT
    Field _valign:Int = Align.TOP
    Field renderPos:Vector2D

    Public

    Field frameSpeed:Int
    Field loopAnimation:Bool
    Field rotation:Float

    Method New(imageName:String, pos:Vector2D=Null)
        Self.imageName = imageName

        LoadImage()
        If pos Then SetPosition(pos)
    End

    Method New(imageName:String, frameSize:Vector2D, frameCount:Int, frameSpeed:Int, pos:Vector2D=Null)
        Self.imageName = imageName
        Self.frameSize = frameSize
        Self.frameCount = frameCount
        Self.frameSpeed = frameSpeed

        LoadImage()
        If pos Then SetPosition(pos)
    End

    Method OnRender:Void()
        If Not renderPos Then CalculateRenderPos()
        GetColor().Activate()
        DrawImage(image, renderPos.x, renderPos.y, rotation, scale.x, scale.y, currentFrame)
        GetColor().Deactivate()
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If frameCount <= 0 Then Return
        If animationIsDone Then Return

        If frameTimer < frameSpeed
            frameTimer += deltaTimer.frameTime
            Return
        End

        If (currentFrame + 1) = frameCount
            If loopAnimation
                currentFrame = 1
            End
        Else
            currentFrame += 1
        End
        frameTimer = 0
    End

    Method Restart:Void()
        currentFrame = 0
    End

    Method animationIsDone:Bool() Property
        If loopAnimation Then Return False
        Return (currentFrame = frameCount)
    End

    Method scale:Vector2D() Property
        Return _scale
    End

    Method scale:Void(newScale:Vector2D) Property
        If image Then GetSize().Div(_scale).Mul(newScale)
        _scale = newScale
        renderPos = Null
    End

    Method filepath:String() Property
        Return imageName
    End

    Method halign:Int() Property
        Return _halign
    End

    Method halign:Void(newAlign:Int) Property
        _halign = newAlign
        renderPos = Null
    End

    Method valign:Int() Property
        Return _valign
    End

    Method valign:Void(newAlign:Int) Property
        _valign = newAlign
        renderPos = Null
    End

    Method SetPosition:Void(newPos:Vector2D)
        Super.SetPosition(newPos)
        renderPos = Null
    End

    Method Collide:Bool(checkPos:Vector2D)
        Local offset:Vector2D = New Vector2D()
        Align.AdjustHorizontal(offset, Self, halign)
        Align.AdjustVertical(offset, Self, valign)

        Return Super.Collide(checkPos.Copy().Sub(offset))
    End

    Private

    Method CalculateRenderPos:Void()
        renderPos = GetPosition().Copy()
        Align.AdjustHorizontal(renderPos, Self, halign)
        Align.AdjustVertical(renderPos, Self, valign)
    End

    Method LoadImage:Void()
        If frameSize
            image = graphics.LoadImage(imageName, frameSize.x, frameSize.y, frameCount)
            SetSize(frameSize.Copy())
        Else
            image = graphics.LoadImage(imageName)
            SetSize(New Vector2D(image.Width(), image.Height()))
        End

        If Not image Then Error("Unable to load: " + imageName)
        GetSize().Mul(scale)
    End
End


InputController calls EnableKeyboard() only once

Otherwise Android will continuously close and reopen the keyboard, consume tons
of CPU and the app / half of the OS becomes useless.

Strict

Private

Import directorevents
Import keyevent
Import mojo
Import touchevent
Import vector2d

Public

Class InputController
    Private

    Const FIRST_KEY:Int = KEY_BACKSPACE
    Const LAST_KEY:Int = KEY_QUOTES
    Const KEY_COUNT:Int = LAST_KEY - FIRST_KEY + 1
    Field _touchFingers:Int = 1
    Field isKeyDown:Bool[KEY_COUNT]
    Field isTouchDown:Bool[MAX_TOUCH_FINGERS]
    Field keyDownDispatched:Bool[KEY_COUNT]
    Field keyEvents:KeyEvent[KEY_COUNT]
    Field touchDownDispatched:Bool[MAX_TOUCH_FINGERS]
    Field touchEvents:TouchEvent[MAX_TOUCH_FINGERS]
    Field keyboardEnabled:Bool

    Public

    Const MAX_TOUCH_FINGERS:Int = 31
    Const RETAIN_UNLIMITED:Int = -1
    Field scale:Vector2D = New Vector2D(0, 0)
    Field trackKeys:Bool = False
    Field trackTouch:Bool = False
    Field touchMinDistance:Float = 5
    Field touchRetainSize:Int = 5

    Method OnUpdate:Void(handler:DirectorEvents)
        If trackTouch
            ReadTouch()
            ProcessTouch(handler)
        End

        If trackKeys
            If Not keyboardEnabled
                keyboardEnabled = True
                EnableKeyboard()
            End
            ReadKeys()
            ProcessKeys(handler)
        Else
            keyboardEnabled = False
            DisableKeyboard()
        End
    End

    Method touchFingers:Void(number:Int) Property
        If number > MAX_TOUCH_FINGERS
            Error("Only " + MAX_TOUCH_FINGERS + " can be tracked.")
        End
        If Not number > 0
            Error("Number of fingers must be greater than 0.")
        End

        _touchFingers = number
    End

    Private

    Method ProcessKeys:Void(handler:DirectorEvents)
        For Local i:Int = 0 Until KEY_COUNT
            If keyEvents[i] = Null Then Continue

            If Not keyDownDispatched[i]
                handler.OnKeyDown(keyEvents[i])
                keyDownDispatched[i] = True
            ElseIf Not isKeyDown[i]
                handler.OnKeyUp(keyEvents[i])
                keyEvents[i] = Null
            Else
                handler.OnKeyPress(keyEvents[i])
            End
        End
    End

    Method ReadKeys:Void()
        Local lastKeyDown:Bool

        For Local i:Int = 0 Until KEY_COUNT
            lastKeyDown = isKeyDown[i]
            isKeyDown[i] = Bool(KeyDown(FIRST_KEY + i))

            If Not isKeyDown[i] And Not lastKeyDown Then Continue

            If keyEvents[i] = Null
                keyDownDispatched[i] = False
                keyEvents[i] = New KeyEvent(FIRST_KEY + i)
            End
        End
    End

    Method ProcessTouch:Void(handler:DirectorEvents)
        For Local i:Int = 0 Until _touchFingers
            If touchEvents[i] = Null Then Continue

            If Not touchDownDispatched[i]
                handler.OnTouchDown(touchEvents[i].Copy())
                touchDownDispatched[i] = True
            ElseIf Not isTouchDown[i]
                handler.OnTouchUp(touchEvents[i])
                touchEvents[i] = Null
            Else
                handler.OnTouchMove(touchEvents[i])
            End
        End
    End

    Method ReadTouch:Void()
        Local scaledVector:Vector2D
        Local diffVector:Vector2D
        Local lastTouchDown:Bool

        For Local i:Int = 0 Until _touchFingers
            lastTouchDown = isTouchDown[i]
            isTouchDown[i] = Bool(TouchDown(i))

            If Not isTouchDown[i] And Not lastTouchDown Then Continue

            If touchEvents[i] = Null
                touchDownDispatched[i] = False
                touchEvents[i] = New TouchEvent(i)
            End

            scaledVector = New Vector2D(TouchX(i), TouchY(i)).Div(scale)
            diffVector = scaledVector.Copy().Sub(touchEvents[i].prevPos)

            If diffVector.Length() >= touchMinDistance
                touchEvents[i].Add(scaledVector)
                If touchRetainSize > -1 Then touchEvents[i].Trim(touchRetainSize)
            End
        End
    End
End

Strict

Private

Import directorevents
Import keyevent
Import mojo
Import touchevent
Import vector2d

Public

Class InputController
    Private

    Const FIRST_KEY:Int = KEY_BACKSPACE
    Const LAST_KEY:Int = KEY_QUOTES
    Const KEY_COUNT:Int = LAST_KEY - FIRST_KEY + 1
    Field _touchFingers:Int = 1
    Field isKeyDown:Bool[KEY_COUNT]
    Field isTouchDown:Bool[MAX_TOUCH_FINGERS]
    Field keyDownDispatched:Bool[KEY_COUNT]
    Field keyEvents:KeyEvent[KEY_COUNT]
    Field touchDownDispatched:Bool[MAX_TOUCH_FINGERS]
    Field touchEvents:TouchEvent[MAX_TOUCH_FINGERS]

    Public

    Const MAX_TOUCH_FINGERS:Int = 31
    Const RETAIN_UNLIMITED:Int = -1
    Field scale:Vector2D = New Vector2D(0, 0)
    Field trackKeys:Bool = False
    Field trackTouch:Bool = False
    Field touchMinDistance:Float = 5
    Field touchRetainSize:Int = 5

    Method OnUpdate:Void(handler:DirectorEvents)
        If trackTouch
            ReadTouch()
            ProcessTouch(handler)
        End

        If trackKeys
            EnableKeyboard()
            ReadKeys()
            ProcessKeys(handler)
        Else
            DisableKeyboard()
        End
    End

    Method touchFingers:Void(number:Int) Property
        If number > MAX_TOUCH_FINGERS
            Error("Only " + MAX_TOUCH_FINGERS + " can be tracked.")
        End
        If Not number > 0
            Error("Number of fingers must be greater than 0.")
        End

        _touchFingers = number
    End

    Private

    Method ProcessKeys:Void(handler:DirectorEvents)
        For Local i:Int = 0 Until KEY_COUNT
            If keyEvents[i] = Null Then Continue

            If Not keyDownDispatched[i]
                handler.OnKeyDown(keyEvents[i])
                keyDownDispatched[i] = True
            ElseIf Not isKeyDown[i]
                handler.OnKeyUp(keyEvents[i])
                keyEvents[i] = Null
            Else
                handler.OnKeyPress(keyEvents[i])
            End
        End
    End

    Method ReadKeys:Void()
        Local lastKeyDown:Bool

        For Local i:Int = 0 Until KEY_COUNT
            lastKeyDown = isKeyDown[i]
            isKeyDown[i] = Bool(KeyDown(FIRST_KEY + i))

            If Not isKeyDown[i] And Not lastKeyDown Then Continue

            If keyEvents[i] = Null
                keyDownDispatched[i] = False
                keyEvents[i] = New KeyEvent(FIRST_KEY + i)
            End
        End
    End

    Method ProcessTouch:Void(handler:DirectorEvents)
        For Local i:Int = 0 Until _touchFingers
            If touchEvents[i] = Null Then Continue

            If Not touchDownDispatched[i]
                handler.OnTouchDown(touchEvents[i].Copy())
                touchDownDispatched[i] = True
            ElseIf Not isTouchDown[i]
                handler.OnTouchUp(touchEvents[i])
                touchEvents[i] = Null
            Else
                handler.OnTouchMove(touchEvents[i])
            End
        End
    End

    Method ReadTouch:Void()
        Local scaledVector:Vector2D
        Local diffVector:Vector2D
        Local lastTouchDown:Bool

        For Local i:Int = 0 Until _touchFingers
            lastTouchDown = isTouchDown[i]
            isTouchDown[i] = Bool(TouchDown(i))

            If Not isTouchDown[i] And Not lastTouchDown Then Continue

            If touchEvents[i] = Null
                touchDownDispatched[i] = False
                touchEvents[i] = New TouchEvent(i)
            End

            scaledVector = New Vector2D(TouchX(i), TouchY(i)).Div(scale)
            diffVector = scaledVector.Copy().Sub(touchEvents[i].prevPos)

            If diffVector.Length() >= touchMinDistance
                touchEvents[i].Add(scaledVector)
                If touchRetainSize > -1 Then touchEvents[i].Trim(touchRetainSize)
            End
        End
    End
End


AngelFont bugs fixed

AngelFont bugs 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:Int
	Field second:Int
	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(".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


Minor bug fixes to the cursor demo, etc.

#Rem
	A basic networking demo showcasing I/O
	abstraction, network integration, etc.
#End

Strict

Public

' Preprocessor related:

' If enabled, this could cause timeouts.
#MOJO_AUTO_SUSPEND_ENABLED = False

' Imports:
Import mojo

Import brl.stream

Import networking

' Classes:
Class Game Extends App Implements NetworkListener Final
	' Constant variable(s):
	Const PORT:= 27015

	Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_UDP
	'Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_TCP

	' Message types:
	Const MSG_TYPE_WELCOME:= NetworkEngine.MSG_TYPE_CUSTOM
	Const MSG_TYPE_CREATE_PLAYER:= (MSG_TYPE_WELCOME+1)
	Const MSG_TYPE_CREATE_PLAYERS_IN_BULK:= (MSG_TYPE_CREATE_PLAYER+1)
	Const MSG_TYPE_STATE:= (MSG_TYPE_CREATE_PLAYERS_IN_BULK+1)
	Const MSG_TYPE_STATES_IN_BULK:= (MSG_TYPE_STATE+1)
	Const MSG_TYPE_DELETE_PLAYER:= (MSG_TYPE_STATES_IN_BULK+1)

	' State types:
	Const STATE_WAITING:= 0
	Const STATE_GAMEPLAY:= 1

	' Functions:

	' I/O related:
	Function ReadWelcomeMessage:Void(S:Stream, PlayerHandle:LocalPlayer)
		PlayerHandle.ID = S.ReadInt()
		PlayerHandle.Load(S)

		Return
	End

	Function WriteWelcomeMessage:Void(S:Stream, PlayerHandle:NetPlayer)
		S.WriteInt(PlayerHandle.ID)
		PlayerHandle.Save(S)

		Return
	End

	' Constructor(s):
	Method OnCreate:Int()
		SetUpdateRate(0)

		Players = New List<Player>()

		SendTime = 50 ' Milliseconds.

		' Return the default response.
		Return 0
	End

	' Methods:
	Method InitNetwork:Void()
		Network = New NetworkEngine()

		Network.SetCallback(Self)

		' Just for the sake of doing it, set the current random-seed.
		Seed = Millisecs()

		Return
	End

	Method Host:Void(Port:Int=PORT)
		InitNetwork()

		Network.Host(Port, True, PROTOCOL)

		Print("Hosting server...")

		Return
	End

	Method Connect:Void(Hostname:String, Port:Int=PORT)
		InitNetwork()

		Network.Connect(Hostname, Port, True, PROTOCOL)

		Print("Connecting to " + Hostname + ":" + Port + "...")

		Return
	End

	Method OnUpdate:Int()
		UpdateAsyncEvents()

		Select State
			Case STATE_WAITING
				If (WhileWaiting()) Then
					State = STATE_GAMEPLAY
				Endif
			Case STATE_GAMEPLAY
				Update()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnRender:Int()
		Cls(205.0, 205.0, 205.0)

		Select State
			Case STATE_WAITING
				DrawText("Press F1 or click/tap to host.", 16.0, 16.0)
				DrawText("Press F2 to connect locally.", 16.0, 32.0)
			Case STATE_GAMEPLAY
				For Local P:= Eachin Players
					P.Render()
				Next
		End Select

		' Return the default response.
		Return 0
	End

	Method WhileWaiting:Bool()
		If (KeyHit(KEY_F1) Or MouseHit(MOUSE_LEFT)) Then
			Host()

			LocalCursor = New LocalPlayer()

			LocalCursor.RandomizeColor()

			Players.AddLast(LocalCursor)

			' Switch to gameplay.
			Return True
		Endif

		If (KeyHit(KEY_F2)) Then
			'Connect("127.0.0.1")
			Connect("192.168.1.32")

			' Switch to gameplay.
			Return True
		Endif

		' Return the default response.
		Return False
	End

	' Gameplay update routine:
	Method Update:Void()
		For Local P:= Eachin Players
			P.Update()
		Next

		If (LocalCursor <> Null) Then
			If (Network.Open()) Then
				If (Millisecs()-SendTimer >= SendTime) Then
					If (Network.IsClient) Then
						SendPlayerState(LocalCursor)
					Else
						SendPlayerStatesInBulk()
					Endif

					SendTimer = Millisecs()
				Endif
			Else
				OnClose()

				Return
			Endif
		Endif

		Return
	End

	' I/O related:
	Method ReadCreatePlayerMessage:Void(S:Stream, C:Client)
		Local ID:= S.ReadInt()

		Local P:= New NetPlayer(ID, C)

		P.Load(S)

		Players.AddLast(P)

		Return
	End

	Method WriteCreatePlayerMessage:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Save(S)

		Return
	End

	Method ReadDeletePlayerMessage:Void(S:Stream)
		Local ID:= S.ReadInt()

		If (LocalCursor.ID = ID) Then
			Network.CloseAsync()
		Else
			RemovePlayer(ID)
		Endif

		Return
	End

	Method WriteDeletePlayerMessage:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		Return
	End

	Method ReadPlayersInBulk:Void(S:Stream, C:Client)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadCreatePlayerMessage(S, C)
		Next

		Return
	End

	Method WritePlayersInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WriteCreatePlayerMessage(S, P)
		Next

		Return
	End

	Method ReadPlayerState:Player(S:Stream)
		Local ID:= S.ReadInt()

		For Local P:= Eachin Players
			If (P.ID = ID) Then
				P.Read(S)

				Return P
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method WritePlayerState:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Write(S)

		Return
	End

	Method ReadPlayerStatesInBulk:Void(S:Stream)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadPlayerState(S)
		Next

		Return
	End

	Method WritePlayerStatesInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WritePlayerState(S, P)
		Next

		Return
	End

	' This tells 'C' about the specified 'Player'.
	Method SendPlayer:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WriteCreatePlayerMessage(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYER, True)

		Network.ReleasePacket(P)

		Return
	End

	' This tells 'C' about every active player.
	Method SendPlayersInBulk:Void(C:Client)
		Local P:= Network.AllocatePacket()

		WritePlayersInBulk(P)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYERS_IN_BULK, True)

		Network.ReleasePacket(P)

		Return
	End

	Method DisconnectPlayer:Void(PlayerHandle:Player)
		RemovePlayer(PlayerHandle)

		Local P:= Network.AllocatePacket()

		WriteDeletePlayerMessage(P, PlayerHandle)

		Network.Send(P, MSG_TYPE_DELETE_PLAYER, True)

		Network.ReleasePacket(P)

		Return
	End

	Method SendWelcomeMessage:Void(PlayerHandle:NetPlayer)
		Local P:= Network.AllocatePacket()

		WriteWelcomeMessage(P, PlayerHandle)

		Network.Send(P, PlayerHandle.NetworkHandle, MSG_TYPE_WELCOME, True)

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to send to everyone, and clients to send to servers.
	Method SendPlayerState:Void(PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to relay/update player states for specific 'Clients'.
	Method SendPlayerState:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is mainly used by hosts; sends all of the player states.
	Method SendPlayerStatesInBulk:Void()
		Local P:= Network.AllocatePacket()

		WritePlayerStatesInBulk(P)

		Network.Send(P, MSG_TYPE_STATES_IN_BULK, True) ' False

		Network.ReleasePacket(P)

		Return
	End

	Method RemovePlayer:Void(ID:Int)
		For Local P:= Eachin Players
			If (P.ID = ID) Then
				RemovePlayer(P)

				Return
			Endif
		Next

		Return
	End

	Method RemovePlayer:Void(PlayerHandle:Player)
		Players.RemoveEach(PlayerHandle)

		Return
	End

	' Call-backs:
	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)
		If (Not Successful) Then
			#If CONFIG = "debug"
				DebugStop()
			#End

			OnClose()

			Return
		Endif

		Print("Socket bound.")

		Return
	End

	Method OnReceiveMessage:Void(Network:NetworkEngine, C:Client, Type:MessageType, Message:Stream, MessageSize:Int)
		Select Type
			Case MSG_TYPE_WELCOME
				Print("Received welcome message.")

				If (Network.IsClient) Then
					LocalCursor = New LocalPlayer()

					ReadWelcomeMessage(Message, LocalCursor)

					Players.AddLast(LocalCursor)
				Endif
			Case MSG_TYPE_CREATE_PLAYER
				ReadCreatePlayerMessage(Message, C)
			Case MSG_TYPE_CREATE_PLAYERS_IN_BULK
				ReadPlayersInBulk(Message, C)
			Case MSG_TYPE_STATE
				Local P:= ReadPlayerState(Message)

				#Rem
					' Relay the message to everyone else:
					For Local OtherClient:= Eachin Network
						If (OtherClient = C) Then
							Continue
						Endif

						SendPlayerState(OtherClient, P)
					Next
				#End
			Case MSG_TYPE_STATES_IN_BULK
				ReadPlayerStatesInBulk(Message)
			Case MSG_TYPE_DELETE_PLAYER
				ReadDeletePlayerMessage(Message)
		End Select

		Return
	End

	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:SocketAddress, BytesSent:Int)
		'Print("Sending operation complete.")

		Return
	End

	Method OnClientConnect:Bool(Network:NetworkEngine, Address:SocketAddress)
		' Return the default response. (Accept the new connection)
		Return True
	End

	' This is called once, at any time after 'OnClientConnect'.
	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)
		' Tell 'C' to create 'NetPlayer' objects.
		SendPlayersInBulk(C)

		' Lazy, but it works; get the next player ID.
		Local ID:= NextPlayerID; NextPlayerID += 1

		' Create a 'NetPlayer' to represent 'C'.
		Local P:= New NetPlayer(ID, C); P.RandomizeColor()

		' Tell all of the current players about 'P':
		For Local CurrentPlayer:= Eachin Players
			If (CurrentPlayer = LocalCursor) Then
				Continue
			Endif

			SendPlayer(CurrentPlayer.NetworkHandle, P)
		Next

		' Now that we've got that settled, add 'P' internally. (Must be done after)
		Players.AddLast(P)

		' Set the meta-flag for client-existence.
		ClientCreated = True

		' Welcome our new 'NetPlayer'.
		SendWelcomeMessage(P)

		Print("Client accepted: " + C.Address)

		Return
	End

	Method OnClientDisconnected:Void(Network:NetworkEngine, C:Client)
		#If CONFIG = "debug"
			DebugStop()
		#End

		Print("Client disconnected: " + C.Address)

		For Local P:= Eachin Players
			If (P.NetworkHandle = C) Then
				DisconnectPlayer(P)
			Endif
		Next

		Return
	End

	Method OnDisconnected:Void(Network:NetworkEngine)
		Print("Disconnected.")

		Return
	End

	' 'MegaPacket' callback layer:
	Method OnMegaPacketRequestAccepted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestSucceeded:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestFailed:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestAborted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketFinished:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketSent:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketDownSize:Bool(Network:NetworkEngine, MP:MegaPacket)
		Return False
	End

	' Fields:
	Field State:Int = STATE_WAITING
	Field SendTimer:Int, SendTime:Int

	Field LocalCursor:LocalPlayer
	Field NextPlayerID:= 1

	Field Network:NetworkEngine
	Field Players:List<Player>

	' Booleans / Flags:
	Field ClientCreated:Bool
End

Class Player Abstract
	' Constructor(s):
	Method New(X:Float, Y:Float)
		Self.X = X
		Self.Y = Y
	End

	' Methods (Abstract):
	Method Update:Void() Abstract

	' Methods (Implemented):
	Method RandomizeColor:Void()
		' Not the best, but it gets the job done.
		R = Rnd(0.0, 255.0)
		G = Rnd(0.0, 255.0)
		B = Rnd(0.0, 255.0)

		Return
	End

	Method Render:Void()
		PushMatrix()

		SetColor(R, G, B)

		Translate(-(Width / 2.0), -(Height / 2.0))
		DrawRect(X, Y, Width, Height)

		SetColor(255.0, 255.0, 255.0)

		PopMatrix()

		Return
	End

	Method Load:Void(S:Stream)
		R = S.ReadFloat()
		G = S.ReadFloat()
		B = S.ReadFloat()

		Return
	End

	Method Save:Void(S:Stream)
		S.WriteFloat(R)
		S.WriteFloat(G)
		S.WriteFloat(B)

		Return
	End

	Method Read:Void(S:Stream)
		X = S.ReadFloat()
		Y = S.ReadFloat()

		Return
	End

	Method Write:Void(S:Stream)
		S.WriteFloat(X)
		S.WriteFloat(Y)

		Return
	End

	' Properties:
	Method Width:Float() Property
		Return 32.0 ' 64.0
	End

	Method Height:Float() Property
		Return 32.0 ' 64.0
	End

	' Fields:

	' Meta:
	Field ID:Int = 0

	' Meta:

	' If we're the server, this will be the actual 'Client'.
	' If we're a client, this will be a handle to the server.
	Field NetworkHandle:Client

	' Position:
	Field X:Float = 0.0
	Field Y:Float = 0.0

	' Color (Defaults to white):
	Field R:Float = 255.0
	Field G:Float = 255.0
	Field B:Float = 255.0
End

Class LocalPlayer Extends Player ' Final
	' Constructor(s):
	Method New(X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)
	End

	' Methods:
	Method Update:Void()
		X = MouseX()
		Y = MouseY()

		Return
	End
End

Class NetPlayer Extends Player Final
	' Constructor(s):
	Method New(ID:Int, Handle:Client, X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)

		Self.ID = ID
		Self.NetworkHandle = Handle
	End

	' Methods:
	Method Update:Void()
		' Nothing so far.

		Return
	End
End

' Functions:
Function Main:Int()
	New Game()

	' Return the default response.
	Return 0
End

#Rem
	A basic networking demo showcasing I/O
	abstraction, network integration, etc.
#End

Strict

Public

' Preprocessor related:

' If enabled, this could cause timeouts.
#MOJO_AUTO_SUSPEND_ENABLED = False

' Imports:
Import mojo

Import brl.process
Import brl.stream

Import networking

' Classes:
Class Game Extends App Implements NetworkListener Final
	' Constant variable(s):
	Const PORT:= 27015

	Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_UDP
	'Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_TCP

	' Message types:
	Const MSG_TYPE_WELCOME:= NetworkEngine.MSG_TYPE_CUSTOM
	Const MSG_TYPE_CREATE_PLAYER:= (MSG_TYPE_WELCOME+1)
	Const MSG_TYPE_CREATE_PLAYERS_IN_BULK:= (MSG_TYPE_CREATE_PLAYER+1)
	Const MSG_TYPE_STATE:= (MSG_TYPE_CREATE_PLAYERS_IN_BULK+1)
	Const MSG_TYPE_STATES_IN_BULK:= (MSG_TYPE_STATE+1)

	' State types:
	Const STATE_WAITING:= 0
	Const STATE_GAMEPLAY:= 1

	' Functions:

	' I/O related:
	Function ReadWelcomeMessage:Void(S:Stream, PlayerHandle:LocalPlayer)
		PlayerHandle.ID = S.ReadInt()
		PlayerHandle.Load(S)

		Return
	End

	Function WriteWelcomeMessage:Void(S:Stream, PlayerHandle:NetPlayer)
		S.WriteInt(PlayerHandle.ID)
		PlayerHandle.Save(S)

		Return
	End

	' Constructor(s):
	Method OnCreate:Int()
		SetUpdateRate(0)

		Players = New List<Player>()

		SendTime = 50 ' Milliseconds.

		' Return the default response.
		Return 0
	End

	' Methods:
	Method InitNetwork:Void()
		Network = New NetworkEngine()

		Network.SetCallback(Self)

		' Just for the sake of doing it, set the current random-seed.
		Seed = Millisecs()

		Return
	End

	Method Host:Void(Port:Int=PORT)
		InitNetwork()

		Network.Host(Port, True, PROTOCOL)

		Print("Hosting server...")

		Return
	End

	Method Connect:Void(Hostname:String, Port:Int=PORT)
		InitNetwork()

		Network.Connect(Hostname, Port, True, PROTOCOL)

		Print("Connecting to " + Hostname + ":" + Port + "...")

		Return
	End

	Method OnUpdate:Int()
		UpdateAsyncEvents()

		Select State
			Case STATE_WAITING
				If (WhileWaiting()) Then
					State = STATE_GAMEPLAY
				Endif
			Case STATE_GAMEPLAY
				Update()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnRender:Int()
		Cls(205.0, 205.0, 205.0)

		Select State
			Case STATE_WAITING
				DrawText("Press F1 to host.", 16.0, 16.0)
				DrawText("Press F2 to connect locally.", 16.0, 32.0)
			Case STATE_GAMEPLAY
				For Local P:= Eachin Players
					P.Render()
				Next
		End Select

		' Return the default response.
		Return 0
	End

	Method WhileWaiting:Bool()
		If (KeyHit(KEY_F1)) Then
			Host()

			LocalCursor = New LocalPlayer()

			LocalCursor.RandomizeColor()

			Players.AddLast(LocalCursor)

			' Switch to gameplay.
			Return True
		Endif

		If (KeyHit(KEY_F2)) Then
			Connect("127.0.0.1")

			' Switch to gameplay.
			Return True
		Endif

		' Return the default response.
		Return False
	End

	' Gameplay update routine:
	Method Update:Void()
		For Local P:= Eachin Players
			P.Update()
		Next

		If (LocalCursor <> Null And Network.Open()) Then
			If (Millisecs()-SendTimer >= SendTime) Then
				If (Network.IsClient) Then
					SendPlayerState(LocalCursor)
				Else
					SendPlayerStatesInBulk()
				Endif

				SendTimer = Millisecs()
			Endif
		Endif

		Return
	End

	' I/O related:
	Method ReadCreatePlayerMessage:Void(S:Stream, C:Client)
		Local ID:= S.ReadInt()

		Local P:= New NetPlayer(ID, C)

		P.Load(S)

		Players.AddLast(P)

		Return
	End

	Method WriteCreatePlayerMessage:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Save(S)

		Return
	End

	Method ReadPlayersInBulk:Void(S:Stream, C:Client)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadCreatePlayerMessage(S, C)
		Next

		Return
	End

	Method WritePlayersInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WriteCreatePlayerMessage(S, P)
		Next

		Return
	End

	Method ReadPlayerState:Player(S:Stream)
		Local ID:= S.ReadInt()

		For Local P:= Eachin Players
			If (P.ID = ID) Then
				P.Read(S)

				Return P
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method WritePlayerState:Void(S:Stream, PlayerHandle:Player)
		S.WriteInt(PlayerHandle.ID)

		PlayerHandle.Write(S)

		Return
	End

	Method ReadPlayerStatesInBulk:Void(S:Stream)
		Local Count:= S.ReadInt()

		For Local I:= 1 To Count
			ReadPlayerState(S)
		Next

		Return
	End

	Method WritePlayerStatesInBulk:Void(S:Stream)
		S.WriteInt(Players.Count())

		For Local P:= Eachin Players
			WritePlayerState(S, P)
		Next

		Return
	End

	' This tells 'C' about the specified 'Player'.
	Method SendPlayer:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WriteCreatePlayerMessage(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYER)

		Network.ReleasePacket(P)

		Return
	End

	' This tells 'C' about every active player.
	Method SendPlayersInBulk:Void(C:Client)
		Local P:= Network.AllocatePacket()

		WritePlayersInBulk(P)

		Network.Send(P, C, MSG_TYPE_CREATE_PLAYERS_IN_BULK)

		Network.ReleasePacket(P)

		Return
	End

	Method SendWelcomeMessage:Void(PlayerHandle:NetPlayer)
		Local P:= Network.AllocatePacket()

		WriteWelcomeMessage(P, PlayerHandle)

		Network.Send(P, PlayerHandle.NetworkHandle, MSG_TYPE_WELCOME, True)

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to send to everyone, and clients to send to servers.
	Method SendPlayerState:Void(PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is used by servers to relay/update player states for specific 'Clients'.
	Method SendPlayerState:Void(C:Client, PlayerHandle:Player)
		Local P:= Network.AllocatePacket()

		WritePlayerState(P, PlayerHandle)

		Network.Send(P, C, MSG_TYPE_STATE, False) ' True

		Network.ReleasePacket(P)

		Return
	End

	' This is mainly used by hosts; sends all of the player states.
	Method SendPlayerStatesInBulk:Void()
		Local P:= Network.AllocatePacket()

		WritePlayerStatesInBulk(P)

		Network.Send(P, MSG_TYPE_STATES_IN_BULK, True) ' False

		Network.ReleasePacket(P)

		Return
	End

	' Call-backs:
	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)
		If (Not Successful) Then
			#If CONFIG = "debug"
				DebugStop()
			#End

			OnClose()

			Return
		Endif

		Print("Socket bound.")

		Return
	End

	Method OnReceiveMessage:Void(Network:NetworkEngine, C:Client, Type:MessageType, Message:Stream, MessageSize:Int)
		Select Type
			Case MSG_TYPE_WELCOME
				Print("Received welcome message.")

				If (Network.IsClient) Then
					LocalCursor = New LocalPlayer()

					ReadWelcomeMessage(Message, LocalCursor)

					Players.AddLast(LocalCursor)
				Endif
			Case MSG_TYPE_CREATE_PLAYER
				ReadCreatePlayerMessage(Message, C)
			Case MSG_TYPE_CREATE_PLAYERS_IN_BULK
				ReadPlayersInBulk(Message, C)
			Case MSG_TYPE_STATE
				Local P:= ReadPlayerState(Message)

				#Rem
					' Relay the message to everyone else:
					For Local OtherClient:= Eachin Network
						If (OtherClient = C) Then
							Continue
						Endif

						SendPlayerState(OtherClient, P)
					Next
				#End
			Case MSG_TYPE_STATES_IN_BULK
				ReadPlayerStatesInBulk(Message)
		End Select

		Return
	End

	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:SocketAddress, BytesSent:Int)
		'Print("Sending operation complete.")

		Return
	End

	Method OnClientConnect:Bool(Network:NetworkEngine, Address:SocketAddress)
		' Return the default response. (Accept the new connection)
		Return True
	End

	' This is called once, at any time after 'OnClientConnect'.
	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)
		' Tell 'C' to create 'NetPlayer' objects.
		SendPlayersInBulk(C)

		' Lazy, but it works; get the next player ID.
		Local ID:= NextPlayerID; NextPlayerID += 1

		' Create a 'NetPlayer' to represent 'C'.
		Local P:= New NetPlayer(ID, C); P.RandomizeColor()

		' Tell all of the current players about 'P':
		For Local CurrentPlayer:= Eachin Players
			If (CurrentPlayer = LocalCursor) Then
				Continue
			Endif

			SendPlayer(CurrentPlayer.NetworkHandle, P)
		Next

		' Now that we've got that settled, add 'P' internally. (Must be done after)
		Players.AddLast(P)

		' Set the meta-flag for client-existence.
		ClientCreated = True

		' Welcome our new 'NetPlayer'.
		SendWelcomeMessage(P)

		Print("Client accepted: " + C.Address)

		Return
	End

	Method OnClientDisconnected:Void(Network:NetworkEngine, C:Client)
		#If CONFIG = "debug"
			DebugStop()
		#End

		Print("Client disconnected: " + C.Address)

		For Local P:= Eachin Players
			If (P.NetworkHandle = C) Then
				Players.RemoveEach(P)
			Endif
		Next

		Return
	End

	Method OnDisconnected:Void(Network:NetworkEngine)
		Print("Disconnected.")

		Return
	End

	' 'MegaPacket' callback layer:
	Method OnMegaPacketRequestAccepted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestSucceeded:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestFailed:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketRequestAborted:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketFinished:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketSent:Void(Network:NetworkEngine, MP:MegaPacket)
		Return
	End

	Method OnMegaPacketDownSize:Bool(Network:NetworkEngine, MP:MegaPacket)
		Return False
	End

	' Fields:
	Field State:Int = STATE_WAITING
	Field SendTimer:Int, SendTime:Int

	Field LocalCursor:LocalPlayer
	Field NextPlayerID:= 1

	Field Network:NetworkEngine
	Field Players:List<Player>

	' Booleans / Flags:
	Field ClientCreated:Bool
End

Class Player Abstract
	' Constructor(s):
	Method New(X:Float, Y:Float)
		Self.X = X
		Self.Y = Y
	End

	' Methods (Abstract):
	Method Update:Void() Abstract

	' Methods (Implemented):
	Method RandomizeColor:Void()
		' Not the best, but it gets the job done.
		R = Rnd(0.0, 255.0)
		G = Rnd(0.0, 255.0)
		B = Rnd(0.0, 255.0)

		Return
	End

	Method Render:Void()
		PushMatrix()

		SetColor(R, G, B)

		Translate(-(Width / 2.0), -(Height / 2.0))
		DrawRect(X, Y, Width, Height)

		SetColor(255.0, 255.0, 255.0)

		PopMatrix()

		Return
	End

	Method Load:Void(S:Stream)
		R = S.ReadFloat()
		G = S.ReadFloat()
		B = S.ReadFloat()

		Return
	End

	Method Save:Void(S:Stream)
		S.WriteFloat(R)
		S.WriteFloat(G)
		S.WriteFloat(B)

		Return
	End

	Method Read:Void(S:Stream)
		X = S.ReadFloat()
		Y = S.ReadFloat()

		Return
	End

	Method Write:Void(S:Stream)
		S.WriteFloat(X)
		S.WriteFloat(Y)

		Return
	End

	' Properties:
	Method Width:Float() Property
		Return 32.0 ' 64.0
	End

	Method Height:Float() Property
		Return 32.0 ' 64.0
	End

	' Fields:

	' Meta:
	Field ID:Int = 0

	' Meta:

	' If we're the server, this will be the actual 'Client'.
	' If we're a client, this will be a handle to the server.
	Field NetworkHandle:Client

	' Position:
	Field X:Float = 0.0
	Field Y:Float = 0.0

	' Color (Defaults to white):
	Field R:Float = 255.0
	Field G:Float = 255.0
	Field B:Float = 255.0
End

Class LocalPlayer Extends Player ' Final
	' Constructor(s):
	Method New(X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)
	End

	' Methods:
	Method Update:Void()
		X = MouseX()
		Y = MouseY()

		Return
	End
End

Class NetPlayer Extends Player Final
	' Constructor(s):
	Method New(ID:Int, Handle:Client, X:Float=0.0, Y:Float=0.0)
		Super.New(X, Y)

		Self.ID = ID
		Self.NetworkHandle = Handle
	End

	' Methods:
	Method Update:Void()
		' Nothing so far.

		Return
	End
End

' Functions:
Function Main:Int()
	New Game()

	' Return the default response.
	Return 0
End


fix missin OnSceneEnter & -Leave events in SceneTransitionFlash

Strict

Private

Import bono

Public

Class SceneTransitionFlash Extends SceneTransitionProxy Implements Colorable
    Private

    Field animation:Animation
    Field intro:Animation
    Field effect := New EffectColorAlpha(1.0, 0.0)
    Field blend := New ColorBlend()
    Field switched := True

    Public

    Method New()
        Throw New InvalidConstructorException("use New(Float, Transition)")
    End

    Method New(duration:Float, transition:Transition)
        animation = New Animation(duration, transition)
        animation.AddLast(effect)

        effect.AddLast(blend)
    End

    Method WithIntro:Void(duration:Float, transition:Transition)
        intro = New Animation(duration, transition)
        intro.AddLast(New EffectReverse(effect))
    End

    Method SetColor:Void(newColor:Color)
        blend.SetColor(newColor)
    End

    Method GetColor:Color()
        Return blend.GetColor()
    End

    Method Switch:Void(prevScene:Sceneable, nextScene:Sceneable)
        If Not switched Then TriggerSceneLeaveAndEnter()

        Super.Switch(prevScene, nextScene)
        switched = False
        animation.Restart()

        If intro
            intro.Restart()
            activeScene = prevScene
        Else
            SwitchOver()
        End
    End

    Method OnRender:Void()
        Super.OnRender()
        blend.OnRender()
    End

    Method OnUpdate:Void(timer:DeltaTimer)
        If intro
            intro.OnUpdate(timer)
            If intro.IsPlaying()
                Super.OnUpdate(timer)
                Return
            Else
                SwitchOver()
            End
        End

        animation.OnUpdate(timer)
        If animation.IsPlaying()
            Super.OnUpdate(timer)
        Else
            Director.Shared().GetApp().SetHandler(nextScene)
        End
    End

    Private

    Method SwitchOver:Void()
        If switched Then Return
        switched = True

        activeScene = nextScene
        TriggerSceneLeaveAndEnter()
    End

    Method TriggerSceneLeaveAndEnter:Void()
        If prevScene Then prevScene.OnSceneLeave()
        If nextScene Then nextScene.OnSceneEnter()
    End
End

Strict

Private

Import bono

Public

Class SceneTransitionFlash Extends SceneTransitionProxy Implements Colorable
    Private

    Field animation:Animation
    Field intro:Animation
    Field effect := New EffectColorAlpha(1.0, 0.0)
    Field blend := New ColorBlend()
    Field switched:Bool

    Public

    Method New()
        Throw New InvalidConstructorException("use New(Float, Transition)")
    End

    Method New(duration:Float, transition:Transition)
        animation = New Animation(duration, transition)
        animation.AddLast(effect)

        effect.AddLast(blend)
    End

    Method WithIntro:Void(duration:Float, transition:Transition)
        intro = New Animation(duration, transition)
        intro.AddLast(New EffectReverse(effect))
    End

    Method SetColor:Void(newColor:Color)
        blend.SetColor(newColor)
    End

    Method GetColor:Color()
        Return blend.GetColor()
    End

    Method Switch:Void(prevScene:Sceneable, nextScene:Sceneable)
        Super.Switch(prevScene, nextScene)
        switched = False
        animation.Restart()

        If intro
            intro.Restart()
            activeScene = prevScene
        Else
            SwitchOver()
        End
    End

    Method OnRender:Void()
        Super.OnRender()
        blend.OnRender()
    End

    Method OnUpdate:Void(timer:DeltaTimer)
        If intro
            intro.OnUpdate(timer)
            If intro.IsPlaying()
                Super.OnUpdate(timer)
                Return
            Else
                SwitchOver()
            End
        End

        animation.OnUpdate(timer)
        If animation.IsPlaying()
            Super.OnUpdate(timer)
        Else
            Director.Shared().GetApp().SetHandler(nextScene)
        End
    End

    Private

    Method SwitchOver:Void()
        If switched Then Return
        switched = True

        activeScene = nextScene
        If prevScene Then prevScene.OnSceneLeave()
        If nextScene Then nextScene.OnSceneEnter()
    End
End


Removed unneeded import

Removed unneeded import

Strict

Import mojo
Import diddy

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

Global titleScreen:TitleScreen
Global gameScreen:GameScreen

Class MyGame Extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()
		titleScreen = New TitleScreen
		gameScreen = new GameScreen
		titleScreen.PreStart()
		Return 0
	End
End

Class TitleScreen Extends Screen
	Method New()
		name = "Title"
	End

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

	Method Render:Void()
		Cls
		DrawText "TITLE SCREEN", SCREEN_WIDTH2, SCREEN_HEIGHT2, 0.5, 0.5
		DrawText "CLICK TO PLAY", SCREEN_WIDTH2, SCREEN_HEIGHT2 + 10, 0.5, 0.5
		FPSCounter.Draw(0,0)
	End

	Method Update:Void()
		if game.mouseHit
			game.screenFade.Start(50, True, True, True)
			game.nextScreen = gameScreen
		End
	End
End

Class GameScreen Extends Screen
	Field tilemap:MyTileMap
	Field offsetX:Int, offsetY:Int
	Field str$

	Method New()
		name = "Game"
	End

	Method Start:Void()
		Local reader:MyTiledTileMapReader = New MyTiledTileMapReader
		Local tm:TileMap = reader.LoadMap("maps/map.xml")
		tilemap = MyTileMap(tm)
		game.screenFade.Start(50, false)
	End

	Method Render:Void()
		Cls
		tilemap.RenderMap(offsetX, offsetY, SCREEN_WIDTH, SCREEN_HEIGHT)

		FPSCounter.Draw(0,0)
	End

	Method Update:Void()
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, True)
			game.nextScreen = game.exitScreen
		End
		If KeyDown(KEY_UP) Then offsetY -= 4
		If KeyDown(KEY_DOWN) Then offsetY += 4
		If KeyDown(KEY_LEFT) Then offsetX -= 4
		If KeyDown(KEY_RIGHT) Then offsetX += 4

		tilemap.UpdateAnimation(dt.frametime)
	End
End

Class MyTiledTileMapReader Extends TiledTileMapReader
	Method CreateMap:TileMap()
		Return New MyTileMap
	End
End

Class MyTileMap Extends TileMap
	Method ConfigureLayer:Void(tileLayer:TileMapLayer)
		SetAlpha(tileLayer.opacity)
	End

	Method DrawTile:Void(tileLayer:TileMapTileLayer, mapTile:TileMapTile, x:Int, y:Int)
		mapTile.image.DrawTile(x, y, mapTile.id, 0, 1, 1)
	End
End

Strict

Import mojo
Import diddy
Import diddy.tile

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

Global titleScreen:TitleScreen
Global gameScreen:GameScreen

Class MyGame Extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()
		titleScreen = New TitleScreen
		gameScreen = new GameScreen
		titleScreen.PreStart()
		Return 0
	End
End

Class TitleScreen Extends Screen
	Method New()
		name = "Title"
	End

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

	Method Render:Void()
		Cls
		DrawText "TITLE", SCREEN_WIDTH2, SCREEN_HEIGHT2, 0.5, 0.5
		FPSCounter.Draw(0,0)
	End

	Method Update:Void()
		if game.mouseHit
			game.screenFade.Start(50, True, True, True)
			game.nextScreen = gameScreen
		End
	End
End

Class GameScreen Extends Screen
	Field tilemap:MyTileMap
	Field offsetX:Int, offsetY:Int
	Field str$

	Method New()
		name = "Game"
	End

	Method Start:Void()
		Local reader:MyTiledTileMapReader = New MyTiledTileMapReader
		Local tm:TileMap = reader.LoadMap("maps/map.xml")
		tilemap = MyTileMap(tm)
		game.screenFade.Start(50, false)
	End

	Method Render:Void()
		Cls
		tilemap.RenderMap(offsetX, offsetY, SCREEN_WIDTH, SCREEN_HEIGHT)

		FPSCounter.Draw(0,0)
	End

	Method Update:Void()
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, True)
			game.nextScreen = game.exitScreen
		End
		If KeyDown(KEY_UP) Then offsetY -= 4
		If KeyDown(KEY_DOWN) Then offsetY += 4
		If KeyDown(KEY_LEFT) Then offsetX -= 4
		If KeyDown(KEY_RIGHT) Then offsetX += 4

		tilemap.UpdateAnimation(dt.frametime)
	End
End

Class MyTiledTileMapReader Extends TiledTileMapReader
	Method CreateMap:TileMap()
		Return New MyTileMap
	End
End

Class MyTileMap Extends TileMap
	Method ConfigureLayer:Void(tileLayer:TileMapLayer)
		SetAlpha(tileLayer.opacity)
	End

	Method DrawTile:Void(tileLayer:TileMapTileLayer, mapTile:TileMapTile, x:Int, y:Int)
		mapTile.image.DrawTile(x, y, mapTile.id, 0, 1, 1)
	End
End


Added "Mod" hack :/

Added "Mod" hack :/

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


add MathHelper.ModF() and ModI()

add MathHelper.ModF() and ModI()

Strict

Public

Class MathHelper Abstract
    Function Round:Int(given:Float)
        If given > 0.5 Then Return Floor(given)
        Return Ceil(given)
    End

    Function ModF:Float(left:Float, right:Float)
        Return left - right * Floor(left / right)
    End

    Function ModI:Int(left:Int, right:Int)
        Return left Mod right
    End
End

Strict

Public

Class MathHelper Abstract
    Function Round:Int(given:Float)
        If given > 0.5 Then Return Floor(given)
        Return Ceil(given)
    End
End