Allow to enable beam draw

Allow to enable beam draw

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

RENDER_HORN_GLOW = CreateConVar('ppm2_horn_glow', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn glow when player uses physgun')
HORN_PARTICLES = CreateConVar('ppm2_horn_particles', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn particles when player uses physgun')
HORN_FP = CreateConVar('ppm2_horn_firstperson', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn effetcs in first person')
HORN_HIDE_BEAM = CreateConVar('ppm2_horn_nobeam', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Hide physgun beam')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
DRAW_LEGS_DEPTH = CreateConVar('ppm2_render_legsdepth', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Render legs in depth pass. Useful with Boken DoF enabled')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')
SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_cl_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')
SV_SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_sv_draw_hands', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return if PPM2.__RENDERING_REFLECTIONS
    return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
    return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
    return unless IsValid(arms)
    return unless ply.__cachedIsPony
    return unless ply\Alive()
    arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
    wep = ply\GetActiveWeapon()
    if IsValid(wep) and wep.UseHands == false
        return true -- Dafuck?
    return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
    data = ply\GetPonyData()
    return unless data
    status = data\GetRenderController()\PreDrawArms(arms)
    return status if status ~= nil
    arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return if PPM2.__RENDERING_REFLECTIONS
    return unless IsValid(arms)
    return unless arms.__ppm2_draw
    data = ply\GetPonyData()
    return unless data
    data\GetRenderController()\PostDrawArms(arms)
    arms.__ppm2_draw = false

IN_DRAW = false

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    return if IN_DRAW
    return if PPM2.__RENDERING_REFLECTIONS
    if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegsDepth()
                    IN_DRAW = false

    return if bDrawingDepth or bDrawingSkybox

    if not LEGS_RENDER_TYPE\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegs()
                    IN_DRAW = false

Think = ->
    if TASK_RENDER_TYPE\GetBool()
        for task in *PPM2.NetworkedPonyData.RenderTasks
            ent = task.ent
            if IsValid(ent) and ent.__cachedIsPony
                if ent.__ppm2_task_hit
                    ent.__ppm2_task_hit = false
                    ent\SetNoDraw(false)

                if not ent.__ppm2RenderOverride
                    ent = ent\GetEntity()
                    ent.__ppm2_oldRenderOverride = ent.RenderOverride
                    ent.__ppm2RenderOverride = ->
                        renderController = task\GetRenderController()
                        renderController\PreDraw(ent, true)
                        ent\DrawModel()
                        renderController\PostDraw(ent, true)
                        ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
                    ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    return if IN_DRAW
    return if PPM2.__RENDERING_REFLECTIONS
    if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegsDepth()
                    IN_DRAW = false

    return if bDrawingDepth or bDrawingSkybox

    if not TASK_RENDER_TYPE\GetBool()
        for task in *PPM2.NetworkedPonyData.RenderTasks
            ent = task.ent
            if IsValid(ent)
                if ent.__cachedIsPony
                    ent\SetNoDraw(true)
                    ent.__ppm2_task_hit = true
                    renderController = task\GetRenderController()
                    renderController\PreDraw(ent)
                    IN_DRAW = true
                    ent\DrawModel()
                    IN_DRAW = false
                    renderController\PostDraw(ent)
                else
                    if ent.__ppm2_task_hit
                        ent.__ppm2_task_hit = false
                        ent\SetNoDraw(false)
                        task\Reset()

    if not ENABLE_NEW_RAGDOLLS\GetBool()
        for ply in *player.GetAll()
            alive = ply\Alive()
            ply.__ppm2_last_dead = RealTime() + 2 if not alive
            if ply.__cachedIsPony
                if ply\GetPonyData() and not alive
                    data = ply\GetPonyData()
                    rag = ply\GetRagdollEntity()
                    if IsValid(rag)
                        renderController = data\GetRenderController()
                        data\DoRagdollMerge()
                        if renderController
                            renderController\PreDraw(rag)
                            IN_DRAW = true
                            rag\DrawModel()
                            IN_DRAW = false
                            renderController\PostDraw(rag)

    if LEGS_RENDER_TYPE\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegs()
                    IN_DRAW = false

PPM2.PrePlayerDraw = =>
    return if PPM2.__RENDERING_REFLECTIONS
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController

PPM2.PostPlayerDraw = =>
    return if PPM2.__RENDERING_REFLECTIONS
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

do
    hornGlowStatus = {}
    smokeMaterial = 'ppm/hornsmoke'
    fireMat = 'particle/fire'
    hornShift = Vector(1, 0.15, 14.5)

    hook.Add 'Think', 'PPM2.HornEffects', =>
        frame = FrameNumber()
        for ent, status in pairs hornGlowStatus
            if not IsValid(ent)
                status.emmiter\Finish() if IsValid(status.emmiter)
                status.emmiterProp\Finish() if IsValid(status.emmiterProp)
                hornGlowStatus[ent] = nil
            elseif status.frame ~= frame
                status.data\SetHornGlow(status.prevStatus)
                status.emmiter\Finish() if IsValid(status.emmiter)
                status.emmiterProp\Finish() if IsValid(status.emmiterProp)
                hornGlowStatus[ent] = nil
            else
                if not status.prevStatus and RENDER_HORN_GLOW\GetBool() and status.data\GetHornGlow() ~= status.isEnabled
                    status.data\SetHornGlow(status.isEnabled)
                if status.attach and IsValid(status.target)
                    grabHornPos = Vector(hornShift) * status.data\GetPonySize()
                    {:Pos, :Ang} = ent\GetAttachment(status.attach)
                    grabHornPos\Rotate(Ang)
                    if status.isEnabled and IsValid(status.emmiter) and status.nextSmokeParticle < RealTime()
                        status.nextSmokeParticle = RealTime() + math.Rand(0.1, 0.5)
                        for i = 1, math.random(1, 4)
                            vec = VectorRand()
                            calcPos = Pos + grabHornPos + vec
                            with particle = status.emmiter\Add(smokeMaterial, calcPos)
                                \SetRollDelta(math.rad(math.random(0, 360)))
                                \SetPos(calcPos)
                                life = math.Rand(0.9, 3)
                                \SetStartAlpha(math.random(80, 170))
                                \SetDieTime(life)
                                \SetColor(status.color.r, status.color.g, status.color.b)
                                \SetEndAlpha(0)
                                size = math.Rand(2, 3)
                                \SetEndSize(math.Rand(2, size))
                                \SetStartSize(size)
                                \SetGravity(Vector())
                                \SetAirResistance(10)
                                vecRand = VectorRand()
                                vecRand.z *= 2
                                \SetVelocity(vecRand * status.data\GetPonySize() * 2)
                                \SetCollide(false)
                        for i = 1, math.random(2, 4)
                            vec = VectorRand() * 3
                            calcPos = Pos + grabHornPos + vec
                            with particle = status.emmiter\Add(fireMat, calcPos)
                                \SetRollDelta(math.rad(math.random(0, 360)))
                                \SetPos(calcPos)
                                life = math.Rand(0.9, 6)
                                \SetStartAlpha(math.random(80, 170))
                                \SetDieTime(life)
                                \SetColor(status.color2.r, status.color2.g, status.color2.b)
                                \SetEndAlpha(0)
                                \SetEndSize(0)
                                \SetStartSize(math.Rand(2, 3))
                                \SetGravity(Vector())
                                \SetAirResistance(0)
                                calcVel = calcPos - status.tpos
                                calcVel\Normalize()
                                calcVel *= calcPos\Distance(status.tpos) * .2
                                \SetVelocity(-calcVel)
                                \SetCollide(false)
                    if status.isEnabled and IsValid(status.emmiterProp) and status.nextGrabParticle < RealTime() and status.mins and status.maxs
                        status.nextGrabParticle = RealTime() + math.Rand(0.2, 0.9)
                        status.emmiterProp\SetPos(status.tpos)
                        for i = 1, math.random(5, 10)
                            calcPos = Vector(math.Rand(status.mins.x, status.maxs.x), math.Rand(status.mins.y, status.maxs.y), math.Rand(status.mins.z, status.maxs.z))
                            with particle = status.emmiterProp\Add(fireMat, calcPos)
                                \SetRollDelta(math.rad(math.random(0, 360)))
                                \SetPos(calcPos)
                                life = math.Rand(0.9, 6)
                                \SetStartAlpha(math.random(130, 230))
                                \SetDieTime(life)
                                \SetColor(status.bcolor.r, status.bcolor.g, status.bcolor.b)
                                \SetEndAlpha(0)
                                \SetEndSize(math.Rand(5, 15))
                                \SetStartSize(0)
                                \SetGravity(Vector(0, 0, -math.Rand(5, 15)))
                                \SetAirResistance(15)
                                \SetVelocity(VectorRand())
                                \SetCollide(false)

    hook.Add 'DrawPhysgunBeam', 'PPM2.HornEffects', (physgun = NULL, isEnabled = false, target = NULL, bone = 0, hitPos = Vector()) =>
        return if not HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
        data = @GetPonyData()
        return if not data
        return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
        if not hornGlowStatus[@]
            hornGlowStatus[@] = {
                frame: FrameNumber()
                prevStatus: data\GetHornGlow()
                :data, :isEnabled, :hitPos, :target, :bone
                tpos: @GetPos()
                attach: @LookupAttachment('eyes')
                nextSmokeParticle: 0
                nextGrabParticle: 0
            }

            if HORN_PARTICLES\GetBool()
                hornGlowStatus[@].emmiter = ParticleEmitter(EyePos())
                hornGlowStatus[@].emmiterProp = ParticleEmitter(EyePos())

            hornGlowStatus[@].color = data\GetBodyColor()
            hornGlowStatus[@].bcolor = hornGlowStatus[@].color
            hornGlowStatus[@].color2 = data\GetHornDetailColor()
            hornGlowStatus[@].color = data\GetHornColor() if data\GetSeparateHorn()
        else
            hornGlowStatus[@].frame = FrameNumber()
            hornGlowStatus[@].isEnabled = isEnabled
            hornGlowStatus[@].target = target
            hornGlowStatus[@].bone = bone
            hornGlowStatus[@].hitPos = hitPos
            if IsValid(target)
                hornGlowStatus[@].tpos = target\GetPos() + hitPos
                hornGlowStatus[@].mins, hornGlowStatus[@].maxs = target\WorldSpaceAABB()
        return not IsValid(target) if HORN_HIDE_BEAM\GetBool()

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, 2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, 2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, 2
hook.Add 'Think', 'PPM2.UpdateRenderTasks', Think, 2
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, 2

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

RENDER_HORN_GLOW = CreateConVar('ppm2_horn_glow', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn glow when player uses physgun')
HORN_PARTICLES = CreateConVar('ppm2_horn_particles', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn particles when player uses physgun')
HORN_FP = CreateConVar('ppm2_horn_firstperson', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn effetcs in first person')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
DRAW_LEGS_DEPTH = CreateConVar('ppm2_render_legsdepth', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Render legs in depth pass. Useful with Boken DoF enabled')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')
SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_cl_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')
SV_SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_sv_draw_hands', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return if PPM2.__RENDERING_REFLECTIONS
    return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
    return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
    return unless IsValid(arms)
    return unless ply.__cachedIsPony
    return unless ply\Alive()
    arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
    wep = ply\GetActiveWeapon()
    if IsValid(wep) and wep.UseHands == false
        return true -- Dafuck?
    return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
    data = ply\GetPonyData()
    return unless data
    status = data\GetRenderController()\PreDrawArms(arms)
    return status if status ~= nil
    arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return if PPM2.__RENDERING_REFLECTIONS
    return unless IsValid(arms)
    return unless arms.__ppm2_draw
    data = ply\GetPonyData()
    return unless data
    data\GetRenderController()\PostDrawArms(arms)
    arms.__ppm2_draw = false

IN_DRAW = false

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    return if IN_DRAW
    return if PPM2.__RENDERING_REFLECTIONS
    if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegsDepth()
                    IN_DRAW = false

    return if bDrawingDepth or bDrawingSkybox

    if not LEGS_RENDER_TYPE\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegs()
                    IN_DRAW = false

Think = ->
    if TASK_RENDER_TYPE\GetBool()
        for task in *PPM2.NetworkedPonyData.RenderTasks
            ent = task.ent
            if IsValid(ent) and ent.__cachedIsPony
                if ent.__ppm2_task_hit
                    ent.__ppm2_task_hit = false
                    ent\SetNoDraw(false)

                if not ent.__ppm2RenderOverride
                    ent = ent\GetEntity()
                    ent.__ppm2_oldRenderOverride = ent.RenderOverride
                    ent.__ppm2RenderOverride = ->
                        renderController = task\GetRenderController()
                        renderController\PreDraw(ent, true)
                        ent\DrawModel()
                        renderController\PostDraw(ent, true)
                        ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
                    ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    return if IN_DRAW
    return if PPM2.__RENDERING_REFLECTIONS
    if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegsDepth()
                    IN_DRAW = false

    return if bDrawingDepth or bDrawingSkybox

    if not TASK_RENDER_TYPE\GetBool()
        for task in *PPM2.NetworkedPonyData.RenderTasks
            ent = task.ent
            if IsValid(ent)
                if ent.__cachedIsPony
                    ent\SetNoDraw(true)
                    ent.__ppm2_task_hit = true
                    renderController = task\GetRenderController()
                    renderController\PreDraw(ent)
                    IN_DRAW = true
                    ent\DrawModel()
                    IN_DRAW = false
                    renderController\PostDraw(ent)
                else
                    if ent.__ppm2_task_hit
                        ent.__ppm2_task_hit = false
                        ent\SetNoDraw(false)
                        task\Reset()

    if not ENABLE_NEW_RAGDOLLS\GetBool()
        for ply in *player.GetAll()
            alive = ply\Alive()
            ply.__ppm2_last_dead = RealTime() + 2 if not alive
            if ply.__cachedIsPony
                if ply\GetPonyData() and not alive
                    data = ply\GetPonyData()
                    rag = ply\GetRagdollEntity()
                    if IsValid(rag)
                        renderController = data\GetRenderController()
                        data\DoRagdollMerge()
                        if renderController
                            renderController\PreDraw(rag)
                            IN_DRAW = true
                            rag\DrawModel()
                            IN_DRAW = false
                            renderController\PostDraw(rag)

    if LEGS_RENDER_TYPE\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    IN_DRAW = true
                    data\GetRenderController()\DrawLegs()
                    IN_DRAW = false

PPM2.PrePlayerDraw = =>
    return if PPM2.__RENDERING_REFLECTIONS
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController

PPM2.PostPlayerDraw = =>
    return if PPM2.__RENDERING_REFLECTIONS
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

do
    hornGlowStatus = {}
    smokeMaterial = 'ppm/hornsmoke'
    fireMat = 'particle/fire'
    hornShift = Vector(1, 0.15, 14.5)

    hook.Add 'Think', 'PPM2.HornEffects', =>
        frame = FrameNumber()
        for ent, status in pairs hornGlowStatus
            if not IsValid(ent)
                status.emmiter\Finish() if IsValid(status.emmiter)
                status.emmiterProp\Finish() if IsValid(status.emmiterProp)
                hornGlowStatus[ent] = nil
            elseif status.frame ~= frame
                status.data\SetHornGlow(status.prevStatus)
                status.emmiter\Finish() if IsValid(status.emmiter)
                status.emmiterProp\Finish() if IsValid(status.emmiterProp)
                hornGlowStatus[ent] = nil
            else
                if not status.prevStatus and RENDER_HORN_GLOW\GetBool() and status.data\GetHornGlow() ~= status.isEnabled
                    status.data\SetHornGlow(status.isEnabled)
                if status.attach and IsValid(status.target)
                    grabHornPos = Vector(hornShift) * status.data\GetPonySize()
                    {:Pos, :Ang} = ent\GetAttachment(status.attach)
                    grabHornPos\Rotate(Ang)
                    if status.isEnabled and IsValid(status.emmiter) and status.nextSmokeParticle < RealTime()
                        status.nextSmokeParticle = RealTime() + math.Rand(0.1, 0.5)
                        for i = 1, math.random(1, 4)
                            vec = VectorRand()
                            calcPos = Pos + grabHornPos + vec
                            with particle = status.emmiter\Add(smokeMaterial, calcPos)
                                \SetRollDelta(math.rad(math.random(0, 360)))
                                \SetPos(calcPos)
                                life = math.Rand(0.9, 3)
                                \SetStartAlpha(math.random(80, 170))
                                \SetDieTime(life)
                                \SetColor(status.color.r, status.color.g, status.color.b)
                                \SetEndAlpha(0)
                                size = math.Rand(2, 3)
                                \SetEndSize(math.Rand(2, size))
                                \SetStartSize(size)
                                \SetGravity(Vector())
                                \SetAirResistance(10)
                                vecRand = VectorRand()
                                vecRand.z *= 2
                                \SetVelocity(vecRand * status.data\GetPonySize() * 2)
                                \SetCollide(false)
                        for i = 1, math.random(2, 4)
                            vec = VectorRand() * 3
                            calcPos = Pos + grabHornPos + vec
                            with particle = status.emmiter\Add(fireMat, calcPos)
                                \SetRollDelta(math.rad(math.random(0, 360)))
                                \SetPos(calcPos)
                                life = math.Rand(0.9, 6)
                                \SetStartAlpha(math.random(80, 170))
                                \SetDieTime(life)
                                \SetColor(status.color2.r, status.color2.g, status.color2.b)
                                \SetEndAlpha(0)
                                \SetEndSize(0)
                                \SetStartSize(math.Rand(2, 3))
                                \SetGravity(Vector())
                                \SetAirResistance(0)
                                calcVel = calcPos - status.tpos
                                calcVel\Normalize()
                                calcVel *= calcPos\Distance(status.tpos) * .2
                                \SetVelocity(-calcVel)
                                \SetCollide(false)
                    if status.isEnabled and IsValid(status.emmiterProp) and status.nextGrabParticle < RealTime() and status.mins and status.maxs
                        status.nextGrabParticle = RealTime() + math.Rand(0.2, 0.9)
                        status.emmiterProp\SetPos(status.tpos)
                        for i = 1, math.random(5, 10)
                            calcPos = Vector(math.Rand(status.mins.x, status.maxs.x), math.Rand(status.mins.y, status.maxs.y), math.Rand(status.mins.z, status.maxs.z))
                            with particle = status.emmiterProp\Add(fireMat, calcPos)
                                \SetRollDelta(math.rad(math.random(0, 360)))
                                \SetPos(calcPos)
                                life = math.Rand(0.9, 6)
                                \SetStartAlpha(math.random(130, 230))
                                \SetDieTime(life)
                                \SetColor(status.bcolor.r, status.bcolor.g, status.bcolor.b)
                                \SetEndAlpha(0)
                                \SetEndSize(math.Rand(5, 15))
                                \SetStartSize(0)
                                \SetGravity(Vector(0, 0, -math.Rand(5, 15)))
                                \SetAirResistance(15)
                                \SetVelocity(VectorRand())
                                \SetCollide(false)

    hook.Add 'DrawPhysgunBeam', 'PPM2.HornEffects', (physgun = NULL, isEnabled = false, target = NULL, bone = 0, hitPos = Vector()) =>
        return if not HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
        data = @GetPonyData()
        return if not data
        return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
        if not hornGlowStatus[@]
            hornGlowStatus[@] = {
                frame: FrameNumber()
                prevStatus: data\GetHornGlow()
                :data, :isEnabled, :hitPos, :target, :bone
                tpos: @GetPos()
                attach: @LookupAttachment('eyes')
                nextSmokeParticle: 0
                nextGrabParticle: 0
            }

            if HORN_PARTICLES\GetBool()
                hornGlowStatus[@].emmiter = ParticleEmitter(EyePos())
                hornGlowStatus[@].emmiterProp = ParticleEmitter(EyePos())

            hornGlowStatus[@].color = data\GetBodyColor()
            hornGlowStatus[@].bcolor = hornGlowStatus[@].color
            hornGlowStatus[@].color2 = data\GetHornDetailColor()
            hornGlowStatus[@].color = data\GetHornColor() if data\GetSeparateHorn()
        else
            hornGlowStatus[@].frame = FrameNumber()
            hornGlowStatus[@].isEnabled = isEnabled
            hornGlowStatus[@].target = target
            hornGlowStatus[@].bone = bone
            hornGlowStatus[@].hitPos = hitPos
            if IsValid(target)
                hornGlowStatus[@].tpos = target\GetPos() + hitPos
                hornGlowStatus[@].mins, hornGlowStatus[@].maxs = target\WorldSpaceAABB()
        return not IsValid(target)

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, 2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, 2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, 2
hook.Add 'Think', 'PPM2.UpdateRenderTasks', Think, 2
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, 2


Example: move comment to proper location.

-- This example is unlicensed under CC0

export script_name        = "ASSInspector Example"
export script_description = "Calculates bounding rectangles on the start and end times of selected events."
export script_author      = "torque"
export script_version     = 0x000003

ffi  = require( 'ffi' )
util = require( 'aegisub.util' )
log  = require( 'a-mo.log' )

ffi.cdef( [[
typedef struct {
	int32_t x, y;
	uint32_t w, h;
} ASSI_Rect;

uint32_t    assi_getVersion( void );
const char* assi_getErrorString( void* );
void*       assi_init( int, int, const char*, uint32_t, const char*, const char* );
int         assi_setScript( void*, const char*, uint32_t, const char *, const uint32_t );
int         assi_calculateBounds( void*, ASSI_Rect*, const int32_t*, const uint32_t );
void        assi_cleanup( void* );
]] )

-- Figure out a better way to load this?
extension = ('OSX' == ffi.os and '.dylib' or 'Windows' == ffi.os and '.dll' or '.so')
libraryPath = "?user/automation/include/ASSInspector"
success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
if not success
	libraryPath = "?data/automation/include/ASSInspector"
	success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
	if not success
		error( "Could not load required libASSInspector library." )

-- Script-wide local variables are stored until the script is reloaded,
-- at which point they are garbage collected. We can abuse this system
-- to create a persistent ASSInspector state that only gets deallocated
-- when the automation script is reloaded or Aegisub is quit.

-- NOTE: this can be somewhat risky. For example, loading a new subtitle
-- file in Aegisub won't reset the automation scripts, and thus the old
-- header information will continue being used. It would be better to
-- make some uniqueness check on the loaded script. The full path to the
-- script would be an option, except that doesn't work for scripts that
-- have yet to be saved to disk. Appending some sort of UUID to the
-- script header and checking that each run would probably be more
-- foolproof. This implementation detail is left as an exercise to the
-- user.
inspector = nil
initializeInspector = ( resX, resY, minimalHeader ) ->
	if nil == inspector
		if '?script' != aegisub.decode_path( '?script' )
			-- Tell ASSInspector to tell fontconfig to search the fonts
			-- directory in the script directory for fonts to load.
			inspector = ffi.gc( ASSInspector.assi_init( resX, resY, minimalHeader, #minimalHeader, aegisub.decode_path( libraryPath .. "/fonts.conf" ), aegisub.decode_path( '?script/fonts' ) ), ASSInspector.assi_cleanup )
		else
			inspector = ffi.gc( ASSInspector.assi_init( resX, resY, minimalHeader, #minimalHeader, aegisub.decode_path( libraryPath .. "/fonts.conf" ), nil ), ASSInspector.assi_cleanup )
		if nil == inspector
			log.windowError( "ASSInspector library init failed." )

dumpRect = ( event, rect ) ->
	bounds = {
		x:  tonumber( rect.x )
		y:  tonumber( rect.y )
		x2: tonumber( rect.w + rect.x )
		y2: tonumber( rect.h + rect.y )
	}

	newEvent = util.deep_copy( event )
	with bounds
		newEvent.text = ([[{\an7\pos(0,0)\bord0\shad0\c&H0000FF&\1a&H00&\p1}m %d %d l %d %d %d %d %d %d]])\format( .x, .y, .x2, .y, .x2, .y2, .x, .y2 )

	event.layer += 1
	return newEvent

mainFunction = ( subtitle, selectedLines, activeLine ) ->
	if ASSInspector.assi_getVersion( ) < script_version
		log.windowError( "Installed ASSInspector library is outdated. Please update it." )
		return selectedLines
	elseif ASSInspector.assi_getVersion( ) > script_version
		log.windowError( "Installed OptimASS is outdated. Please update it." )
		return selectedLines

	scriptHeader = {
		"[Script Info]"
	}

	-- These are the only header fields that actually affect the way ASS
	-- is rendered. I don't actually know if ScriptType makes a matters.
	infoFields = {
		PlayResX:   true
		PlayResY:   true
		WrapStyle:  true
		ScriptType: true
		ScaledBorderAndShadow: true
	}

	styles = { }

	-- If there is no video (or, at minimum, timecodes) loaded, this will
	-- return nil.
	hasTimecodes = aegisub.frame_from_ms( 0 )

	-- Copy these functions into local variables to shorten them.
	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame

	subtitleLen = #subtitle
	seenStyles = false
	resX = 640
	resY = 480

	-- This loop assumes the subtitle script is nicely organized in the
	-- order [Script Info] -> [V4+ Styles] -> [Events]. This isn't
	-- guaranteed for scripts floating around in the wild, but fortunately
	-- Aegisub 3.2 seems to do a very good job of ensuring this order when
	-- a script is loaded. In other words, this loop is probably only safe
	-- to do from within automation.
	for index = 1, subtitleLen
		with line = subtitle[index]
			if "info" == .class
				if infoFields[.key]
					table.insert( scriptHeader, .raw )

				if "PlayResX" == .key
					resX = tonumber( .value )
				elseif "PlayResY" == .key
					resY = tonumber( .value )

			elseif "style" == .class
				unless seenStyles
					table.insert( scriptHeader, "[V4+ Styles]\n" )
					seenStyles = true

				styles[.name] = .raw

			elseif "dialogue" == .class
				break

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution. Not convinced this is a great idea because people may
	-- use smaller workraws or something like that. Well, hopefully they
	-- don't do that for typesetting.
	vidResX, vidResY = aegisub.video_size( )
	resX = vidResX or resX
	resY = vidResY or resY

	minimalHeader = table.concat( scriptHeader, '\n' )

	initializeInspector( resX, resY, minimalHeader )

	insertIndices = { }
	insertEvents  = { }

	for eventIndex in *selectedLines
		with event = subtitle[eventIndex]
			-- This does not account for \r[name] being in the line.
			styleText = (styles[.style] or "") .. "\n[Events]\n"
			eventText = .raw

			-- It occurs to me that perhaps having the user just pass in one
			-- string that contains both styles and events may be easier.
			if 0 < ASSInspector.assi_setScript( inspector, styleText, #styleText, eventText, #eventText )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				ASSInspector.assi_cleanup( inspector )
				aegisub.cancel( )

			-- We're going to render a line twice, once at its start and once
			-- at its end.
			renderCount = 2

			renderTimes = { }

			-- Cheat a bit when generating the times to render. This obviously
			-- will not scale with a renderCount > 2. Use 0-index arrays to
			-- match up with C indices in a later loop.
			if hasTimecodes
				-- Using the aegisub.frame_from_ms function pointers we copied
				-- earlier.
				startFrame = ffms( .start_time )
				endFrame   = ffms( .end_time ) - 1
				renderTimes[0] = math.floor( 0.5*( msff( startFrame ) + msff( startFrame + 1 ) ) )
				renderTimes[1] = math.floor( 0.5*( msff( endFrame ) + msff( endFrame + 1 ) ) )
			else
				renderTimes[0] = .start_time
				-- Trying to render exactly at end_time will result in a
				-- completely blank frame.
				renderTimes[1] = .end_time - 1

			-- Because we're rendering the line twice, we need two rects.
			rects = ffi.new( "ASSI_Rect[?]", renderCount )

			-- Allocate the array of times for rendering.
			times = ffi.new( "int32_t[?]", renderCount )

			-- Set up the render times. There's no real reason to do this in a
			-- loop if you know beforehand you're only going to render the
			-- line twice, but loops are more generic.
			for render = 0, renderCount - 1
				times[render] = renderTimes[render]

			-- Send the data in for rendering and bounds calculation.
			if 0 < ASSInspector.assi_calculateBounds( inspector, rects, times, renderCount )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				aegisub.cancel( )

			-- Check out the calculated bounding rects
			for render = 0, renderCount - 1
				if rects[render].w > 0 and rects[render].h > 0
					newEvent = dumpRect( event, rects[render] )
					table.insert( insertIndices, eventIndex )
					table.insert( insertEvents, newEvent)

			subtitle[eventIndex] = event

	for index = #insertIndices, 1, -1
		subtitle.insert( insertIndices[index], insertEvents[index] )

aegisub.register_macro( script_name, script_description, mainFunction )

-- This example is unlicensed under CC0

export script_name        = "ASSInspector Example"
export script_description = "Calculates bounding rectangles on the start and end times of selected events."
export script_author      = "torque"
export script_version     = 0x000003

ffi  = require( 'ffi' )
util = require( 'aegisub.util' )
log  = require( 'a-mo.log' )

ffi.cdef( [[
typedef struct {
	int32_t x, y;
	uint32_t w, h;
} ASSI_Rect;

uint32_t    assi_getVersion( void );
const char* assi_getErrorString( void* );
void*       assi_init( int, int, const char*, uint32_t, const char*, const char* );
int         assi_setScript( void*, const char*, uint32_t, const char *, const uint32_t );
int         assi_calculateBounds( void*, ASSI_Rect*, const int32_t*, const uint32_t );
void        assi_cleanup( void* );
]] )

-- Figure out a better way to load this?
extension = ('OSX' == ffi.os and '.dylib' or 'Windows' == ffi.os and '.dll' or '.so')
libraryPath = "?user/automation/include/ASSInspector"
success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
if not success
	libraryPath = "?data/automation/include/ASSInspector"
	success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
	if not success
		error( "Could not load required libASSInspector library." )

-- Script-wide local variables are stored until the script is reloaded,
-- at which point they are garbage collected. We can abuse this system
-- to create a persistent ASSInspector state that only gets deallocated
-- when the automation script is reloaded or Aegisub is quit.

-- NOTE: this can be somewhat risky. For example, loading a new subtitle
-- file in Aegisub won't reset the automation scripts, and thus the old
-- header information will continue being used. It would be better to
-- make some uniqueness check on the loaded script. The full path to the
-- script would be an option, except that doesn't work for scripts that
-- have yet to be saved to disk. Appending some sort of UUID to the
-- script header and checking that each run would probably be more
-- foolproof. This implementation detail is left as an exercise to the
-- user.
inspector = nil
initializeInspector = ( resX, resY, minimalHeader ) ->
	if nil == inspector
		if '?script' != aegisub.decode_path( '?script' )
			inspector = ffi.gc( ASSInspector.assi_init( resX, resY, minimalHeader, #minimalHeader, aegisub.decode_path( libraryPath .. "/fonts.conf" ), aegisub.decode_path( '?script/fonts' ) ), ASSInspector.assi_cleanup )
		else
			inspector = ffi.gc( ASSInspector.assi_init( resX, resY, minimalHeader, #minimalHeader, aegisub.decode_path( libraryPath .. "/fonts.conf" ), nil ), ASSInspector.assi_cleanup )
		if nil == inspector
			log.windowError( "ASSInspector library init failed." )

		-- Tell ASSInspector to tell fontconfig to search the fonts directory
		-- in the script directory for fonts to load.

dumpRect = ( event, rect ) ->
	bounds = {
		x:  tonumber( rect.x )
		y:  tonumber( rect.y )
		x2: tonumber( rect.w + rect.x )
		y2: tonumber( rect.h + rect.y )
	}

	newEvent = util.deep_copy( event )
	with bounds
		newEvent.text = ([[{\an7\pos(0,0)\bord0\shad0\c&H0000FF&\1a&H00&\p1}m %d %d l %d %d %d %d %d %d]])\format( .x, .y, .x2, .y, .x2, .y2, .x, .y2 )

	event.layer += 1
	return newEvent

mainFunction = ( subtitle, selectedLines, activeLine ) ->
	if ASSInspector.assi_getVersion( ) < script_version
		log.windowError( "Installed ASSInspector library is outdated. Please update it." )
		return selectedLines
	elseif ASSInspector.assi_getVersion( ) > script_version
		log.windowError( "Installed OptimASS is outdated. Please update it." )
		return selectedLines

	scriptHeader = {
		"[Script Info]"
	}

	-- These are the only header fields that actually affect the way ASS
	-- is rendered. I don't actually know if ScriptType makes a matters.
	infoFields = {
		PlayResX:   true
		PlayResY:   true
		WrapStyle:  true
		ScriptType: true
		ScaledBorderAndShadow: true
	}

	styles = { }

	-- If there is no video (or, at minimum, timecodes) loaded, this will
	-- return nil.
	hasTimecodes = aegisub.frame_from_ms( 0 )

	-- Copy these functions into local variables to shorten them.
	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame

	subtitleLen = #subtitle
	seenStyles = false
	resX = 640
	resY = 480

	-- This loop assumes the subtitle script is nicely organized in the
	-- order [Script Info] -> [V4+ Styles] -> [Events]. This isn't
	-- guaranteed for scripts floating around in the wild, but fortunately
	-- Aegisub 3.2 seems to do a very good job of ensuring this order when
	-- a script is loaded. In other words, this loop is probably only safe
	-- to do from within automation.
	for index = 1, subtitleLen
		with line = subtitle[index]
			if "info" == .class
				if infoFields[.key]
					table.insert( scriptHeader, .raw )

				if "PlayResX" == .key
					resX = tonumber( .value )
				elseif "PlayResY" == .key
					resY = tonumber( .value )

			elseif "style" == .class
				unless seenStyles
					table.insert( scriptHeader, "[V4+ Styles]\n" )
					seenStyles = true

				styles[.name] = .raw

			elseif "dialogue" == .class
				break

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution. Not convinced this is a great idea because people may
	-- use smaller workraws or something like that. Well, hopefully they
	-- don't do that for typesetting.
	vidResX, vidResY = aegisub.video_size( )
	resX = vidResX or resX
	resY = vidResY or resY

	minimalHeader = table.concat( scriptHeader, '\n' )

	initializeInspector( resX, resY, minimalHeader )

	insertIndices = { }
	insertEvents  = { }

	for eventIndex in *selectedLines
		with event = subtitle[eventIndex]
			-- This does not account for \r[name] being in the line.
			styleText = (styles[.style] or "") .. "\n[Events]\n"
			eventText = .raw

			-- It occurs to me that perhaps having the user just pass in one
			-- string that contains both styles and events may be easier.
			if 0 < ASSInspector.assi_setScript( inspector, styleText, #styleText, eventText, #eventText )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				ASSInspector.assi_cleanup( inspector )
				aegisub.cancel( )

			-- We're going to render a line twice, once at its start and once
			-- at its end.
			renderCount = 2

			renderTimes = { }

			-- Cheat a bit when generating the times to render. This obviously
			-- will not scale with a renderCount > 2. Use 0-index arrays to
			-- match up with C indices in a later loop.
			if hasTimecodes
				-- Using the aegisub.frame_from_ms function pointers we copied
				-- earlier.
				startFrame = ffms( .start_time )
				endFrame   = ffms( .end_time ) - 1
				renderTimes[0] = math.floor( 0.5*( msff( startFrame ) + msff( startFrame + 1 ) ) )
				renderTimes[1] = math.floor( 0.5*( msff( endFrame ) + msff( endFrame + 1 ) ) )
			else
				renderTimes[0] = .start_time
				-- Trying to render exactly at end_time will result in a
				-- completely blank frame.
				renderTimes[1] = .end_time - 1

			-- Because we're rendering the line twice, we need two rects.
			rects = ffi.new( "ASSI_Rect[?]", renderCount )

			-- Allocate the array of times for rendering.
			times = ffi.new( "int32_t[?]", renderCount )

			-- Set up the render times. There's no real reason to do this in a
			-- loop if you know beforehand you're only going to render the
			-- line twice, but loops are more generic.
			for render = 0, renderCount - 1
				times[render] = renderTimes[render]

			-- Send the data in for rendering and bounds calculation.
			if 0 < ASSInspector.assi_calculateBounds( inspector, rects, times, renderCount )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				aegisub.cancel( )

			-- Check out the calculated bounding rects
			for render = 0, renderCount - 1
				if rects[render].w > 0 and rects[render].h > 0
					newEvent = dumpRect( event, rects[render] )
					table.insert( insertIndices, eventIndex )
					table.insert( insertEvents, newEvent)

			subtitle[eventIndex] = event

	for index = #insertIndices, 1, -1
		subtitle.insert( insertIndices[index], insertEvents[index] )

aegisub.register_macro( script_name, script_description, mainFunction )


Initial implemenation for Engine new

Initial implemenation for Engine new

require "LovePunk.Entity"
require "LovePunk.Screen"
require "LovePunk.geometry.Rectangle"

export ^

---
-- The main class which manages the game loop
class Engine
	---
	-- Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10).
	@maxElapsed = 0.0333
	---
	-- The max amount of frames that can be skipped in fixed framerate mode.
	@maxFrameSkip = 0
	---
	-- If the game should stop updating/rendering.
	@paused = false
	---
	-- The amount of milliseconds between ticks in fixed framerate mode.
	@tickRate = 4

	---
	-- Constructor. Defines startup information about your game.
	-- @param width      The width of your game.
	-- @param height     The height of your game.
	-- @param frameRate  The game framerate, in frames per second.
	-- @param fixed      If a fixed-framerate should be used.
	-- @param renderMode Overrides the default render mode for this target
	new: (width = 0, height = 0, frameRate = 0, fixed = false, renderMode) =>
		LP.bounds = Rectangle(0, 0, width, height)
		LP.assignedFrameRate = frameRate
		LP.fixed = fixed

		LP.engine = @
		LP.screen = Screen!
		LP.width = width
		LP.height = height

		print LP.randomizeSeed
		LP.randomizeSeed! unless LP.randomSeed

		LP.entity = Entity!
		--LP.time = Lib.getTimer!

		@paused = false
		@maxElapsed = 0.0333
		@tickRate = 4

		-- Do something with the Löve functions...

	focusGained: =>
		return

	focusLost: =>
		return

	init: =>
		return

	render: =>
		return

	update: =>
		return

export ^

class Engine
	-- Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10).
	@maxElapsed = 30.0
	-- The max amount of frames that can be skipped in fixed framerate mode.
	@maxFrameSkip = 0
	-- If the game should stop updating/rendering.
	@paused = false
	-- The amount of milliseconds between ticks in fixed framerate mode.
	@tickRate = 0

	new: (width, height, frameRate, fixed, renderMode) =>

	focusGained: =>
		return

	focusLost: =>
		return

	init: =>
		return

	render: =>
		return

	update: =>
		return


Some fixes to Entity\collide and calling functions

require "LunoPunk.graphics.Graphiclist"
require "LunoPunk.LP"
require "LunoPunk.Mask"
require "LunoPunk.Tweener"
require "LunoPunk.utils.moonscript"
require "LunoPunk.utils.Math"

export ^

class Entity extends Tweener
	-- Constructor. Can be usd to place the Entity and assign a graphic and mask.
	-- @param	x			X position to place the Entity.
	-- @param	y			Y position to place the Entity.
	-- @param	graphic		Graphic to assign to the Entity.
	-- @param	mask		Mask to assign to the Entity.
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 0
		-- The collision type, used for collision checking.
		@__typeNext = nil
		@__typePrev = nil
		@__type = ""
		@name = ""

		@__point = LP.point
		@__camera = LP.point2

		-- Collision information.
		@__hitbox = Mask!
		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@__mask = mask if mask != nil
		@__moveX, @__moveY = 0, 0
		@__collide_x, @__collide_y = 0, 0

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@__layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@__graphic = graphic if graphic != nil

		@__hitbox\assignTo @

	-- X position of the Entity in the Scene.
	x: (value) =>
		if value != nil
			@__x = value
			return @__x

		if @followCamera
			@__x + LP.camera.x
		else
			@__x

	-- Y position of the Entity in the Scene.
	y: (value) =>
		if value != nil
			@__y = value
			return @__y
		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- Some helper accessors

	-- If the Entity collides with the camera rectangle.
	onCamera: =>
		return false if @__scene == nil
		@collideRect @x!, @y!, @__scene.camera.x, @__scene.camera.y, LP.width, LP.height

	-- Half the Entity's width.
	halfWidth: => @width/2
	-- Half the Entity's height.
	halfHeight: => @height/2
	-- The center x position of the Entity's hitbox.
	centerX: => @x! - @originX + @halfWidth!
	-- The center y position of the Entity's hitbox.
	centerY: => @y! - @originY + @halfHeight!
	-- The leftmost position of the Entity's hitbox.
	left: => @x! - @originX
	-- The rightmost position of the Entity's hitbox.
	right: => @x! - @originX + @width
	-- The topmost position of the Entity's hitbox.
	top: => @y! - @originY
	-- The bottommost position of the Entity's hitbox.
	bottom: => @y! - @originY + @height

	layer: (value) =>
		return @__layer if value == nil

		@__graphic.layer = value if @__graphic != nil

		if @__scene == nil
			@__layer = value
			return @__layer

		@__scene\removeRender @
		@__layer = if value then value else nil
		@__scene\addRender @
		@__layer

	type: (value) =>
		return @__type if value == nil

		if @__scene == nil
			@__type = value
			return @__type

		@__scene\removeType(@) if @__type != ""
		@__type = value
		@__scene\addType(@) if @__type != ""

		@__type

	mask: (value) =>
		return @__mask if value == nil or @__mask == value

		@__mask\assignTo(nil) if @__mask != nil
		@__mask = if value then value else nil
		@__mask\assignTo(@) if value

		@__mask

	graphic: (value) =>
		return @__graphic if value == nil or @__graphic == value

		value.layer = @__layer if value
		@__graphic = if value then value else nil
		@__graphic

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value if value != nil
		@__scene

	-- Override this, called when the Entity is added to a Scene.
	added: =>
	-- Override this, called when the Entity is removed from a Scene.
	removed: =>
	-- Updates the Entity.
	update: => super!

	-- Renders the Entity. If you override this for special behaviour,
	-- remember to call super.render() to render the Entity's graphic.
	render: =>
		if @__graphic != nil and @__graphic.visible
			if @__graphic.relative
				@__point.x = @x!
				@__point.y = @y!
			else
				@__point.x, @__point.y = 0, 0
				@__camera.x = if @__scene == nil then LP.camera.x else @__scene.camera.x
				@__camera.y = if @__scene == nil then LP.camera.y else @__scene.camera.y

			@__graphic\render @__point, @__camera

	-- Checks for a collision against an Entity type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil

		return nil if not @collidable or @__scene.__typeFirst == nil
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene\typeFirst!
		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e\x! - e.originX
					c3 = y - @originY + @height > e\y! - e.originY
					c4 = x - @originX < e\x! - e.originX + e.width
					c5 = y - @originY < e\y! - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__colide_y

			return nil

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				if e.__mask\collide (if e.__mask != nil then e.__mask else e.HITBOX)
					@x @__collide_x
					@y @__collide_y
					return e

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y

		return nil

	-- Checks for collision against multiple Entity types.
	-- @param	types		An Array or Vector of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collideTypes: (types, x, y) =>
		return nil if @__scene == nil

		if type(types) == "string"
			return @collide types, x, y
		else
			if types != nil
				for t in *types
					e = @collide t, x, y
					return e if e != nil

		return nil

	-- Checks if this Entity collides with a specific Entity.
	-- @param	e		The Entity to collide against.
	-- @param	x		Virtual x position to place this Entity.
	-- @param	y		Virtual y position to place this Entity.
	-- @return	The Entity if they overlap, or null if they don't.
	collideWith: (e, x, y) =>
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		c = do
			c1 = @collidable and e.collidable
			c2 = x - @originX + @width > e.x - e.originX
			c3 = y - @originY + @height > e.y - e.originY
			c4 = x - @originX < e.x - e.originX + e.width
			c5 = y - @originY < e.y - e.originY + e.height
			c1 and c2 and c3 and c4 and c5
		if c
			if @__mask == nil
				if e.__mask == nil or e.__mask\collide @__hitbox
					@x @__collide_x
					@y @__collide_y
					return e
				@x @__collide_x
				@y @__collide_y
				return nil

			if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)
				@x @__collide_x
				@y @__collide_y
				return e

		@x @__collide_x
		@y @__collide_y
		return nil

	-- Checks if this Entity overlaps the specified rectangle.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @return	If they overlap.
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
		c = do
			c1 = x - @originX + @width >= rX
			c2 = y - @originY + @height >= rY
			c3 = x - @originX <= rX + rWidth
			c4 = y - @originY <= rY + rHeight
			c1 and c2 and c3 and c4
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x = rX
			LP.entity.y = rY
			LP.entity.width = rWidth
			LP.entity.height = rHeight
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Checks if this Entity overlaps the specified position.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	If the Entity intersects with the position.
	collidePoint: (x, y, pX, pY) =>
		c = do
			c1 = pX >= x - @originX
			c2 = pY >= y - @originY
			c3 = pX < x - @originX + @width
			c4 = pY < y - @originY + height
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x, LP.entity.y = pX, pY
			LP.entity.width, LP.entity.height = 1, 1
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Populates an array with all collided Entities of a type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideInto: (type, x, y, array) =>
		return if @__scene == nil

		e = @__scene.__typeFirst\get type
		return if not @collidable or e == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					array[#array+1] = e if e__mask == nil or e.__mask\collide @__hitbox

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y
			return

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				array[#array+1] = e if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.
	-- @param	types		An array of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideTypesInto: (types, x, y, array) =>
		return if @__scene == nil
		for t in *types
			@collideInto t, x, y, array

	-- Adds the graphic to the Entity via a Graphiclist.
	-- @param	g		Graphic to add.
	addGraphic: (g) =>
		g.layer = @__layer
		if @__graphic == nil
			@graphic g
		else if moon_type(g) == Graphiclist
			@graphic!\add g
		else
			list = Graphiclist!
			list\add graphic if @graphic! != nil
			list\add g
			@graphic list
			list\setEntity @

		g

	-- Sets the Entity's hitbox properties.
	-- @param	width		Width of the hitbox.
	-- @param	height		Height of the hitbox.
	-- @param	originX		X origin of the hitbox.
	-- @param	originY		Y origin of the hitbox.
	setHitbox: (width, height, originX, originY) =>
		@width, @height = width, height
		@originX, @originY = originX, originY

	-- Sets the Entity's hitbox to match that of the provided object.
	-- @param	o		The object defining the hitbox (eg. an Image or Rectangle).
	setHitboxTo: (o) =>
		@width, @height = o.width, o.height

		if o.originX and o.originY
			@originX, @originY = o.originX, o.originY
		else
			@originX, @originY = o.x, o.y
			@originX, @originY = -@originX, -@originY

	-- Sets the origin of the Entity.
	-- @param	x		X origin.
	-- @param	y		Y origin.
	setOrigin: (x, y) => @originX, @originY = x, y

	-- Center's the Entity's origin (half width & height).
	centerOrigin: => @originX, @originY = @halfWidth!, @halfHeight!

	-- Calculates the distance from another Entity.
	-- @param	e				The other Entity.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceFrom: (e, useHitboxes) =>
		return math.sqrt (@x! - e.x) * (@x! - e.x) + (@y! - e.y) * (@y - e.y) if not useHitboxes
		LP.distanceRects @x! - @originX, @y! - @originY, @width, @height, e.x - e.originX, e.y - e.originY, e.width, e.height

	-- Calculates the distance from this Entity to the point.
	-- @param	px				X position.
	-- @param	py				Y position.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceToPoint: (px, py, useHitbox) =>
		return math.sqrt (@x! - px) * (@x! - px) + (@y! - py) * (@y! - py) if not useHitbox
		LP.distanceRectPoint px, py, @x! - @originX, @y! - @originY, @width, @height

	-- Calculates the distance from this Entity to the rectangle.
	-- @param	rx			X position of the rectangle.
	-- @param	ry			Y position of the rectangle.
	-- @param	rwidth		Width of the rectangle.
	-- @param	rheight		Height of the rectangle.
	-- @return	The distance.
	distanceToRect: (rx, ry, rwidth, rheight) =>
		LP.distanceRects rx, ry, rwidth, rheight, @x! - @originX, @y! - @originY, @width, @height

	-- Gets the class name as a string.
	-- @return	A string representing the class name.
	toString: => @@.__name

	__tostring: => @toString!

	-- Moves the Entity by the amount, retaining integer values for its x and y.
	-- @param	x			Horizontal offset.
	-- @param	y			Vertical offset.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveBy: (x, y, solidType, sweep) =>
		@__moveX += x
		@__moveY += y
		x, y = Math.round(@__moveX), Math.round(@__moveY)
		@__moveX -= x
		@__moveY -= y

		if solidType != nil
			if x != 0
				if @collidable and (sweep or @collideTypes(solidType, @x! + x, @y!) != nil)
					sign = if x > 0 then 1 else -1
					while x != 0
						e = @collideTypes solidType, @x! + sign, @y!
						if e != nil
							break if @moveCollideX e
							@x @x! + sign
						else
							@x @x! + sign

						x -= sign
				else
					@x @x! + x

			if y != 0
				if @collidable and (sweep or @collideTypes(solidType, @x!, @y! + y) != nil)
					sign = if y > 0 then 1 else -1
					while y != 0
						e = @collideTypes solidType, @x!, @y! + sign
						if e != nil
							break if @moveCollideY e
							@y @y! + sign
						else
							@y @y! + sign

						y -= sign
		else
			@x @x! + x
			@y @y! + y

	-- Moves the Entity to the position, retaining integer values for its x and y.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTo: (x, y, solidType, sweep) => moveBy x - @x!, y - @y!, solidType, sweep

	-- Moves towards the target position, retaining integer values for its x and y.
	-- @param	x			X target.
	-- @param	y			Y target.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTowards: (x, y, amount, solidType, sweep) =>
		@__point.x, @__point.y = x - @x!, y - @y!
		@__point.normalize amount if @__point.x * @__point.x + @__point.y * @__point.y > amount * amount
		@moveBy @__point.x, @__point.y, solidType, sweep

	-- Moves at an angle by a certain amount, retaining integer values for its x and y.
	-- @param	angle		Angle to move at in degrees.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveAtAngle: (angle, amount, solidType, sweep) =>
		angle *= LP.RAD!
		@moveBy math.cos(angle) * amount, math.sin(angle) * amount, solidType, sweep

	-- When you collide with an Entity on the x-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideX: (e) => true

	-- When you collide with an Entity on the y-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideY: (e) => true

	-- Clamps the Entity's hitbox on the x-axis.
	-- @param	left		Left bounds.
	-- @param	right		Right bounds.
	-- @param	padding		Optional padding on the clamp.
	clampHorizontal: (left, right, padding) =>
		@x left + @originX + padding if @x! - @originX < left + padding
		@x right - @width + @originX - padding if @x! - @originX + @width > right - padding

	-- Clamps the Entity's hitbox on the y axis.
	-- @param	top			Min bounds.
	-- @param	bottom		Max bounds.
	-- @param	padding		Optional padding on the clamp.
	clampVertical: (top, bottom, padding) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originX - padding if @y! - @originY + @height > bottom - padding

	-- Center graphic inside bounding rect.
	centerGraphicInRect: =>
		@__graphic.x, @__graphic.y = @halfWidth!, @halfHeight! if @__graphic != nil

require "LunoPunk.graphics.Graphiclist"
require "LunoPunk.LP"
require "LunoPunk.Mask"
require "LunoPunk.Tweener"
require "LunoPunk.utils.moonscript"
require "LunoPunk.utils.Math"

export ^

class Entity extends Tweener
	-- Constructor. Can be usd to place the Entity and assign a graphic and mask.
	-- @param	x			X position to place the Entity.
	-- @param	y			Y position to place the Entity.
	-- @param	graphic		Graphic to assign to the Entity.
	-- @param	mask		Mask to assign to the Entity.
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 0
		-- The collision type, used for collision checking.
		@__typeNext = nil
		@__typePrev = nil
		@__type = ""
		@name = ""

		@__point = LP.point
		@__camera = LP.point2

		-- Collision information.
		@__hitbox = Mask!
		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@__mask = mask if mask != nil
		@__moveX, @__moveY = 0, 0
		@__collide_x, @__collide_y = 0, 0

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@__layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@__graphic = graphic if graphic != nil

		@__hitbox\assignTo @

	-- X position of the Entity in the Scene.
	x: (value) =>
		if value != nil
			@__x = value
			return @__x

		if @followCamera
			@__x + LP.camera.x
		else
			@__x

	-- Y position of the Entity in the Scene.
	y: (value) =>
		if value != nil
			@__y = value
			return @__y
		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- Some helper accessors

	-- If the Entity collides with the camera rectangle.
	onCamera: =>
		return false if @__scene == nil
		@collideRect @x!, @y!, @__scene.camera.x, @__scene.camera.y, LP.width, LP.height

	-- Half the Entity's width.
	halfWidth: => @width/2
	-- Half the Entity's height.
	halfHeight: => @height/2
	-- The center x position of the Entity's hitbox.
	centerX: => @x! - @originX + @halfWidth!
	-- The center y position of the Entity's hitbox.
	centerY: => @y! - @originY + @halfHeight!
	-- The leftmost position of the Entity's hitbox.
	left: => @x! - @originX
	-- The rightmost position of the Entity's hitbox.
	right: => @x! - @originX + @width
	-- The topmost position of the Entity's hitbox.
	top: => @y! - @originY
	-- The bottommost position of the Entity's hitbox.
	bottom: => @y! - @originY + @height

	layer: (value) =>
		return @__layer if value == nil

		@__graphic.layer = value if @__graphic != nil

		if @__scene == nil
			@__layer = value
			return @__layer

		@__scene\removeRender @
		@__layer = if value then value else nil
		@__scene\addRender @
		@__layer

	type: (value) =>
		return @__type if value == nil

		if @__scene == nil
			@__type = value
			return @__type

		@__scene\removeType(@) if @__type != ""
		@__type = value
		@__scene\addType(@) if @__type != ""

		@__type

	mask: (value) =>
		return @__mask if value == nil or @__mask == value

		@__mask\assignTo(nil) if @__mask != nil
		@__mask = if value then value else nil
		@__mask\assignTo(@) if value

		@__mask

	graphic: (value) =>
		return @__graphic if value == nil or @__graphic == value

		value.layer = @__layer if value
		@__graphic = if value then value else nil
		@__graphic

	-- The World object is deprecated for FlashPunk-like compatibility
	-- TODO give a massage about this kept for porting compatibility
	world: (value) =>
		@__scene = value if value != nil
		@__scene

	-- Override this, called when the Entity is added to a Scene.
	added: =>
	-- Override this, called when the Entity is removed from a Scene.
	removed: =>
	-- Updates the Entity.
	update: => super!

	-- Renders the Entity. If you override this for special behaviour,
	-- remember to call super.render() to render the Entity's graphic.
	render: =>
		if @__graphic != nil and @__graphic.visible
			if @__graphic.relative
				@__point.x = @x!
				@__point.y = @y!
			else
				@__point.x, @__point.y = 0, 0
				@__camera.x = if @__scene == nil then LP.camera.x else @__scene.camera.x
				@__camera.y = if @__scene == nil then LP.camera.y else @__scene.camera.y

			@__graphic\render @__point, @__camera

	-- Checks for a collision against an Entity type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collide: (type, x, y) =>
		return nil if @__scene == nil

		return nil if not @collidable or @__scene.__typeFirst == nil
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y
		e = @__scene.typeFirst
		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					if e.__mask == nil or e.__mask\collide @HITBOX
						@x @__collide_x
						@y @__collide_y
						return e

				e = e.__typeNext

			@x @__collide_x
			@y @__colide_y

			return nil

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				if e.__mask\collide (if e.__mask != nil then e.__mask else e.HITBOX)
					@x @__collide_x
					@y @__collide_y
					return e

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y

		return nil

	-- Checks for collision against multiple Entity types.
	-- @param	types		An Array or Vector of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @return	The first Entity collided with, or null if none were collided.
	collideTypes: (types, x, y) =>
		return nil if @__scene == nil

		if type(types) == "string"
			return @collide types, x, y
		else
			if types != nil
				for t in *types
					e = @collide t, x, y
					return e if e != nil

		return nil

	-- Checks if this Entity collides with a specific Entity.
	-- @param	e		The Entity to collide against.
	-- @param	x		Virtual x position to place this Entity.
	-- @param	y		Virtual y position to place this Entity.
	-- @return	The Entity if they overlap, or null if they don't.
	collideWith: (e, x, y) =>
		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		c = do
			c1 = @collidable and e.collidable
			c2 = x - @originX + @width > e.x - e.originX
			c3 = y - @originY + @height > e.y - e.originY
			c4 = x - @originX < e.x - e.originX + e.width
			c5 = y - @originY < e.y - e.originY + e.height
			c1 and c2 and c3 and c4 and c5
		if c
			if @__mask == nil
				if e.__mask == nil or e.__mask\collide @__hitbox
					@x @__collide_x
					@y @__collide_y
					return e
				@x @__collide_x
				@y @__collide_y
				return nil

			if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)
				@x @__collide_x
				@y @__collide_y
				return e

		@x @__collide_x
		@y @__collide_y
		return nil

	-- Checks if this Entity overlaps the specified rectangle.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	rX			X position of the rectangle.
	-- @param	rY			Y position of the rectangle.
	-- @param	rWidth		Width of the rectangle.
	-- @param	rHeight		Height of the rectangle.
	-- @return	If they overlap.
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
		c = do
			c1 = x - @originX + @width >= rX
			c2 = y - @originY + @height >= rY
			c3 = x - @originX <= rX + rWidth
			c4 = y - @originY <= rY + rHeight
			c1 and c2 and c3 and c4
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x = rX
			LP.entity.y = rY
			LP.entity.width = rWidth
			LP.entity.height = rHeight
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Checks if this Entity overlaps the specified position.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	pX			X position.
	-- @param	pY			Y position.
	-- @return	If the Entity intersects with the position.
	collidePoint: (x, y, pX, pY) =>
		c = do
			c1 = pX >= x - @originX
			c2 = pY >= y - @originY
			c3 = pX < x - @originX + @width
			c4 = pY < y - @originY + height
		if c
			return true if @__mask == nil
			@__collide_x, @__collide_y = @x!, @y!
			@x x
			@y y
			LP.entity.x, LP.entity.y = pX, pY
			LP.entity.width, LP.entity.height = 1, 1
			if @__mask\collide LP.entity.__hitbox
				@x @__collide_x
				@y @__collide_y
				return true

			@x @__collide_x
			@y @__collide_y
			return false

		return false

	-- Populates an array with all collided Entities of a type.
	-- @param	type		The Entity type to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideInto: (type, x, y, array) =>
		return if @__scene == nil

		e = @__scene.__typeFirst\get type
		return if not @collidable or e == nil

		@__collide_x, @__collide_y = @x!, @y!
		@x x
		@y y

		if @__mask == nil
			while e != nil
				c = do
					c1 = e.collidable and e != @
					c2 = x - @originX + @width > e.x - e.originX
					c3 = y - @originY + @height > e.y - e.originY
					c4 = x - @originX < e.x - e.originX + e.width
					c5 = y - @originY < e.y - e.originY + e.height
					c1 and c2 and c3 and c4 and c5
				if c
					array[#array+1] = e if e__mask == nil or e.__mask\collide @__hitbox

				e = e.__typeNext

			@x @__collide_x
			@y @__collide_y
			return

		while e != nil
			c = do
				c1 = e.collidable and e != @
				c2 = x - @originX + @width > e.x - e.originX
				c3 = y - @originY + @height > e.y - e.originY
				c4 = x - @originX < e.x - e.originX + e.width
				c5 = y - @originY < e.y - e.originY + e.height
				c1 and c2 and c3 and c4 and c5
			if c
				array[#array+1] = e if (if @__mask\collide(e.__mask) != nil then e.__mask else e.__hitbox)

			e = e.__typeNext

		@x @__collide_x
		@y @__collide_y
		return

	-- Populates an array with all collided Entities of multiple types.
	-- @param	types		An array of Entity types to check for.
	-- @param	x			Virtual x position to place this Entity.
	-- @param	y			Virtual y position to place this Entity.
	-- @param	array		The Array or Vector object to populate.
	-- @return	The array, populated with all collided Entities.
	collideTypesInto: (types, x, y, array) =>
		return if @__scene == nil
		for t in *types
			@collideInto t, x, y, array

	-- Adds the graphic to the Entity via a Graphiclist.
	-- @param	g		Graphic to add.
	addGraphic: (g) =>
		g.layer = @__layer
		if @__graphic == nil
			@graphic g
		else if moon_type(g) == Graphiclist
			@graphic!\add g
		else
			list = Graphiclist!
			list\add graphic if @graphic! != nil
			list\add g
			@graphic list
			list\setEntity @

		g

	-- Sets the Entity's hitbox properties.
	-- @param	width		Width of the hitbox.
	-- @param	height		Height of the hitbox.
	-- @param	originX		X origin of the hitbox.
	-- @param	originY		Y origin of the hitbox.
	setHitbox: (width, height, originX, originY) =>
		@width, @height = width, height
		@originX, @originY = originX, originY

	-- Sets the Entity's hitbox to match that of the provided object.
	-- @param	o		The object defining the hitbox (eg. an Image or Rectangle).
	setHitboxTo: (o) =>
		@width, @height = o.width, o.height

		if o.originX and o.originY
			@originX, @originY = o.originX, o.originY
		else
			@originX, @originY = o.x, o.y
			@originX, @originY = -@originX, -@originY

	-- Sets the origin of the Entity.
	-- @param	x		X origin.
	-- @param	y		Y origin.
	setOrigin: (x, y) => @originX, @originY = x, y

	-- Center's the Entity's origin (half width & height).
	centerOrigin: => @originX, @originY = @halfWidth!, @halfHeight!

	-- Calculates the distance from another Entity.
	-- @param	e				The other Entity.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceFrom: (e, useHitboxes) =>
		return math.sqrt (@x! - e.x) * (@x! - e.x) + (@y! - e.y) * (@y - e.y) if not useHitboxes
		LP.distanceRects @x! - @originX, @y! - @originY, @width, @height, e.x - e.originX, e.y - e.originY, e.width, e.height

	-- Calculates the distance from this Entity to the point.
	-- @param	px				X position.
	-- @param	py				Y position.
	-- @param	useHitboxes		If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used.
	-- @return	The distance.
	distanceToPoint: (px, py, useHitbox) =>
		return math.sqrt (@x! - px) * (@x! - px) + (@y! - py) * (@y! - py) if not useHitbox
		LP.distanceRectPoint px, py, @x! - @originX, @y! - @originY, @width, @height

	-- Calculates the distance from this Entity to the rectangle.
	-- @param	rx			X position of the rectangle.
	-- @param	ry			Y position of the rectangle.
	-- @param	rwidth		Width of the rectangle.
	-- @param	rheight		Height of the rectangle.
	-- @return	The distance.
	distanceToRect: (rx, ry, rwidth, rheight) =>
		LP.distanceRects rx, ry, rwidth, rheight, @x! - @originX, @y! - @originY, @width, @height

	-- Gets the class name as a string.
	-- @return	A string representing the class name.
	toString: => @@.__name

	__tostring: => @toString!

	-- Moves the Entity by the amount, retaining integer values for its x and y.
	-- @param	x			Horizontal offset.
	-- @param	y			Vertical offset.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveBy: (x, y, solidType, sweep) =>
		@__moveX += x
		@__moveY += y
		x, y = Math.round(@__moveX), Math.round(@__moveY)
		@__moveX -= x
		@__moveY -= y

		if solidType != nil
			if x != 0
				if @collidable and (sweep or @collideTypes(solidType, @x! + x, @y!) != nil)
					sign = if x > 0 then 1 else -1
					while x != 0
						e = @collideTypes solidType, @x! + sign, @y!
						if e != nil
							break if @moveCollideX e
							@x @x! + sign
						else
							@x @x! + sign

						x -= sign
				else
					@x @x! + x

			if y != 0
				if @collidable and (sweep or @collideTypes(solidType, @x!, @y! + y) != nil)
					sign = if y > 0 then 1 else -1
					while y != 0
						e = @collideTypes solidType, @x!, @y! + sign
						if e != nil
							break if @moveCollideY e
							@y @y! + sign
						else
							@y @y! + sign

						y -= sign
		else
			@x @x! + x
			@y @y! + y

	-- Moves the Entity to the position, retaining integer values for its x and y.
	-- @param	x			X position.
	-- @param	y			Y position.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTo: (x, y, solidType, sweep) => moveBy x - @x!, y - @y!, solidType, sweep

	-- Moves towards the target position, retaining integer values for its x and y.
	-- @param	x			X target.
	-- @param	y			Y target.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveTowards: (x, y, amount, solidType, sweep) =>
		@__point.x, @__point.y = x - @x!, y - @y!
		@__point.normalize amount if @__point.x * @__point.x + @__point.y * @__point.y > amount * amount
		@moveBy @__point.x, @__point.y, solidType, sweep

	-- Moves at an angle by a certain amount, retaining integer values for its x and y.
	-- @param	angle		Angle to move at in degrees.
	-- @param	amount		Amount to move.
	-- @param	solidType	An optional collision type to stop flush against upon collision.
	-- @param	sweep		If sweeping should be used (prevents fast-moving objects from going through solidType).
	moveAtAngle: (angle, amount, solidType, sweep) =>
		angle *= LP.RAD!
		@moveBy math.cos(angle) * amount, math.sin(angle) * amount, solidType, sweep

	-- When you collide with an Entity on the x-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideX: (e) => true

	-- When you collide with an Entity on the y-axis with moveTo() or moveBy().
	-- @param	e		The Entity you collided with.
	moveCollideY: (e) => true

	-- Clamps the Entity's hitbox on the x-axis.
	-- @param	left		Left bounds.
	-- @param	right		Right bounds.
	-- @param	padding		Optional padding on the clamp.
	clampHorizontal: (left, right, padding) =>
		@x left + @originX + padding if @x! - @originX < left + padding
		@x right - @width + @originX - padding if @x! - @originX + @width > right - padding

	-- Clamps the Entity's hitbox on the y axis.
	-- @param	top			Min bounds.
	-- @param	bottom		Max bounds.
	-- @param	padding		Optional padding on the clamp.
	clampVertical: (top, bottom, padding) =>
		@y top + @originY + padding if @y! - @originY < top + padding
		@y bottom - @height + @originX - padding if @y! - @originY + @height > bottom - padding

	-- Center graphic inside bounding rect.
	centerGraphicInRect: =>
		@__graphic.x, @__graphic.y = @halfWidth!, @halfHeight! if @__graphic != nil


fixes \o/

fixes \o/

msgpack=require "MessagePack"
utils=require "moon-mission.utils"
dedent=utils.string.dedent
gsplit=utils.string.gsplit
wrap=utils.string.wrap
trim=utils.string.trim

class Game
  new: (@options={})=>
    @prompt or="> "
    @data or={}
    @name or= @__class.__name
    @scene_instances={k,v @ for k,v in pairs @scenes}
    if @options.load
      l=msgpack.unpack @options.load
      for k,v in pairs l.data
        @data[k]=v
      @to l.cs
    else
      @to @initscene
    if @options.run
      @run!
  run: =>
    @running=true
    while @running
      if @prompt
        io.write @prompt
      io.flush!
      inp=trim io.read "*l"
      words=[w for w in inp\gmatch("%w+")]
      if @currentscene["c_"..words[1]]
        @currentscene["c_"..words[1]] @currentscene, unpack words
      elseif @["c_"..words[1]]
        @["c_"..words[1]] @, unpack words
      else
        @whatDoYouMean unpack words
  out: (str,strip=true,more=20,wrapl=79)=>
    if strip
      str=dedent str
    if wrapl>0
      str=wrap str,wrapl, "  ",""
    if more>0
      i=0
      for l in gsplit str,"\n"
        i+=1
        print l
        if i>more
          i=0
          @more!
    else
      print str
  more: (str="[More...] ")=>
    io.write str
    io.flush!
    io.read "*l"
  whatDoYouMean: (...)=>
    @out "I don't understand that."
  askText: (...)=>
    @out ...
    trim io.read "*l"
  askYN: (...)=>
    @out ...
    io.write "[Y/n] "
    io.flush!
    yn=io.read 1
    return yn=="Y" or yn=="" or yn=="y" or yn=="\n"
  quit: =>
    @running=false
  to: (s)=>
    old=@currentscene
    @currentscene=@scene_instances[s]
    @currentscene_name=s
    if old ~= @currentscene
      old\exit! if old
      @currentscene\enter! if @currentscene
  save: =>
    msgpack.pack({cs:@currentscene_name,data:@data})

msgpack=require "MessagePack"
utils=require "moon-mission.utils"
dedent=utils.string.dedent
gsplit=utils.string.gsplit
wrap=utils.string.wrap

class Game
  new: (@options={})=>
    @data or={}
    @name or= @__class.__name
    @scene_instances={k,v @ for k,v in pairs @scenes}
    if @options.load
      l=msgpack.unpack @options.load
      for k,v in pairs l.data
        @data[k]=v
      @to l.cs
    else
      @to @initscene
    if @options.run
      @run!
  run: =>
    @running=true
    while @running
      inp=io.read "*l"
      words=[w for w in inp\gmatch("%w+")]
      if @currentscene["c_"..words[1]]
        @currentscene["c_"..words[1]] @currentscene, words
      elseif @["c_"..words[1]]
        @["c_"..words[1]] @, words
      else
        @whatDoYouMean words
  out: (str,strip=true,more=10,wrapl=60)=>
    if strip
      str=dedent str
    if wrapl>0
      str=wrap str,wrapl, "  ",""
    if more>0
      i=0
      for l in gsplit str,"\n"
        i+=1
        print l
        if i>more
          i=0
          @more!
    else
      print str
  more: =>
    io.write "[More...] "
    io.flush!
    io.read "*l"
  whatDoYouMean: (words)=>
    @out "I don't understand that."
  askText: (...)=>
    @out ...
    io.read "*l"
  quit: =>
    @running=false
  to: (s)=>
    old=@currentscene
    @currentscene=@scene_instances[s]
    @currentscene_name=s
    if old ~= @currentscene
      old\exit! if old
      @currentscene\enter! if @currentscene
  save: =>
    msgpack.pack({cs:@currentscene_name,data:@data})


textutils: use silly utf8lower

textutils: use silly utf8lower

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say ivar2.util.utf8.lower(arg)
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    chan = ivar2.channels[destination] or ivar2.channels[destination\lower!]
    say table.concat([ivar2.util.nonickalert(chan.nicks, n) for n,k in pairs(chan.nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    say ivar2.util.stripformatting(arg)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) ->
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)
  '^%prot13 (.+)$': (source, destination, arg) =>
    say ivar2.util.rot13(arg)
  '^%phex (.+)$': (source, destination, arg) =>
    say arg\gsub '.', (b) ->
      ('%02x ')\format(b\byte!)
  '^%prtl (.+)$': (source, destination, arg) =>
    say ''..arg
  '^%pltr (.+)$': (source, destination, arg) =>
    say ''..arg
  '^%pemote (.+)$': (source, destination, arg) =>
    @Action destination, arg
  'botsnack': =>
    replies = {
      'Nom nom nom.'
      'Ooooh'
      'Yummi!'
      'Delightful.'
      'That makes me happy'
      'How kind!'
      'Sweet.'
      "Sorry, but I can't handle more right now."
      "*burp*"
      "Ah.. Hiccup!"
    }
    reply replies[math.random #replies]
  "#{ivar2.config.nick}.?%?": =>
    replies = {
      "Sorry, but I tried my best"
      "I am not fully sentient yet, but I'm learning every day"
      "Please, I tried my best."
      "That was what my very best, just for you"
      "Maybe another time"
      "Sorry, not feeling so good"
      "Oof."
      "Whoopsie."
      "Just give me some time, I will get it eventually"
      "ಠ_ಠ"
      "( ͡° ͜ʖ ͡°) "
    }
    reply replies[math.random #replies]
  --'^%prepeat (%d+) (.*)$': (source, destination, nr, command) =>
  --  for i=1, nr
  --    @DispatchCommand 'PRIVMSG', command, source, destination

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    chan = ivar2.channels[destination] or ivar2.channels[destination\lower!]
    say table.concat([ivar2.util.nonickalert(chan.nicks, n) for n,k in pairs(chan.nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    say ivar2.util.stripformatting(arg)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) ->
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)
  '^%prot13 (.+)$': (source, destination, arg) =>
    say ivar2.util.rot13(arg)
  '^%phex (.+)$': (source, destination, arg) =>
    say arg\gsub '.', (b) ->
      ('%02x ')\format(b\byte!)
  '^%prtl (.+)$': (source, destination, arg) =>
    say ''..arg
  '^%pltr (.+)$': (source, destination, arg) =>
    say ''..arg
  '^%pemote (.+)$': (source, destination, arg) =>
    @Action destination, arg
  'botsnack': =>
    replies = {
      'Nom nom nom.'
      'Ooooh'
      'Yummi!'
      'Delightful.'
      'That makes me happy'
      'How kind!'
      'Sweet.'
      "Sorry, but I can't handle more right now."
      "*burp*"
      "Ah.. Hiccup!"
    }
    reply replies[math.random #replies]
  "#{ivar2.config.nick}.?%?": =>
    replies = {
      "Sorry, but I tried my best"
      "I am not fully sentient yet, but I'm learning every day"
      "Please, I tried my best."
      "That was what my very best, just for you"
      "Maybe another time"
      "Sorry, not feeling so good"
      "Oof."
      "Whoopsie."
      "Just give me some time, I will get it eventually"
      "ಠ_ಠ"
      "( ͡° ͜ʖ ͡°) "
    }
    reply replies[math.random #replies]
  --'^%prepeat (%d+) (.*)$': (source, destination, nr, command) =>
  --  for i=1, nr
  --    @DispatchCommand 'PRIVMSG', command, source, destination