Use RenderOverride

Use RenderOverride

--
-- 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.
--

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')

class PonyRenderController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)
    @NEXT_OBJ_ID = 0

    CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
    new: (data) =>
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @isValid = true
        @networkedData = data
        @ent = data.ent
        @hideModels = false
        @modelCached = data\GetModel()
        @IGNORE_DRAW = false
        @CompileTextures() if @ent
        @CreateLegs() if @ent == LocalPlayer()
        @socksModel = data\GetSocksModel()
        @socksModel\SetNoDraw(false) if IsValid(@socksModel)
        @CreateFlexController() if @ent
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"
    GetEntity: => @ent
    GetData: => @networkedData
    GetModel: => @networkedData\GetModel()

    GetLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        @CreateLegs() if not IsValid()
        return @legsModel
    CreateLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        for ent in *ents.GetAll()
            if ent.isPonyLegsModel
                ent\Remove()
        @legsModel = ClientsideModel(@modelCached)
        with @legsModel
            .isPonyLegsModel = true
            \SetNoDraw(true)
            .__PPM2_PonyData = @GetData()

        @GetData()\GetWeightController()\UpdateWeight(@legsModel)

        @lastLegUpdate = CurTime()
        @legClipPlanePos = Vector(0, 0, 0)
        @legBGSetup = CurTime()
        @legUpdateFrame = 0
        @legClipDot = 0
        @duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
        @legsClipPlane = @@LEG_CLIP_VECTOR
        return @legsModel

    @LEG_SHIFT_CONST = 24
    @LEG_SHIFT_CONST_VEHICLE = 14
    @LEG_Z_CONST = 0
    @LEG_Z_CONST_VEHICLE = 20
    @LEG_ANIM_SPEED_CONST = 1
    @LEG_CLIP_OFFSET_STAND = 28
    @LEG_CLIP_OFFSET_DUCK = 12
    @LEG_CLIP_OFFSET_VEHICLE = 11

    UpdateLegs: =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        return unless IsValid(@legsModel)
        return if @legUpdateFrame == FrameNumber()
        @legUpdateFrame = FrameNumber()
        ctime = CurTime()
        ply = @ent
        seq = ply\GetSequence()

        if seq ~= @legSeq
            @legSeq = seq
            @legsModel\ResetSequence(seq)

        if @legBGSetup < ctime
            @legBGSetup = ctime + 1
            for group in *ply\GetBodyGroups()
                @legsModel\SetBodygroup(group.id, ply\GetBodygroup(group.id))

        with @legsModel
            \FrameAdvance(ctime - @lastLegUpdate)
            \SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
            @lastLegUpdate = ctime
            \SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
            \SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
            \SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
            \SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
            \SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

        if ply\InVehicle()
            local bonePos

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    bonePos = boneData

            veh = ply\GetVehicle()
            vehAng = veh\GetAngles()
            eyepos = EyePos()
            vehAng\RotateAroundAxis(vehAng\Up(), 90)

            clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
            clipAng\RotateAroundAxis(clipAng\Right(), -90)

            @legsClipPlane = clipAng\Forward()
            @legsModel\SetRenderAngles(vehAng)

            drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
            drawPos\Rotate(vehAng)
            @legsModel\SetPos(eyepos - drawPos)
            @legsModel\SetRenderOrigin(eyepos - drawPos)

            if not bonePos
                legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
                legClipPlanePos\Rotate(vehAng)
                @legClipPlanePos = eyepos - legClipPlanePos
            else
                @legClipPlanePos = bonePos

        else
            @legsClipPlane = @@LEG_CLIP_VECTOR
            eangles = EyeAngles()
            yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
            newAng = Angle(0, yaw, 0)
            rad = math.rad(yaw)
            sin, cos = math.sin(rad), math.cos(rad)
            pos = ply\GetPos()
            {:x, :y, :z} = pos
            newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
            if ply\Crouching()
                @duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
            else
                @duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

            @legsModel\SetRenderAngles(newAng)
            @legsModel\SetAngles(newAng)
            @legsModel\SetRenderOrigin(newPos)
            @legsModel\SetPos(newPos)

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    @legClipPlanePos = boneData
                else
                    @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
            else
                @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

        @legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

    @LEG_CLIP_VECTOR = Vector(0, 0, -1)
    @LEGS_MAX_DISTANCE = 60 ^ 2
    DrawLegs: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        if USE_RENDER_OVERRIDE\GetBool()
            @legsModel\SetNoDraw(false)
            if not @legsModel.RenderOverride
                @legsModel.RenderOverride = -> @DrawLegsOverride()
                @legsModel\DrawModel()
            return
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs(@legsModel)

        if ENABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @GetTextureController()\PreDrawLegs(@legsModel)
            if sizes = @GetData()\GetSizeController()
                sizes\ModifyNeck(@legsModel)
                sizes\ModifyLegs(@legsModel)
                sizes\ModifyScale(@legsModel)
            @legsModel\DrawModel()
            @GetTextureController()\PostDrawLegs(@legsModel)
            render.PopFlashlightMode()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    DrawLegsOverride: =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs(@legsModel)

        if ENABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @GetTextureController()\PreDrawLegs(@legsModel)
            if sizes = @GetData()\GetSizeController()
                sizes\ModifyNeck(@legsModel)
                sizes\ModifyLegs(@legsModel)
                sizes\ModifyScale(@legsModel)
            @legsModel\DrawModel()
            @GetTextureController()\PostDrawLegs(@legsModel)
            render.PopFlashlightMode()

        render.PopCustomClipPlane()
        render.EnableClipping(oldClip)

    DrawLegsDepth: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    IsValid: => IsValid(@ent) and @isValid
    Reset: =>
        @flexes\Reset() if @flexes and @flexes.Reset
        @GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
        @GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()
    Remove: =>
        @flexes\Remove() if @flexes
        @GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
        @isValid = false

    PlayerDeath: =>
        return if not @isValid
        @HideModels(true)
        @GetTextureController()\ResetTextures() if @GetTextureController() and @ent\IsPony()

    PlayerRespawn: =>
        return if not @isValid
        @HideModels(false) if @ent\IsPony()
        @flexes\PlayerRespawn() if @flexes
        @GetTextureController()\ResetTextures() if @GetTextureController()

    DrawModels: =>
        @socksModel\DrawModel() if IsValid(@socksModel)
    HideModels: (status = true) =>
        return if @hideModels == status
        @socksModel\SetNoDraw(status) if IsValid(@socksModel)
        @hideModels = status

    PreDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PreDraw(ent)
        @GetTextureController()\UpdateSocks(@ent, @socksModel) if IsValid(@socksModel) and PPM2.ALTERNATIVE_RENDER\GetBool()
        @flexes\Think(ent) if @flexes
    PostDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PostDraw(ent)

    @ARMS_MATERIAL_INDEX = 0
    PreDrawArms: (ent) =>
        return if not @isValid
        if ent and not @armsWeightSetup
            @armsWeightSetup = true
            weight = @GetData()\GetWeight()
            vec = Vector(weight, weight, weight)
            for i = 1, 13
                ent\ManipulateBoneScale(i, vec)
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
    PostDrawArms: (ent) =>
        return if not @isValid
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

    DataChanges: (state) =>
        return if not @isValid
        return if not @ent
        @GetTextureController()\DataChanges(state)
        @flexes\DataChanges(state) if @flexes
        switch state\GetKey()
            when 'Weight'
                @armsWeightSetup = false
                @GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
            when 'SocksModel'
                @socksModel = @GetData()\GetSocksModel()
                @socksModel\SetNoDraw(@hideModels) if IsValid(@socksModel)
                @GetTextureController()\UpdateSocks(@ent, @socksModel) if @GetTextureController() and IsValid(@socksModel)
            when 'NoFlex'
                if state\GetValue()
                    @flexes\ResetSequences() if @flexes
                    @flexes = nil
                else
                    @CreateFlexController()
    GetTextureController: =>
        return @renderController if not @isValid
        if not @renderController
            cls = PPM2.GetTextureController(@modelCached)
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    CreateFlexController: =>
        return @flexes if not @isValid
        return if @GetData()\GetNoFlex()
        if not @flexes
            cls = PPM2.GetFlexController(@modelCached)
            return if not cls
            @flexes = cls(@)
        @flexes.ent = @ent
        return @flexes
    GetFlexController: => @flexes

class NewPonyRenderController extends PonyRenderController
    @MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

    new: (data) =>
        super(data)
        @upperManeModel = data\GetUpperManeModel()
        @lowerManeModel = data\GetLowerManeModel()
        @tailModel = data\GetTailModel()
        @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    DataChanges: (state) =>
        return if not @ent
        return if not @isValid
        switch state\GetKey()
            when 'UpperManeModel'
                @upperManeModel = @GetData()\GetUpperManeModel()
                @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
                @GetTextureController()\UpdateUpperMane(@ent, @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
            when 'LowerManeModel'
                @lowerManeModel = @GetData()\GetLowerManeModel()
                @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
                @GetTextureController()\UpdateLowerMane(@ent, @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
            when 'TailModel'
                @tailModel = @GetData()\GetTailModel()
                @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
                @GetTextureController()\UpdateTail(@ent, @tailModel) if @GetTextureController() and IsValid(@tailModel)
        super(state)

    DrawModels: =>
        @upperManeModel\DrawModel() if IsValid(@upperManeModel)
        @lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
        @tailModel\DrawModel() if IsValid(@tailModel)
        super()
    HideModels: (status = true) =>
        return if @hideModels == status
        @upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(status) if IsValid(@tailModel)
        super(status)

    PreDraw: (ent = @ent) =>
        super(ent)
        if PPM2.ALTERNATIVE_RENDER\GetBool()
            textures = @GetTextureController()
            return if not textures
            textures\UpdateUpperMane(@ent, @upperManeModel) if IsValid(@upperManeModel)
            textures\UpdateLowerMane(@ent, @lowerManeModel) if IsValid(@lowerManeModel)
            textures\UpdateTail(@ent, @tailModel) if IsValid(@tailModel)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController

--
-- 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.
--

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')

class PonyRenderController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)
    @NEXT_OBJ_ID = 0

    CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
    new: (data) =>
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @isValid = true
        @networkedData = data
        @ent = data.ent
        @hideModels = false
        @modelCached = data\GetModel()
        @IGNORE_DRAW = false
        @CompileTextures() if @ent
        @CreateLegs() if @ent == LocalPlayer()
        @socksModel = data\GetSocksModel()
        @socksModel\SetNoDraw(false) if IsValid(@socksModel)
        @CreateFlexController() if @ent
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"
    GetEntity: => @ent
    GetData: => @networkedData
    GetModel: => @networkedData\GetModel()

    GetLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        @CreateLegs() if not IsValid()
        return @legsModel
    CreateLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        for ent in *ents.GetAll()
            if ent.isPonyLegsModel
                ent\Remove()
        @legsModel = ClientsideModel(@modelCached)
        with @legsModel
            .isPonyLegsModel = true
            \SetNoDraw(true)
            .__PPM2_PonyData = @GetData()

        @GetData()\GetWeightController()\UpdateWeight(@legsModel)

        @lastLegUpdate = CurTime()
        @legClipPlanePos = Vector(0, 0, 0)
        @legBGSetup = CurTime()
        @legUpdateFrame = 0
        @legClipDot = 0
        @duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
        @legsClipPlane = @@LEG_CLIP_VECTOR
        return @legsModel

    @LEG_SHIFT_CONST = 24
    @LEG_SHIFT_CONST_VEHICLE = 14
    @LEG_Z_CONST = 0
    @LEG_Z_CONST_VEHICLE = 20
    @LEG_ANIM_SPEED_CONST = 1
    @LEG_CLIP_OFFSET_STAND = 28
    @LEG_CLIP_OFFSET_DUCK = 12
    @LEG_CLIP_OFFSET_VEHICLE = 11

    UpdateLegs: =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        return unless IsValid(@legsModel)
        return if @legUpdateFrame == FrameNumber()
        @legUpdateFrame = FrameNumber()
        ctime = CurTime()
        ply = @ent
        seq = ply\GetSequence()

        if seq ~= @legSeq
            @legSeq = seq
            @legsModel\ResetSequence(seq)

        if @legBGSetup < ctime
            @legBGSetup = ctime + 1
            for group in *ply\GetBodyGroups()
                @legsModel\SetBodygroup(group.id, ply\GetBodygroup(group.id))

        with @legsModel
            \FrameAdvance(ctime - @lastLegUpdate)
            \SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
            @lastLegUpdate = ctime
            \SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
            \SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
            \SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
            \SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
            \SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

        if ply\InVehicle()
            local bonePos

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    bonePos = boneData

            veh = ply\GetVehicle()
            vehAng = veh\GetAngles()
            eyepos = EyePos()
            vehAng\RotateAroundAxis(vehAng\Up(), 90)

            clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
            clipAng\RotateAroundAxis(clipAng\Right(), -90)

            @legsClipPlane = clipAng\Forward()
            @legsModel\SetRenderAngles(vehAng)

            drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
            drawPos\Rotate(vehAng)
            @legsModel\SetPos(eyepos - drawPos)
            @legsModel\SetRenderOrigin(eyepos - drawPos)

            if not bonePos
                legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
                legClipPlanePos\Rotate(vehAng)
                @legClipPlanePos = eyepos - legClipPlanePos
            else
                @legClipPlanePos = bonePos

        else
            @legsClipPlane = @@LEG_CLIP_VECTOR
            eangles = EyeAngles()
            yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
            newAng = Angle(0, yaw, 0)
            rad = math.rad(yaw)
            sin, cos = math.sin(rad), math.cos(rad)
            pos = ply\GetPos()
            {:x, :y, :z} = pos
            newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
            if ply\Crouching()
                @duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
            else
                @duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

            @legsModel\SetRenderAngles(newAng)
            @legsModel\SetAngles(newAng)
            @legsModel\SetRenderOrigin(newPos)
            @legsModel\SetPos(newPos)

            if bone = @legsModel\LookupBone('LrigNeck1')
                if boneData = @legsModel\GetBonePosition(bone)
                    @legClipPlanePos = boneData
                else
                    @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
            else
                @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

        @legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

    @LEG_CLIP_VECTOR = Vector(0, 0, -1)
    @LEGS_MAX_DISTANCE = 60 ^ 2
    DrawLegs: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs(@legsModel)

        if ENABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @GetTextureController()\PreDrawLegs(@legsModel)
            if sizes = @GetData()\GetSizeController()
                sizes\ModifyNeck(@legsModel)
                sizes\ModifyLegs(@legsModel)
                sizes\ModifyScale(@legsModel)
            @legsModel\DrawModel()
            @GetTextureController()\PostDrawLegs(@legsModel)
            render.PopFlashlightMode()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    DrawLegsDepth: (start3D = false) =>
        return if not @isValid
        return if not ENABLE_LEGS\GetBool()
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        @GetTextureController()\PreDrawLegs(@legsModel)
        if sizes = @GetData()\GetSizeController()
            sizes\ModifyNeck(@legsModel)
            sizes\ModifyLegs(@legsModel)
            sizes\ModifyScale(@legsModel)
        @legsModel\DrawModel()
        @GetTextureController()\PostDrawLegs()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    IsValid: => IsValid(@ent) and @isValid
    Reset: =>
        @flexes\Reset() if @flexes and @flexes.Reset
        @GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
        @GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()
    Remove: =>
        @flexes\Remove() if @flexes
        @GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
        @isValid = false

    PlayerDeath: =>
        return if not @isValid
        @HideModels(true)
        @GetTextureController()\ResetTextures() if @GetTextureController() and @ent\IsPony()

    PlayerRespawn: =>
        return if not @isValid
        @HideModels(false) if @ent\IsPony()
        @flexes\PlayerRespawn() if @flexes
        @GetTextureController()\ResetTextures() if @GetTextureController()

    DrawModels: =>
        @socksModel\DrawModel() if IsValid(@socksModel)
    HideModels: (status = true) =>
        return if @hideModels == status
        @socksModel\SetNoDraw(status) if IsValid(@socksModel)
        @hideModels = status

    PreDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PreDraw(ent)
        @GetTextureController()\UpdateSocks(@ent, @socksModel) if IsValid(@socksModel) and PPM2.ALTERNATIVE_RENDER\GetBool()
        @flexes\Think(ent) if @flexes
    PostDraw: (ent = @ent) =>
        return if not @isValid
        @GetTextureController()\PostDraw(ent)

    @ARMS_MATERIAL_INDEX = 0
    PreDrawArms: (ent) =>
        return if not @isValid
        if ent and not @armsWeightSetup
            @armsWeightSetup = true
            weight = @GetData()\GetWeight()
            vec = Vector(weight, weight, weight)
            for i = 1, 13
                ent\ManipulateBoneScale(i, vec)
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
    PostDrawArms: (ent) =>
        return if not @isValid
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

    DataChanges: (state) =>
        return if not @isValid
        return if not @ent
        @GetTextureController()\DataChanges(state)
        @flexes\DataChanges(state) if @flexes
        switch state\GetKey()
            when 'Weight'
                @armsWeightSetup = false
                @GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
            when 'SocksModel'
                @socksModel = @GetData()\GetSocksModel()
                @socksModel\SetNoDraw(@hideModels) if IsValid(@socksModel)
                @GetTextureController()\UpdateSocks(@ent, @socksModel) if @GetTextureController() and IsValid(@socksModel)
            when 'NoFlex'
                if state\GetValue()
                    @flexes\ResetSequences() if @flexes
                    @flexes = nil
                else
                    @CreateFlexController()
    GetTextureController: =>
        return @renderController if not @isValid
        if not @renderController
            cls = PPM2.GetTextureController(@modelCached)
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    CreateFlexController: =>
        return @flexes if not @isValid
        return if @GetData()\GetNoFlex()
        if not @flexes
            cls = PPM2.GetFlexController(@modelCached)
            return if not cls
            @flexes = cls(@)
        @flexes.ent = @ent
        return @flexes
    GetFlexController: => @flexes

class NewPonyRenderController extends PonyRenderController
    @MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

    new: (data) =>
        super(data)
        @upperManeModel = data\GetUpperManeModel()
        @lowerManeModel = data\GetLowerManeModel()
        @tailModel = data\GetTailModel()
        @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    DataChanges: (state) =>
        return if not @ent
        return if not @isValid
        switch state\GetKey()
            when 'UpperManeModel'
                @upperManeModel = @GetData()\GetUpperManeModel()
                @upperManeModel\SetNoDraw(@hideModels) if IsValid(@upperManeModel)
                @GetTextureController()\UpdateUpperMane(@ent, @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
            when 'LowerManeModel'
                @lowerManeModel = @GetData()\GetLowerManeModel()
                @lowerManeModel\SetNoDraw(@hideModels) if IsValid(@lowerManeModel)
                @GetTextureController()\UpdateLowerMane(@ent, @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
            when 'TailModel'
                @tailModel = @GetData()\GetTailModel()
                @tailModel\SetNoDraw(@hideModels) if IsValid(@tailModel)
                @GetTextureController()\UpdateTail(@ent, @tailModel) if @GetTextureController() and IsValid(@tailModel)
        super(state)

    DrawModels: =>
        @upperManeModel\DrawModel() if IsValid(@upperManeModel)
        @lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
        @tailModel\DrawModel() if IsValid(@tailModel)
        super()
    HideModels: (status = true) =>
        return if @hideModels == status
        @upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(status) if IsValid(@tailModel)
        super(status)

    PreDraw: (ent = @ent) =>
        super(ent)
        if PPM2.ALTERNATIVE_RENDER\GetBool()
            textures = @GetTextureController()
            return if not textures
            textures\UpdateUpperMane(@ent, @upperManeModel) if IsValid(@upperManeModel)
            textures\UpdateLowerMane(@ent, @lowerManeModel) if IsValid(@lowerManeModel)
            textures\UpdateTail(@ent, @tailModel) if IsValid(@tailModel)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController


trigger: fix pattern, add list, fix deletion, add show

Former-commit-id: ef7df438cd17a448f9ee6f1a103e4e335cdef806 [formerly 024750e4e7d82d3e82eea25aad147d551974d05c]
Former-commit-id: fcc40717149745942539018870ab7c3e9338cf31

sql = require'lsqlite3'

openDb = ->
  db = sql.open 'cache/trigger.sql'

  db\exec [[
    CREATE TABLE IF NOT EXISTS trigger (
      name UNIQUE ON CONFLICT REPLACE,
      pattern UNIQUE ON CONFLICT REPLACE,
      funcstr
    );
    ]]
  return db

triggerHelp  = (source, destination, argument) =>
   @Msg 'privmsg', destination, source, 'Usage: !trigger add <name>|<lua pattern>|<lua code>. Ex: !trigger add help|^!help (%w+)|say "%s, you need help. Delete: !trigger del <name>"'

-- Construct a safe environ for trigger with two functions; say and reply
--- Preferrably you should be able to cross-call modules here to make aliasing possible
sandbox = (func) ->
  (source, destination, ...) =>
    -- Store args so we can use them in env
    arg = {...}
    env =
      say: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      reply: (str) ->
        @Msg 'privmsg', destination, source, source.nick..': '..str, unpack(arg),
      print: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      simplehttp:require'simplehttp'
      json:require'json'
      :string
      :type
      :tostring
      :tonumber
      :ipars
      :pairs
      :table
      :next

    --setfenv func, setmetatable(env, {__index: _G })
    setfenv func, env
    success, err = pcall func, arg
    if err
      @Msg 'privmsg', destination, source, err
    else
      success

-- Construct a safe function to run and return a handler
triggerHandler = (source, destination, funcstr) =>
  func, err = loadstring funcstr
  unless func
    @Msg 'privmsg', destination, source, 'Trigger error: %s', err
  else
    sandbox(func)

-- Register the command
regCommand = (source, destination, name, pattern, funcstr) =>
  db = openDb!
  ins = db\prepare "INSERT INTO trigger (name, pattern, funcstr) VALUES(?, ?, ?)"
  code = ins\bind_values name, pattern, funcstr
  code = ins\step!
  code = ins\finalize!
  db\close!

  @RegisterCommand 'trigger', pattern, triggerHandler(@, source, destination, funcstr)

delCommand = (source, destination, name) =>
  db = openDb!
  -- Find pattern which is used for deletion of handler
  stmt = db\prepare "SELECT pattern FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  code = stmt\step!
  if code == sqlite3.DONE
    -- Invalid name
    db\close!
    @Msg 'privmsg', destination, source, 'Invalid name'
    return
  pattern = stmt\get_values()[1]

  -- Delete trigger from db
  ins = db\prepare "DELETE FROM trigger WHERE name = ?"
  code = ins\bind_values name
  code = ins\step!
  code = ins\finalize!
  db\close!

  @UnregisterCommand 'triggers', pattern
  @Msg 'privmsg', destination, source, "Trigger #{pattern} deleted"

listTriggers = (source, destination) =>
  db = openDb!
  stmt = db\prepare "SELECT name FROM trigger"
  out = {}
  for row in stmt\nrows! do
    table.insert out, row.name

  db\close!

  if #out > 0
    @Msg 'privmsg', destination, source, "Triggers: "..table.concat(out, ', ')

showTrigger = (source, destination, name) =>
  db = openDb!
  stmt = db\prepare "SELECT * FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  for row in stmt\nrows! do
    @Msg 'privmsg', destination, source, "Trigger: \002#{row.name}\002, pattern: #{row.pattern}, code: #{row.funcstr}"
  db\close!

-- Register commands on startup
db = openDb!
for row in db\nrows 'SELECT pattern, funcstr FROM trigger'
  ivar2\RegisterCommand 'triggers', row.pattern, triggerHandler(ivar2, nil, nil, row.funcstr)
db\close!

PRIVMSG:
  '^%ptrigger$': triggerHelp
  '^%ptrigger add (.+)|(.+)|(.+)$': regCommand
  '^%ptrigger del (.+)$': delCommand
  '^%ptrigger list$': listTriggers
  '^%ptrigger show (.+)$': showTrigger

sql = require'lsqlite3'

openDb = ->
  db = sql.open 'cache/trigger.sql'

  db\exec [[
    CREATE TABLE IF NOT EXISTS trigger (
      name UNIQUE ON CONFLICT REPLACE,
      pattern UNIQUE ON CONFLICT REPLACE,
      funcstr
    );
    ]]
  return db

triggerHelp  = (source, destination, argument) =>
   @Msg 'privmsg', destination, source, 'Usage: !trigger add <name>|<lua pattern>|<lua code>. Ex: !trigger add help|^!help (%w+)|say "%s, you need help"'

-- Construct a safe environ for trigger with two functions; say and reply
--- Preferrably you should be able to cross-call modules here to make aliasing possible
sandbox = (func) ->
  (source, destination, ...) =>
    -- Store args so we can use them in env
    arg = {...}
    env =
      say: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      reply: (str) ->
        @Msg 'privmsg', destination, source, source.nick..': '..str, unpack(arg),
      print: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      simplehttp:require'simplehttp'
      json:require'json'
      :string
      :type
      :tostring
      :tonumber
      :ipars
      :pairs
      :table
      :next

    --setfenv func, setmetatable(env, {__index: _G })
    setfenv func, env
    success, err = pcall func, arg
    if err
      @Msg 'privmsg', destination, source, err
    else
      success

-- Construct a safe function to run and return a handler
triggerHandler = (source, destination, funcstr) =>
  func, err = loadstring funcstr
  unless func
    @Msg 'privmsg', destination, source, 'Trigger error: %s', err
  else
    sandbox(func)

-- Register the command
regCommand = (source, destination, name, pattern, funcstr) =>
  db = openDb!
  ins = db\prepare "INSERT INTO trigger (name, pattern, funcstr) VALUES(?, ?, ?)"
  code = ins\bind_values name, pattern, funcstr
  code = ins\step!
  code = ins\finalize!
  db\close!

  @RegisterCommand 'trigger', pattern, triggerHandler(@, source, destination, funcstr)

delCommand = (source, destination, name) =>
  db = openDb!
  -- Find pattern which is used for deletion of handler
  stmt = db\prepare "SELECT pattern FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  code = stmt\step!
  if code == sqlite3.DONE
    -- Invalid name
    db\close!
    return
  pattern = stmt\get_values()[1]

  -- Delete trigger from db
  ins = db\prepare "DELETE FROM trigger WHERE name = ?"
  code = ins\bind_values pattern
  code = ins\step!
  code = ins\finalize!
  db\close!

  @UnregisterCommand 'triggers', pattern

-- Register commands on startup
db = openDb!
for row in db\nrows 'SELECT pattern, funcstr FROM trigger'
  ivar2\RegisterCommand 'triggers', row.pattern, triggerHandler(ivar2, nil, nil, row.funcstr)
db\close!

PRIVMSG:
  '%ptrigger$': triggerHelp
  '%ptrigger add (.+)|(.+)|(.+)$': regCommand
  '%ptrigger del (.+)$': delCommand


Actually does things.

Actually does things.

rex = require "rex_pcre"
cli = require "cliargs"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	results = {}
	for k, v in pairs structure
		res = func(v, map_listing)
		insert(results, res) if res
	results

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "mkdir"
			"[mkdir]: #{old} to: #{new}"
		when "symlink", "hardlink"
			lfs.link(old, new, action == "hardlink" or false) if not debug
			"[link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')

			isdir = (md) -> if md == 'directory' then true else false

			in_f = parent.in .. '/' .. file
			out_f = if isdir(mode)
				do
					f = file
					->
					f = rex.gsub(f, regex, '')
					f = f\match("^%s*(.-)%s*$")
					parent.out .. '/' .. f
			else
				do
					f = file
					->
					f = rex.gsub(f, regex, '')
					f = rex.match(f, [[(.*)(v\d)]]) or f
					f = f\gsub('[ ]', '')
					f = rex.gsub(f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
					f ..= file_ext(file)
					parent.out .. '/' .. f

			action = if isdir(mode) then "mkdir" else args["a"]
			print perform_action(in_f, out_f, action)
			process_files(data, regex, {in: in_f, out: out_f}) if data

		else
			print "ERR: ", file, mode

cli\set_name("regexulon.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-e, --existing", "skip creating previously existing target directories.")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")

export args = cli\parse_args()
return if not args

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

inputdir = args["DIR"]
targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

--map_listing(files, print_listing)
processed = process_files(files, regex, {in: inputdir, out: targetdir})

rex = require "rex_pcre"
cli = require "cliargs"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	results = {}
	for k, v in pairs structure
		res = func(v, map_listing)
		insert(results, res) if res
	results

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "mkdir"
			"[mkdir]: Woo?"
		when "symlink", "hardlink"
			lfs.link(old, new, action == "hardlink" or false) if not debug
			"[link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')

			isdir = (md) -> if md == 'directory' then true else false

			in_f = parent.in .. '/' .. file
			out_f = if isdir(mode)
				do
					f = file
					->
					f = rex.gsub(f, regex, '')
					f = f\match("^%s*(.-)%s*$")
					parent.out .. '/' .. f
			else
				do
					f = file
					->
					f = rex.gsub(f, regex, '')
					f = rex.match(f, [[(.*)(v\d)]]) or f
					f = f\gsub('[ ]', '')
					f = rex.gsub(f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
					f ..= file_ext(file)
					parent.out .. '/' .. f

			action = if isdir(mode) then "mkdir" else args["a"]
			print perform_action(in_f, out_f, action)
			process_files(data, regex, {in: in_f, out: out_f}) if data

		else
			print "ERR: ", file, mode

cli\set_name("regexulon.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-e, --existing", "skip creating previously existing target directories.")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")

export args = cli\parse_args()
return if not args

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

inputdir = args["DIR"]
targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

--map_listing(files, print_listing)
processed = process_files(files, regex, {in: inputdir, out: targetdir})


github: proper unescape

github: proper unescape

Former-commit-id: d788d851e25b9d0e3336aa414fc7f553ce2ae3a3 [formerly fcd1eb723fbfe35dbf6d20c9426f12051207753a]
Former-commit-id: e8c56570724553f6c8cb96d414eaa5e33ff5044a

util = require 'util'

hex_to_char = (x) ->
  string.char(tonumber(x, 16))

unescape = (url) ->
  url\gsub("%%(%x%x)", hex_to_char)

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = unescape(destination)
  json = util.json.decode(req.body)

  repo = json.repository.full_name
  branch = json.ref\gsub 'refs/heads/', ''

  for i, c in pairs(json.commits)
    if i > 3
      break
    message = c.message\gsub '\n.*', ''
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{c.author.username}: #{message} #{c.url}"
  if #json.commits > 3
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{#json.commits-3} more commits not displayed."

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}

util = require 'util'

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = destination\gsub '%%23', '#'
  json = util.json.decode(req.body)

  repo = json.repository.full_name
  branch = json.ref\gsub 'refs/heads/', ''

  for i, c in pairs(json.commits)
    if i > 3
      break
    message = c.message\gsub '\n.*', ''
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{c.author.username}: #{message} #{c.url}"
  if #json.commits > 3
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{#json.commits-3} more commits not displayed."

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}


github: limit commit output to 3 commits

util = require 'util'

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = destination\gsub '%%23', '#'
  json = util.json.decode(req.body)

  repo = json.repository.full_name
  branch = json.ref\gsub 'refs/heads/', ''

  for i, c in pairs(json.commits)
    if i > 3
      break
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{c.author.username}: #{c.message} #{c.url}"
  if #json.commits > 3
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{#json.commits-3} more commits not displayed."

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}

util = require 'util'

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = destination\gsub '%%23', '#'
  json = util.json.decode(req.body)

  repo = json.repository.full_name
  branch = json.ref\gsub 'refs/heads/', ''

  for c in *json.commits
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{c.author.username}: #{c.message} #{c.url}"

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}


Fix type in configuration file

Fix type in configuration file

The log level for nginx warnings is `warn`, not `warning`

config = require 'lapis.config'

config 'development', ->
    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'notice'

config 'production', ->
    port 80
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warn'

config = require 'lapis.config'

config 'development', ->
    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'notice'

config 'production', ->
    port 80
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warning'


clean up client nacl module

clean up client nacl module

cjson = require"cjson"

ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=[];',./!@#$%^&*()_+{}|:\"<>?\\`~ "
DEFAULT_FONT = "12px sans-serif"

-- disable invalid functions
os.remove = -> error "os.remove is disabled"
os.rename = -> error "os.rename is disabled"
os.execute = -> error "os.execute is disabled"
os.exit = -> error "os.exit is disabled"
io.tmpfile = -> error "io.tmpfile is disabled"

post_message = (o) ->
  o = cjson.encode o if type(o) == "table"
  nacl.post_message o

printer = (channel="std_out") ->
  (...) ->
    args = {...}
    str = table.concat [tostring s for s in *args], "\t"
    post_message { channel, str }

async_print = printer"std_out"
async_err = printer"std_err"

msg_id = 0
msg_responses = {}

request_response = (msg) ->
  id = msg_id
  msg_id += 1
  post_message { "request", id, msg }
  while true
    msg = coroutine.yield!
    if msg == "response" and msg_responses[id]
      return with msg_responses[id]
        msg_responses[id] = nil

safe_resume = (co, ...) ->
  success, error_msg = coroutine.resume co, ...
  if not success
    async_err debug.traceback co, error_msg
    nacl.set_game_thread nil
    false
  else
    true

async_require = (module_name) ->
  mod = package.loaded[module_name]
  return mod if mod

  loader = if package.preload[module_name]
    package.preload[module_name]
  else
    msg = request_response { "require", module_name }
    status, code = unpack msg
    return async_err code if status == "error"
    assert loadstring code, module_name

  setfenv loader, getfenv 2
  mod = loader module_name
  mod = true if mod == nil
  package.loaded[module_name] = mod
  mod

nacl.lib = {
  core: nacl
  prefetch: (files) ->
    tuples = {}

    add_files = (list, t="text") ->
      for k, file_or_list in pairs list
        if type(file_or_list) == "table"
          add_files file_or_list, k
        else
          table.insert tuples, { t, file_or_list }

    add_files files

    return if #tuples == 0
    status, msg = unpack request_response { "prefetch", tuples }
    error "prefetch: " .. msg if status != "success"

  track_event: (category, action, label=nil, value=nil, interactive=true) ->
    post_message { "track_event", { category, action, label, value, interactive } }
}

-- scope of the game thread
async_scope = setmetatable {
    print: async_print
    require: async_require
    nacl: nacl.lib
  }, {
    __index: _G
  }

game_thread = nil -- to prevent it from being garbage collected

nacl.show_error = async_err -- used by C to send errors to client

nacl.handle_message = (msg) ->
  error "unknown msg: " .. tostring(msg) if type(msg) != "string"
  print ">>", msg\sub 1, 120

  msg = cjson.decode msg

  switch msg[1]
    -- create a new execution thread
    -- throws out the old one
    when "execute"
      fn, err = loadstring msg[2], "execute"
      if not fn
        async_err err
      else
        setfenv fn, async_scope
        run = -> aroma.run fn

        game_thread = coroutine.create run
        nacl.set_game_thread game_thread
        safe_resume game_thread

    when "response"
      _, id, data = unpack msg
      msg_responses[id] = data
      safe_resume game_thread, "response"
    else
      error "Don't know how to handle message: " .. (msg[1] or msg)

img_cache = {}

image_data_from_url = (url) ->
  msg = request_response { "image", url }
  status, bytes, width, height = unpack msg
  if status == "success"
    nacl.image_data_from_byte_string bytes, width, height
  else
    error bytes

class AudioSource
  new: (@source_id, @url) =>
  msg: (...) => post_message { "audio", @source_id, ... }

  play: => @msg "play"
  stop: => @msg "stop"
  rewind: => @msg "rewind"
  setLooping: (should_loop) => @msg "set_looping", should_loop

  __tostring: => "HTMLAudioSource<" .. @url .. ">"

nacl.init = {
  graphics: =>
    old_new_image = @newImage

    @newImage = (url, ...) ->
      return old_new_image url, ... if type(url) != "string"
      return img_cache[url] if img_cache[url]

      img_data = image_data_from_url url
      img = old_new_image img_data
      img_cache[url] = img
      img

    @newFont = (font_str, alphabet=ALPHABET) ->
      glyphs = request_response { "font", font_str, alphabet }
      cache = aroma.font.newGlyphCache!
      for g in *glyphs
        letter, data_string, w, h = unpack g
        data = nacl.image_data_from_byte_string data_string, w, h
        cache\add_glyph letter\byte!, data
      cache\to_font!

    aroma.boot = ->
      if not self.getFont!
        self.setFont self.newFont DEFAULT_FONT

  audio: =>
    @newSource = (url, kind="static") ->
      msg = request_response { "audio", url, kind }
      status, source_id, err_msg = unpack msg
      if status == "success"
        AudioSource source_id, url
      else
        error error_msg

  image: =>
    old_new_image_data = @newImageData
    @newImageData = (...) ->
      args = {...}
      old_new_image_data ... if type(args[1]) != "string"
      image_data_from_url args[1]
}

functions_to_reset = {"draw", "update", "keypressed", "keyreleased"}

nacl.init_all = (aroma) ->
  aroma.run = (setup_fn) ->
    blank = ->
    aroma[key] = blank for key in *functions_to_reset
    aroma.graphics.reset!
    aroma.boot!

    setup_fn!

    while true -- listen for game messages
      msg = {coroutine.yield!}
      if aroma[msg[1]]
        name = table.remove msg, 1
        aroma[name] unpack msg
      else
        print "unknown message"
        print unpack msg

cjson = require"cjson"

ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=[];',./!@#$%^&*()_+{}|:\"<>?\\`~ "
DEFAULT_FONT = "12px sans-serif"

-- disable invalid functions
os.remove = -> error "os.remove is disabled"
os.rename = -> error "os.rename is disabled"
os.execute = -> error "os.execute is disabled"
os.exit = -> error "os.exit is disabled"
io.tmpfile = -> error "io.tmpfile is disabled"

post_message = (o) ->
  o = cjson.encode o if type(o) == "table"
  nacl.post_message o

printer = (channel="std_out") ->
  (...) ->
    args = {...}
    str = table.concat [tostring s for s in *args], "\t"
    post_message { channel, str }

async_print = printer"std_out"
async_err = printer"std_err"

msg_id = 0
msg_responses = {}

request_response = (msg) ->
  id = msg_id
  msg_id += 1
  post_message { "request", id, msg }
  while true
    msg = coroutine.yield!
    if msg == "response" and msg_responses[id]
      return with msg_responses[id]
        msg_responses[id] = nil

safe_resume = (co, ...) ->
  success, error_msg = coroutine.resume co, ...
  if not success
    async_err debug.traceback co, error_msg
    nacl.set_game_thread nil
    false
  else
    true

async_require = (module_name) ->
  mod = package.loaded[module_name]
  return mod if mod

  loader = if package.preload[module_name]
    package.preload[module_name]
  else
    msg = request_response { "require", module_name }
    status, code = unpack msg
    return async_err code if status == "error"
    assert loadstring code, module_name

  setfenv loader, getfenv 2
  mod = loader module_name
  mod = true if mod == nil
  package.loaded[module_name] = mod
  mod

-- scope of the game thread
async_scope = setmetatable {
    print: async_print
    require: async_require
  }, {
    __index: _G
  }

game_thread = nil -- to prevent it from being garbage collected

nacl.show_error = async_err

-- these are client functions, need a way to separate them
nacl.prefetch = (files) ->
  tuples = {}

  add_files = (list, t="text") ->
    for k, file_or_list in pairs list
      if type(file_or_list) == "table"
        add_files file_or_list, k
      else
        table.insert tuples, { t, file_or_list }

  add_files files

  return if #tuples == 0
  status, msg = unpack request_response { "prefetch", tuples }
  error "prefetch: " .. msg if status != "success"

nacl.track_event = (category, action, label=nil, value=nil, interactive=true) ->
  post_message { "track_event", { category, action, label, value, interactive } }

---

nacl.handle_message = (msg) ->
  error "unknown msg: " .. tostring(msg) if type(msg) != "string"
  print ">>", msg\sub 1, 120

  msg = cjson.decode msg

  switch msg[1]
    -- create a new execution thread
    -- throws out the old one
    when "execute"
      fn, err = loadstring msg[2], "execute"
      if not fn
        async_err err
      else
        setfenv fn, async_scope
        run = -> aroma.run fn

        game_thread = coroutine.create run
        nacl.set_game_thread game_thread
        safe_resume game_thread

    when "response"
      _, id, data = unpack msg
      msg_responses[id] = data
      safe_resume game_thread, "response"
    else
      error "Don't know how to handle message: " .. (msg[1] or msg)

img_cache = {}

image_data_from_url = (url) ->
  msg = request_response { "image", url }
  status, bytes, width, height = unpack msg
  if status == "success"
    nacl.image_data_from_byte_string bytes, width, height
  else
    error bytes

class AudioSource
  new: (@source_id, @url) =>
  msg: (...) => post_message { "audio", @source_id, ... }

  play: => @msg "play"
  stop: => @msg "stop"
  rewind: => @msg "rewind"
  setLooping: (should_loop) => @msg "set_looping", should_loop

  __tostring: => "HTMLAudioSource<" .. @url .. ">"

nacl.init = {
  graphics: =>
    old_new_image = @newImage

    @newImage = (url, ...) ->
      return old_new_image url, ... if type(url) != "string"
      return img_cache[url] if img_cache[url]

      img_data = image_data_from_url url
      img = old_new_image img_data
      img_cache[url] = img
      img

    @newFont = (font_str, alphabet=ALPHABET) ->
      glyphs = request_response { "font", font_str, alphabet }
      cache = aroma.font.newGlyphCache!
      for g in *glyphs
        letter, data_string, w, h = unpack g
        data = nacl.image_data_from_byte_string data_string, w, h
        cache\add_glyph letter\byte!, data
      cache\to_font!

    aroma.boot = ->
      if not self.getFont!
        self.setFont self.newFont DEFAULT_FONT

  audio: =>
    @newSource = (url, kind="static") ->
      msg = request_response { "audio", url, kind }
      status, source_id, err_msg = unpack msg
      if status == "success"
        AudioSource source_id, url
      else
        error error_msg

  image: =>
    old_new_image_data = @newImageData
    @newImageData = (...) ->
      args = {...}
      old_new_image_data ... if type(args[1]) != "string"
      image_data_from_url args[1]
}

functions_to_reset = {"draw", "update", "keypressed", "keyreleased"}

nacl.init_all = (aroma) ->
  aroma.run = (setup_fn) ->
    blank = ->
    aroma[key] = blank for key in *functions_to_reset
    aroma.graphics.reset!
    aroma.boot!

    setup_fn!

    while true -- listen for game messages
      msg = {coroutine.yield!}
      if aroma[msg[1]]
        name = table.remove msg, 1
        aroma[name] unpack msg
      else
        print "unknown message"
        print unpack msg