Attempt to fix tail scaling

Attempt to fix tail scaling

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

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

class DefaultBodygroupController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/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(@)

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = 1
    @BODYGROUP_HORN = 2
    @BODYGROUP_WINGS = 3
    @BODYGROUP_MANE_UPPER = 4
    @BODYGROUP_MANE_LOWER = 5
    @BODYGROUP_TAIL = 6
    @BODYGROUP_CMARK = 7
    @BODYGROUP_EYELASH = 8

    new: (controller) =>
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller

    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    ApplyRace: =>
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)

    ResetBodygroups: =>
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
    ApplyBodygroups: =>
        @ResetBodygroups()
        @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
        @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
        @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
        @ApplyRace()

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        switch state\GetKey()
            when 'ManeType'
                @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
            when 'ManeTypeLower'
                @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
            when 'TailType'
                @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
            when 'TailSize'
                size = state\GetValue()
                vec = Vector(size, size, size)
                @ent\ManipulateBoneScale(@@TAIL_BONE1, vec)
                @ent\ManipulateBoneScale(@@TAIL_BONE2, vec)
                @ent\ManipulateBoneScale(@@TAIL_BONE3, vec)
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
            when 'Race'
                @ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

    new: (...) => super(...)

    ApplyRace: =>
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 2)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 3)

class NewBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = -1
    @BODYGROUP_HORN = 1
    @BODYGROUP_WINGS = 2

    CreateUpperManeModel: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)

        for ent in *ents.GetAll()
            if ent.isManeModel and ent.upperMane and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isManeModel = true
            .upperMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
                @ent\SetNWEntity('PPM2.UpperManeModel', @maneModelUP) if IsValid(@ent)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        @maneModelLower\Remove() if IsValid(@maneModelLower)

        for ent in *ents.GetAll()
            if ent.isManeModel and ent.lowerMane and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isManeModel = true
            .lowerMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
                @ent\SetNWEntity('PPM2.LowerManeModel', @maneModelLower) if IsValid(@ent)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        @tailModel\Remove() if IsValid(@tailModel)

        for ent in *ents.GetAll()
            if ent.isManeModel and ent.isTail and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isManeModel = true
            .isTail = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@tailModel)
                @GetData()\SetTailModel(@tailModel)
                @ent\SetNWEntity('PPM2.TailModel', @tailModel) if IsValid(@ent)
        else
            @GetData()\SetTailModel(@tailModel)

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        return @tailModel

    GetUpperMane: => @maneModelUP or NULL
    GetLowerMane: => @maneModelLower or NULL
    GetTail: => @tailModel or NULL

    MergeModels: (targetEnt = NULL) =>
        return unless IsValid(targetEnt)
        maneUpper = @CreateUpperManeModelIfNotExists()
        maneLower = @CreateLowerManeModelIfNotExists()
        tail = @CreateTailModelIfNotExists()
        if IsValid(maneUpper)
            maneUpper\SetParent(targetEnt)
            maneUpper\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(maneLower)
            maneLower\SetParent(targetEnt)
            maneLower\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(tail)
            tail\SetParent(targetEnt)
            tail\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    UpdateUpperMane: =>
        return if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model)
        @maneModelUP\SetBodygroup(1, bodygroupID)
        return @maneModelUP
    UpdateLowerMane: =>
        return if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model)
        @maneModelLower\SetBodygroup(1, bodygroupID)
        return @maneModelLower
    UpdateTailModel: =>
        return if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model)
        @tailModel\SetBodygroup(1, bodygroupID)
        @tailModel\SetModelScale(@GetData()\GetTailSize())
        return @tailModel

    ApplyBodygroups: =>
        @ResetBodygroups()
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
        @ApplyRace()
        @CreateUpperManeModel()
        @CreateLowerManeModel()
        @CreateTailModel()

    DataChanges: (state) =>
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
            when 'ManeTypeNew'
                @UpdateUpperMane()
            when 'ManeTypeLowerNew'
                @UpdateLowerMane()
            when 'TailTypeNew'
                @UpdateTailModel()
            when 'TailSize'
                @UpdateTailModel()
            when 'Race'
                @ApplyRace()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

PPM2.GetBodugroupController = (model = 'models/ppm/player_default_base.mdl') -> DefaultBodygroupController.AVALIABLE_CONTROLLERS[model\lower()] or DefaultBodygroupController

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

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

class DefaultBodygroupController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/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(@)

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = 1
    @BODYGROUP_HORN = 2
    @BODYGROUP_WINGS = 3
    @BODYGROUP_MANE_UPPER = 4
    @BODYGROUP_MANE_LOWER = 5
    @BODYGROUP_TAIL = 6
    @BODYGROUP_CMARK = 7
    @BODYGROUP_EYELASH = 8

    new: (controller) =>
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller

    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    ApplyRace: =>
        switch @controller\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)

    ApplyBodygroups: =>
        @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @controller\GetManeType())
        @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @controller\GetManeTypeLower())
        @ent\SetBodygroup(@@BODYGROUP_TAIL, @controller\GetTailType())
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @controller\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @controller\GetGender())
        @ApplyRace()
    DataChanges: (state) =>
        switch state\GetKey()
            when 'ManeType'
                @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @controller\GetManeType())
            when 'ManeTypeLower'
                @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @controller\GetManeTypeLower())
            when 'TailType'
                @ent\SetBodygroup(@@BODYGROUP_TAIL, @controller\GetTailType())
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @controller\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @controller\GetGender())
            when 'Race'
                @ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

    new: (...) => super(...)

    ApplyRace: =>
        switch @controller\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 2)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 3)

class NewBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = -1
    @BODYGROUP_HORN = 1
    @BODYGROUP_WINGS = 2

    CreateUpperManeModel: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)

        for ent in *ents.GetAll()
            if ent.isManeModel and ent.upperMane and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@controller\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"

        return if CLIENT and @controller\IsGoingToNetwork()
        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isManeModel = true
            .upperMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelUP)
                @controller\SetUpperManeModel(@maneModelUP)
                @ent\SetNWEntity('PPM2.UpperManeModel', @maneModelUP) if IsValid(@ent)
        else
            @controller\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        @maneModelLower\Remove() if IsValid(@maneModelLower)

        for ent in *ents.GetAll()
            if ent.isManeModel and ent.lowerMane and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@controller\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"

        return if CLIENT and @controller\IsGoingToNetwork()
        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isManeModel = true
            .lowerMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelLower)
                @controller\SetLowerManeModel(@maneModelLower)
                @ent\SetNWEntity('PPM2.LowerManeModel', @maneModelLower) if IsValid(@ent)
        else
            @controller\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        @tailModel\Remove() if IsValid(@tailModel)

        for ent in *ents.GetAll()
            if ent.isManeModel and ent.isTail and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@controller\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"

        return if CLIENT and @controller\IsGoingToNetwork()
        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isManeModel = true
            .isTail = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@tailModel)
                @controller\SetTailModel(@tailModel)
                @ent\SetNWEntity('PPM2.TailModel', @tailModel) if IsValid(@ent)
        else
            @controller\SetTailModel(@tailModel)

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return if CLIENT and @controller\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return if CLIENT and @controller\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return if CLIENT and @controller\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        return @tailModel

    GetUpperMane: => @maneModelUP or NULL
    GetLowerMane: => @maneModelLower or NULL
    GetTail: => @tailModel or NULL

    MergeModels: (targetEnt = NULL) =>
        return unless IsValid(targetEnt)
        maneUpper = @CreateUpperManeModelIfNotExists()
        maneLower = @CreateLowerManeModelIfNotExists()
        tail = @CreateTailModelIfNotExists()
        if IsValid(maneUpper)
            maneUpper\SetParent(targetEnt)
            maneUpper\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(maneLower)
            maneLower\SetParent(targetEnt)
            maneLower\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(tail)
            tail\SetParent(targetEnt)
            tail\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    UpdateUpperMane: =>
        return if CLIENT and @controller\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        modelID, bodygroupID = PPM2.TransformNewModelID(@controller\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model)
        @maneModelUP\SetBodygroup(1, bodygroupID)
        return @maneModelUP
    UpdateLowerMane: =>
        return if CLIENT and @controller\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        modelID, bodygroupID = PPM2.TransformNewModelID(@controller\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model)
        @maneModelLower\SetBodygroup(1, bodygroupID)
        return @maneModelLower
    UpdateTailModel: =>
        return if CLIENT and @controller\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        modelID, bodygroupID = PPM2.TransformNewModelID(@controller\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model)
        @tailModel\SetBodygroup(1, bodygroupID)
        return @tailModel

    ApplyBodygroups: =>
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @controller\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @controller\GetGender())
        @ApplyRace()
        @CreateUpperManeModel()
        @CreateLowerManeModel()
        @CreateTailModel()

    DataChanges: (state) =>
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @controller\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @controller\GetGender())
            when 'ManeTypeNew'
                @UpdateUpperMane()
            when 'ManeTypeLowerNew'
                @UpdateLowerMane()
            when 'TailTypeNew'
                @UpdateTailModel()
            when 'Race'
                @ApplyRace()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

PPM2.GetBodugroupController = (model = 'models/ppm/player_default_base.mdl') -> DefaultBodygroupController.AVALIABLE_CONTROLLERS[model\lower()] or DefaultBodygroupController


Add reference URL

Add reference URL

{
    makeWeakTable: (table, key=true, value=true) ->
        -- see http://lua-users.org/wiki/WeakTablesTutorial
        meta = getmetatable(table)
        if meta == nil
            meta = {}
        meta.__mode = ""
        if key
            meta.__mode ..= "k"
        if value
            meta.__mode ..= "v"
        setmetatable(table, meta)
}

{
    makeWeakTable: (table, key=true, value=true) ->
        meta = getmetatable(table)
        if meta == nil
            meta = {}
        meta.__mode = ""
        if key
            meta.__mode ..= "k"
        if value
            meta.__mode ..= "v"
        setmetatable(table, meta)
}


Keep call info from glVertexAttribPointer around.

- Also fixed some minor errors.

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

Promise = require "glurg.common.Promise"
Filter = require "glurg.filters.Filter"
Call = require "glurg.trace.Call"
Trace = require "glurg.trace.Trace"

class VertexArray
	new: (name) =>
		@name = name
		@descriptions = {}
		@element_array_buffer = 0

	_update_description: (trace, array_buffer, call) =>
		index = call\get_argument_by_name("index")\query!

		local description
		if @descriptions[index]
			description = @descriptions[index]
			trace\delete_call(description.call.index)
		else
			description = {}
			@descriptions[index] = description

		@descriptions[index].call = call
		@descriptions[index].array_buffer = array_buffer

	dispose: (trace) =>
		for _, description in pairs(@descriptions)
			trace\delete_call(description.call.index)

class VertexArrayFilter extends Filter
	new: =>
		@_vertex_arrays = {}
		@_current_array_buffer = 0
		@current_vertex_array = 0

	_get_vertex_array: (name) =>
		if @_vertex_arrays[name] == nil
			@_vertex_arrays[name] = VertexArray(name)
		return @_vertex_arrays[name]

	get_current_vertex_array: (name) =>
		return _get_vertex_array(@current_vertex_array)

	glBindBuffer: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		target = call\get_argument_by_name("target")\query!.value_name
		buffer = call\get_argument_by_name("buffer")\query!
		switch target
			when "GL_ELEMENT_ARRAY_BUFFER"
				if @current_vertex_array != 0
					vao = @\_get_vertex_array(@current_vertex_array)
					vao.element_array_buffer = buffer
			when "GL_ARRAY_BUFFER"
				@_current_array_buffer = buffer

		return true

	glBindVertexArray: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		@current_vertex_array = call\get_argument_by_name("array")\query!

		return false

	glVertexAttribPointer: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		vao = @\_get_vertex_array(@current_vertex_array)
		vao\_update_description(trace, @_current_array_buffer, call)

		return true

	dispose: (trace) =>
		for _, vao in pairs(@_vertex_arrays)
			vao\dispose(trace)

return VertexArrayFilter

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

Promise = require "glurg.common.Promise"
Filter = require "glurg.filters.Filter"
Call = require "glurg.trace.Call"
Trace = require "glurg.trace.Trace"

class VertexArray
	new: (name) =>
		@name = name
		@descriptions = {}
		@element_array_buffer = 0

	_update_description: (trace, array_buffer, call) =>
		index = call\get_argument_by_name("index")\query!

		local description
		if @descriptions[index]
			description = @descriptions[index]
			trace\delete_call(description.call.index)
		else
			description = {}
			@descriptions[index] = description

		@descriptions[index].call = call
		@descriptions[index].array_buffer = array_buffer

	dispose: (trace) =>
		for _, description in pairs(@descriptions)
			trace\delete_call(description.call.index)

class VertexArrayFilter extends Filter
	new: =>
		@_vertex_arrays = {}
		@_current_array_buffer = 0
		@current_vertex_array = 0

	_get_vertex_array: (name) =>
		if @_vertex_arrays[name] == nil
			@_vertex_arrays[name] = VertexArray(name)
		return @_vertex_arrays[name]

	get_current_vertex_array: (name) =>
		return _get_vertex_array(@current_vertex_array)

	glBindBuffer: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		target = call\get_argument_by_name("target")\query!.value_name
		buffer = call\get_argument_by_name("buffer")\query!
		switch target
			when "GL_ELEMENT_ARRAY_BUFFER"
				if @current_vertex_array != 0
					vao = @\_get_vertex_array(@current_vertex_array)
					vao.element_array_buffer = buffer
			when "GL_ARRAY_BUFFER"
				@_current_array_buffer = buffer

		return false

	glBindVertexArray: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		@current_vertex_array = event.call\get_argument_by_name("array")\query!

	glVertexAttribPointer: (trace, call)
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		vao = @\_get_vertex_array(@current_vertex_array)
		vao\_update_description(trace, @_current_array_buffer, call)

		return false

	dispose: (trace) =>
		for _, vao in pairs(@_vertex_arrays)
			vao\dispose(trace)

return VertexArrayFilter


insult: fix patterns

insult: fix patterns

-- a port of https://github.com/LizardGamer/tumblr-argument-generator/blob/develop/app/assets/js/main.js
-- original by Lokaltog
-- github pls no delete

util = require'util'
html2unicode = require'html'
simplehttp = util.simplehttp

intro = {
  'burn in hell',
  'check your privilege',
  'fuck you',
  'fuck off',
  'please die',
  'rot in hell',
  'screw you',
  'shut the fuck up',
  'shut up',
  'kill yourself',
  'drop dead'
}
description = {
  'deluded',
  'fucking',
  'god damn',
  'judgemental',
  'worthless'
}
marginalized = {
  {
    'activist',
    'agender',
    'appearance',
    'asian',
    'attractive',
    'bi',
    'bigender',
    'black',
    'celestial',
    'chubby',
    'closet',
    'color',
    'curvy',
    'dandy',
    'deathfat',
    'demi',
    'differently abled',
    'disabled',
    'diversity',
    'dysphoria',
    'ethnic',
    'ethnicity',
    'fat love',
    'fat',
    'fatist',
    'fatty',
    'female',
    'feminist',
    'genderfuck',
    'genderless',
    'hair',
    'height',
    'indigenous',
    'intersectionality',
    'invisible',
    'kin',
    'lesbianism',
    'little person',
    'marginalized',
    'minority',
    'multigender',
    'non-gender',
    'non-white',
    'obesity',
    'otherkin',
    'pansexual',
    'polygender',
    'privilege',
    'prosthetic',
    'queer',
    'radfem',
    'skinny',
    'smallfat',
    'stretchmark',
    'thin',
    'third-gender',
    'trans*',
    'transfat',
    'transgender',
    'transman',
    'transwoman',
    'trigger',
    'two-spirit',
    'womyn',
    'poc',
    'woc'
  },
  {
    'chauvinistic',
    'misogynistic',
    'nphobic',
    'oppressive',
    'phobic',
    'shaming',
    'denying',
    'discriminating',
    'hypersexualizing',
    'intolerant',
    'racist',
    'sexualizing'
  }
}
privileged = {
  {
    'able-bodied',
    'appearance',
    'attractive',
    'binary',
    'bi',
    'cis',
    'cisgender',
    'cishet',
    'hetero',
    'male',
    'smallfat',
    'thin',
    'white'
  },
  {
    'ableist',
    'classist',
    'normative',
    'overprivileged',
    'patriarch',
    'sexist',
    'privileged'
  }
}
finisher = {
  'asshole',
  'bigot',
  'oppressor',
  'piece of shit',
  'rapist',
  'scum',
  'shitlord',
  'subhuman',
  'misogynist',
  'nazi'
}

getRandomItem = (array) ->
  array[math.random(1, #array)]

generateTerm = ->
  getRandomItem({
    'a',
    'bi',
    'dandy',
    'demi',
    'gender',
    'multi',
    'pan',
    'poly'
  }) .. getRandomItem({
    'amorous',
    'femme',
    'fluid',
    'queer',
    'romantic',
    'sexual',
  })

generateArgument = ->
  buf = {
    getRandomItem(intro),
    ', you ',
    getRandomItem(description),
    ' ',
    getRandomItem({
      generateTerm(),
      getRandomItem(marginalized[1])
    }),
    '-',
    getRandomItem(marginalized[2]),
    ', ',
    getRandomItem(privileged[1]),
    '-',
    getRandomItem(privileged[2]),
    ' ',
    getRandomItem(finisher),
    '.',
  }
  table.concat(buf)

martinLutherInsult = (source, destination, arg) =>
  simplehttp 'http://ergofabulous.org/luther/', (data) ->
    insult = data\match '
(.-)
'
    if insult
      say arg..": "..insult

shaker = (source, destination, arg) =>
  simplehttp 'http://www.pangloss.com/seidel/Shaker/', (data) ->
    insult = data\match '%s*(.-)'
    if insult
      say arg..": "..insult

PRIVMSG:
  '^%pinsult$': (source, destination) =>
    say generateArgument()
  '^%pinsult (.+)$': (source, destination, arg) =>
    say arg..": "..generateArgument()
  '^%pminsult (.+)$': martinLutherInsult
  '^%psinsult (.+)$': shaker

-- a port of https://github.com/LizardGamer/tumblr-argument-generator/blob/develop/app/assets/js/main.js
-- original by Lokaltog
-- github pls no delete

util = require'util'
html2unicode = require'html'
simplehttp = util.simplehttp

intro = {
  'burn in hell',
  'check your privilege',
  'fuck you',
  'fuck off',
  'please die',
  'rot in hell',
  'screw you',
  'shut the fuck up',
  'shut up',
  'kill yourself',
  'drop dead'
}
description = {
  'deluded',
  'fucking',
  'god damn',
  'judgemental',
  'worthless'
}
marginalized = {
  {
    'activist',
    'agender',
    'appearance',
    'asian',
    'attractive',
    'bi',
    'bigender',
    'black',
    'celestial',
    'chubby',
    'closet',
    'color',
    'curvy',
    'dandy',
    'deathfat',
    'demi',
    'differently abled',
    'disabled',
    'diversity',
    'dysphoria',
    'ethnic',
    'ethnicity',
    'fat love',
    'fat',
    'fatist',
    'fatty',
    'female',
    'feminist',
    'genderfuck',
    'genderless',
    'hair',
    'height',
    'indigenous',
    'intersectionality',
    'invisible',
    'kin',
    'lesbianism',
    'little person',
    'marginalized',
    'minority',
    'multigender',
    'non-gender',
    'non-white',
    'obesity',
    'otherkin',
    'pansexual',
    'polygender',
    'privilege',
    'prosthetic',
    'queer',
    'radfem',
    'skinny',
    'smallfat',
    'stretchmark',
    'thin',
    'third-gender',
    'trans*',
    'transfat',
    'transgender',
    'transman',
    'transwoman',
    'trigger',
    'two-spirit',
    'womyn',
    'poc',
    'woc'
  },
  {
    'chauvinistic',
    'misogynistic',
    'nphobic',
    'oppressive',
    'phobic',
    'shaming',
    'denying',
    'discriminating',
    'hypersexualizing',
    'intolerant',
    'racist',
    'sexualizing'
  }
}
privileged = {
  {
    'able-bodied',
    'appearance',
    'attractive',
    'binary',
    'bi',
    'cis',
    'cisgender',
    'cishet',
    'hetero',
    'male',
    'smallfat',
    'thin',
    'white'
  },
  {
    'ableist',
    'classist',
    'normative',
    'overprivileged',
    'patriarch',
    'sexist',
    'privileged'
  }
}
finisher = {
  'asshole',
  'bigot',
  'oppressor',
  'piece of shit',
  'rapist',
  'scum',
  'shitlord',
  'subhuman',
  'misogynist',
  'nazi'
}

getRandomItem = (array) ->
  array[math.random(1, #array)]

generateTerm = ->
  getRandomItem({
    'a',
    'bi',
    'dandy',
    'demi',
    'gender',
    'multi',
    'pan',
    'poly'
  }) .. getRandomItem({
    'amorous',
    'femme',
    'fluid',
    'queer',
    'romantic',
    'sexual',
  })

generateArgument = ->
  buf = {
    getRandomItem(intro),
    ', you ',
    getRandomItem(description),
    ' ',
    getRandomItem({
      generateTerm(),
      getRandomItem(marginalized[1])
    }),
    '-',
    getRandomItem(marginalized[2]),
    ', ',
    getRandomItem(privileged[1]),
    '-',
    getRandomItem(privileged[2]),
    ' ',
    getRandomItem(finisher),
    '.',
  }
  table.concat(buf)

martinLutherInsult = (source, destination, arg) =>
  simplehttp 'http://ergofabulous.org/luther/', (data) ->
    insult = data\match '
(.-)
'
    if insult
      say arg..": "..insult

shaker = (source, destination, arg) =>
  simplehttp 'http://www.pangloss.com/seidel/Shaker/', (data) ->
    insult = data\match '%s*(.-)'
    if insult
      say arg..": "..insult

PRIVMSG:
  '^%pinsult$': (source, destination) =>
    say generateArgument()
  '^%pinsult (.+)$': (source, destination, arg) =>
    say arg..": "..generateArgument()
  '%pminsult (.+)$': martinLutherInsult
  '%psinsult (.+)$': shaker


prevent bad usage of PonyflyController from other addons

--
-- Copyright (C) 2017-2018 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.
--

ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow flight for pegasus and alicorns. It obeys PlayerNoClip hook.')
FORCE_ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight_force', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Ignore PlayerNoClip hook')
SUPPRESS_CLIENTSIDE_CHECK = CreateConVar('ppm2_sv_flight_nocheck', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Suppress PlayerNoClip clientside check (useful with bad coded addons. known are - ULX, Cinema, FAdmin)')
FLIGHT_DAMAGE = CreateConVar('ppm2_sv_flightdmg', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Damage players in flight')

class PonyflyController
	new: (data) =>
		@controller = data
		@ent = data.ent
		@speedMult = 0.5
		@speedMultDirections = 1.25
		@dragMult = 0.95
		@obbCenter = Vector(0, 0, 0)
		@obbMins = Vector(0, 0, 0)
		@obbMaxs = Vector(0, 0, 0)
		@speedMultDiv = 2
		@speedMultLift = 2
		@pitch = 0
		@yaw = 0
		@roll = 0
		@angleLerp = 1
		@lastVelocity = Vector(0, 0, 0)
		@lastState = false

	GetEntity: => @ent
	GetData: => @controller
	GetController: => @controller

	Switch: (status = false) =>
		@ent = @controller.ent
		return if not IsValid(@ent) or not @ent\IsPlayer()
		return if @lastState == status
		@lastState = status
		if not status
			{:p, :y, :r} = @ent\EyeAngles()
			newAng = Angle(p, y, 0)
			@ent\SetEyeAngles(newAng)
			@ent\SetMoveType(MOVETYPE_WALK)
			@roll = 0
			@pitch = 0
			@yaw = 0
			@ent\SetVelocity(@lastVelocity * 50)
			@lastVelocity = Vector(0, 0, 0)
		else
			@lastVelocity = Vector(0, 0, 0)
			@ent\SetVelocity(-@ent\GetVelocity() * .97)
			@ent\SetMoveType(MOVETYPE_CUSTOM)
			@obbCenter = @ent\OBBCenter()
			@obbMins = @ent\OBBMins()
			@obbMaxs = @ent\OBBMaxs()
			@roll = 0
			@pitch = 0
			@yaw = 0
	Think: (movedata) =>
		pos     = movedata\GetOrigin()
		ang     = movedata\GetAngles()
		fwd     = ang\Forward()
		bcwd    = -ang\Forward()
		right   = ang\Right()
		left    = -ang\Right()
		up      = ang\Up()
		down    = -ang\Up()
		W       = movedata\KeyDown(IN_FORWARD)
		S       = movedata\KeyDown(IN_BACK)
		D       = movedata\KeyDown(IN_MOVERIGHT)
		A       = movedata\KeyDown(IN_MOVELEFT)
		CTRL    = movedata\KeyDown(IN_DUCK)
		MULT    = FrameTime() * 66

		velocity = movedata\GetVelocity()
		cSpeed = velocity\Length()
		cSpeed = 1 if cSpeed < 1
		dragSqrt = math.min(math.sqrt(cSpeed) / cSpeed * 2, 0.99)
		cSpeedMult = @speedMultDiv / cSpeed
		cSpeedMult = @speedMultDiv if cSpeedMult ~= cSpeedMult
		cSpeedLiftMult = @speedMultLift / cSpeed
		cSpeedLiftMult = @speedMultLift if cSpeedLiftMult ~= cSpeedLiftMult

		dragCalc = math.Clamp(@dragMult / dragSqrt, 0, 0.99)
		pitch = 0
		yaw = 0
		roll = 0

		hit = false
		hitLift = false

		if W
			velocity += fwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch += 20

		if S
			velocity += bcwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch -= 20

		if A
			velocity += left * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll -= 20

		if D
			velocity += right * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll += 20

		if CTRL
			velocity += Vector(0, 0, -MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if @isLiftingUp
			velocity += Vector(0, 0, MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if CLIENT
			lerpMult = FrameTime() * @angleLerp
			{:p, :y, :r} = ang
			p -= @pitch
			y -= @yaw
			@pitch = Lerp(lerpMult, @pitch, pitch)
			@yaw = Lerp(lerpMult, @yaw, yaw)
			@roll = Lerp(lerpMult, @roll, roll)
			p += @pitch
			y += @yaw
			r = @roll + math.sin(RealTime()) * 2
			newAng = Angle(p, y, r)
			@ent\SetEyeAngles(newAng)

		if not hit
			velocity.x *= dragCalc
			velocity.y *= dragCalc

		if not hitLift
			velocity.z *= dragCalc
			velocity.z += math.sin(RealTime() * 2) * .01

		pos += velocity

		movedata\SetVelocity(velocity)
		movedata\SetOrigin(pos)
		return true

	SetupMove: (movedata, cmd) =>
		@isLiftingUp = movedata\KeyDown(IN_JUMP)
		if @isLiftingUp
			movedata\SetButtons(movedata\GetButtons() - IN_JUMP)
		cmd\SetButtons(cmd\GetButtons() - IN_JUMP) if cmd\KeyDown(IN_JUMP)

	FinishMove: (movedata) =>
		nativeEntity = @ent\GetEntity()
		mvPos = movedata\GetOrigin()
		pos = nativeEntity\GetPos()
		rpos = pos
		tryMove = util.TraceHull({
			filter: (ent) ->
				return false if nativeEntity == ent
				return true if not IsValid(ent)
				collision = ent\GetCollisionGroup()
				return collision ~= COLLISION_GROUP_WORLD and
					collision ~= COLLISION_GROUP_DEBRIS_TRIGGER and
					collision ~= COLLISION_GROUP_WEAPON and
					collision ~= COLLISION_GROUP_PASSABLE_DOOR and
					collision ~= COLLISION_GROUP_DEBRIS and
					ent\GetOwner() ~= nativeEntity and
					ent\GetParent() ~= nativeEntity and
					ent\IsSolid()
			mins: @obbMins
			maxs: @obbMaxs
			start: rpos
			endpos: mvPos
		})

		velocity = movedata\GetVelocity()
		newVelocity = velocity
		length = velocity\Length()

		if not tryMove.Hit
			nativeEntity\SetPos(mvPos)
		else
			if IsValid(tryMove.Entity)
				newVelocity = Vector(0, 0, 0)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			else
				newVelocity = velocity - tryMove.HitNormal * velocity\Dot(tryMove.HitNormal * 1.1)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			if length > 7 and SERVER and FLIGHT_DAMAGE\GetBool()
				dmgInfo = DamageInfo()
				dmgInfo\SetAttacker(nativeEntity)
				dmgInfo\SetInflictor(nativeEntity)
				dmgInfo\SetDamageType(DMG_CRUSH)
				calcDamage = math.Clamp((length / 4) ^ 2, 1, 100)
				if calcDamage >= 100
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 70
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 50
					nativeEntity\EmitSound("physics/body/body_medium_break#{math.random(2, 4)}.wav", 75)
				elseif calcDamage > 25
					nativeEntity\EmitSound("physics/body/body_medium_impact_hard#{math.random(1, 6)}.wav", 75)
				elseif calcDamage > 10
					nativeEntity\EmitSound("physics/body/body_medium_impact_soft#{math.random(1, 7)}.wav", 75)
				else
					nativeEntity\EmitSound("physics/flesh/flesh_impact_bullet#{math.random(1, 5)}.wav", 75)
				dmgInfo\SetDamage(calcDamage)
				nativeEntity\TakeDamageInfo(dmgInfo)
		@lastVelocity = newVelocity

PPM2.PonyflyController = PonyflyController

import IsPonyCached, GetPonyData, GetTable, SetIK, IsNewPonyCached from FindMetaTable('Entity')
import AnimRestartGesture, AnimResetGestureSlot from FindMetaTable('Player')

hook.Add 'SetupMove', 'PPM2.Ponyfly', (movedata, cmd) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\SetupMove(movedata, cmd)

hook.Add 'Move', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\Think(movedata)

hook.Add 'FinishMove', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\FinishMove(movedata)

hook.Add 'CalcMainActivity', 'PPM2.Ponyfly', (movedata) =>
	return if not IsNewPonyCached(@)
	if data = GetPonyData(@)
		if data\GetFly()
			if not @isPlayingPPM2Anim
				@isPlayingPPM2Anim = true
				AnimRestartGesture(@, GESTURE_SLOT_CUSTOM, ACT_GMOD_NOCLIP_LAYER, false)
				SetIK(@, false) if CLIENT
			return ACT_GMOD_NOCLIP_LAYER, 370
		else
			if @isPlayingPPM2Anim
				@isPlayingPPM2Anim = false
				AnimResetGestureSlot(@, GESTURE_SLOT_CUSTOM)
				SetIK(@, true) if CLIENT

if SERVER
	concommand.Add 'ppm2_fly', =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not IsValid(@)
		return if not @IsPonyCached()
		data = @GetPonyData()
		return if not data
		if data\GetFly()
			return data\SetFly(false) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, false) or hook.Run('PPM2Fly', @, false)
			data\SetFly(false) if can
		else
			return if data\GetRace() ~= PPM2.RACE_PEGASUS and data\GetRace() ~= PPM2.RACE_ALICORN
			return data\SetFly(true) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, true) or hook.Run('PPM2Fly', @, true)
			data\SetFly(true) if can
else
	lastDouble = 0
	lastMessage = 0
	lastMessage2 = 0
	hook.Add 'PlayerBindPress', 'PPM2.Ponyfly', (bind = '', pressed = false) =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not pressed
		return if bind ~= '+jump' and bind ~= 'jump'
		if lastDouble > RealTime()
			return if not @IsPonyCached()
			data = @GetPonyData()
			return if not data
			if data\GetRace() ~= PPM2.RACE_PEGASUS and data\GetRace() ~= PPM2.RACE_ALICORN
				if lastMessage < RealTime()
					lastMessage = RealTime() + 1
					PPM2.ChatPrint('You need to be a Pegasus or an Alicorn to fly!')
				return
			if not FORCE_ALLOW_FLIGHT\GetBool() and not SUPPRESS_CLIENTSIDE_CHECK\GetBool()
				can = hook.Run('PlayerNoClip', @, not data\GetFly()) or hook.Run('PPM2Fly', @, not data\GetFly())
				if not can
					if lastMessage2 < RealTime()
						lastMessage2 = RealTime() + 1
						PPM2.ChatPrint("You can not #{data\GetFly() and 'land' or 'fly'} right now.")
					return
			RunConsoleCommand('ppm2_fly')
			lastDouble = 0
			return
		lastDouble = RealTime() + 0.2

--
-- Copyright (C) 2017-2018 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.
--

ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow flight for pegasus and alicorns. It obeys PlayerNoClip hook.')
FORCE_ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight_force', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Ignore PlayerNoClip hook')
SUPPRESS_CLIENTSIDE_CHECK = CreateConVar('ppm2_sv_flight_nocheck', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Suppress PlayerNoClip clientside check (useful with bad coded addons. known are - ULX, Cinema, FAdmin)')
FLIGHT_DAMAGE = CreateConVar('ppm2_sv_flightdmg', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Damage players in flight')

class PonyflyController
	new: (data) =>
		@controller = data
		@ent = data.ent
		@speedMult = 0.5
		@speedMultDirections = 1.25
		@dragMult = 0.95
		@obbCenter = Vector(0, 0, 0)
		@obbMins = Vector(0, 0, 0)
		@obbMaxs = Vector(0, 0, 0)
		@speedMultDiv = 2
		@speedMultLift = 2
		@pitch = 0
		@yaw = 0
		@roll = 0
		@angleLerp = 1
		@lastVelocity = Vector(0, 0, 0)
		@lastState = false

	GetEntity: => @ent
	GetData: => @controller
	GetController: => @controller

	Switch: (status = false) =>
		@ent = @controller.ent
		return if not IsValid(@ent)
		return if @lastState == status
		@lastState = status
		if not status
			{:p, :y, :r} = @ent\EyeAngles()
			newAng = Angle(p, y, 0)
			@ent\SetEyeAngles(newAng)
			@ent\SetMoveType(MOVETYPE_WALK)
			@roll = 0
			@pitch = 0
			@yaw = 0
			@ent\SetVelocity(@lastVelocity * 50)
			@lastVelocity = Vector(0, 0, 0)
		else
			@lastVelocity = Vector(0, 0, 0)
			@ent\SetVelocity(-@ent\GetVelocity() * .97)
			@ent\SetMoveType(MOVETYPE_CUSTOM)
			@obbCenter = @ent\OBBCenter()
			@obbMins = @ent\OBBMins()
			@obbMaxs = @ent\OBBMaxs()
			@roll = 0
			@pitch = 0
			@yaw = 0
	Think: (movedata) =>
		pos     = movedata\GetOrigin()
		ang     = movedata\GetAngles()
		fwd     = ang\Forward()
		bcwd    = -ang\Forward()
		right   = ang\Right()
		left    = -ang\Right()
		up      = ang\Up()
		down    = -ang\Up()
		W       = movedata\KeyDown(IN_FORWARD)
		S       = movedata\KeyDown(IN_BACK)
		D       = movedata\KeyDown(IN_MOVERIGHT)
		A       = movedata\KeyDown(IN_MOVELEFT)
		CTRL    = movedata\KeyDown(IN_DUCK)
		MULT    = FrameTime() * 66

		velocity = movedata\GetVelocity()
		cSpeed = velocity\Length()
		cSpeed = 1 if cSpeed < 1
		dragSqrt = math.min(math.sqrt(cSpeed) / cSpeed * 2, 0.99)
		cSpeedMult = @speedMultDiv / cSpeed
		cSpeedMult = @speedMultDiv if cSpeedMult ~= cSpeedMult
		cSpeedLiftMult = @speedMultLift / cSpeed
		cSpeedLiftMult = @speedMultLift if cSpeedLiftMult ~= cSpeedLiftMult

		dragCalc = math.Clamp(@dragMult / dragSqrt, 0, 0.99)
		pitch = 0
		yaw = 0
		roll = 0

		hit = false
		hitLift = false

		if W
			velocity += fwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch += 20

		if S
			velocity += bcwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch -= 20

		if A
			velocity += left * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll -= 20

		if D
			velocity += right * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll += 20

		if CTRL
			velocity += Vector(0, 0, -MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if @isLiftingUp
			velocity += Vector(0, 0, MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if CLIENT
			lerpMult = FrameTime() * @angleLerp
			{:p, :y, :r} = ang
			p -= @pitch
			y -= @yaw
			@pitch = Lerp(lerpMult, @pitch, pitch)
			@yaw = Lerp(lerpMult, @yaw, yaw)
			@roll = Lerp(lerpMult, @roll, roll)
			p += @pitch
			y += @yaw
			r = @roll + math.sin(RealTime()) * 2
			newAng = Angle(p, y, r)
			@ent\SetEyeAngles(newAng)

		if not hit
			velocity.x *= dragCalc
			velocity.y *= dragCalc

		if not hitLift
			velocity.z *= dragCalc
			velocity.z += math.sin(RealTime() * 2) * .01

		pos += velocity

		movedata\SetVelocity(velocity)
		movedata\SetOrigin(pos)
		return true

	SetupMove: (movedata, cmd) =>
		@isLiftingUp = movedata\KeyDown(IN_JUMP)
		if @isLiftingUp
			movedata\SetButtons(movedata\GetButtons() - IN_JUMP)
		cmd\SetButtons(cmd\GetButtons() - IN_JUMP) if cmd\KeyDown(IN_JUMP)

	FinishMove: (movedata) =>
		nativeEntity = @ent\GetEntity()
		mvPos = movedata\GetOrigin()
		pos = nativeEntity\GetPos()
		rpos = pos
		tryMove = util.TraceHull({
			filter: (ent) ->
				return false if nativeEntity == ent
				return true if not IsValid(ent)
				collision = ent\GetCollisionGroup()
				return collision ~= COLLISION_GROUP_WORLD and
					collision ~= COLLISION_GROUP_DEBRIS_TRIGGER and
					collision ~= COLLISION_GROUP_WEAPON and
					collision ~= COLLISION_GROUP_PASSABLE_DOOR and
					collision ~= COLLISION_GROUP_DEBRIS and
					ent\GetOwner() ~= nativeEntity and
					ent\GetParent() ~= nativeEntity and
					ent\IsSolid()
			mins: @obbMins
			maxs: @obbMaxs
			start: rpos
			endpos: mvPos
		})

		velocity = movedata\GetVelocity()
		newVelocity = velocity
		length = velocity\Length()

		if not tryMove.Hit
			nativeEntity\SetPos(mvPos)
		else
			if IsValid(tryMove.Entity)
				newVelocity = Vector(0, 0, 0)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			else
				newVelocity = velocity - tryMove.HitNormal * velocity\Dot(tryMove.HitNormal * 1.1)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			if length > 7 and SERVER and FLIGHT_DAMAGE\GetBool()
				dmgInfo = DamageInfo()
				dmgInfo\SetAttacker(nativeEntity)
				dmgInfo\SetInflictor(nativeEntity)
				dmgInfo\SetDamageType(DMG_CRUSH)
				calcDamage = math.Clamp((length / 4) ^ 2, 1, 100)
				if calcDamage >= 100
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 70
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 50
					nativeEntity\EmitSound("physics/body/body_medium_break#{math.random(2, 4)}.wav", 75)
				elseif calcDamage > 25
					nativeEntity\EmitSound("physics/body/body_medium_impact_hard#{math.random(1, 6)}.wav", 75)
				elseif calcDamage > 10
					nativeEntity\EmitSound("physics/body/body_medium_impact_soft#{math.random(1, 7)}.wav", 75)
				else
					nativeEntity\EmitSound("physics/flesh/flesh_impact_bullet#{math.random(1, 5)}.wav", 75)
				dmgInfo\SetDamage(calcDamage)
				nativeEntity\TakeDamageInfo(dmgInfo)
		@lastVelocity = newVelocity

PPM2.PonyflyController = PonyflyController

import IsPonyCached, GetPonyData, GetTable, SetIK, IsNewPonyCached from FindMetaTable('Entity')
import AnimRestartGesture, AnimResetGestureSlot from FindMetaTable('Player')

hook.Add 'SetupMove', 'PPM2.Ponyfly', (movedata, cmd) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\SetupMove(movedata, cmd)

hook.Add 'Move', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\Think(movedata)

hook.Add 'FinishMove', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\FinishMove(movedata)

hook.Add 'CalcMainActivity', 'PPM2.Ponyfly', (movedata) =>
	return if not IsNewPonyCached(@)
	if data = GetPonyData(@)
		if data\GetFly()
			if not @isPlayingPPM2Anim
				@isPlayingPPM2Anim = true
				AnimRestartGesture(@, GESTURE_SLOT_CUSTOM, ACT_GMOD_NOCLIP_LAYER, false)
				SetIK(@, false) if CLIENT
			return ACT_GMOD_NOCLIP_LAYER, 370
		else
			if @isPlayingPPM2Anim
				@isPlayingPPM2Anim = false
				AnimResetGestureSlot(@, GESTURE_SLOT_CUSTOM)
				SetIK(@, true) if CLIENT

if SERVER
	concommand.Add 'ppm2_fly', =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not IsValid(@)
		return if not @IsPonyCached()
		data = @GetPonyData()
		return if not data
		if data\GetFly()
			return data\SetFly(false) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, false) or hook.Run('PPM2Fly', @, false)
			data\SetFly(false) if can
		else
			return if data\GetRace() ~= PPM2.RACE_PEGASUS and data\GetRace() ~= PPM2.RACE_ALICORN
			return data\SetFly(true) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, true) or hook.Run('PPM2Fly', @, true)
			data\SetFly(true) if can
else
	lastDouble = 0
	lastMessage = 0
	lastMessage2 = 0
	hook.Add 'PlayerBindPress', 'PPM2.Ponyfly', (bind = '', pressed = false) =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not pressed
		return if bind ~= '+jump' and bind ~= 'jump'
		if lastDouble > RealTime()
			return if not @IsPonyCached()
			data = @GetPonyData()
			return if not data
			if data\GetRace() ~= PPM2.RACE_PEGASUS and data\GetRace() ~= PPM2.RACE_ALICORN
				if lastMessage < RealTime()
					lastMessage = RealTime() + 1
					PPM2.ChatPrint('You need to be a Pegasus or an Alicorn to fly!')
				return
			if not FORCE_ALLOW_FLIGHT\GetBool() and not SUPPRESS_CLIENTSIDE_CHECK\GetBool()
				can = hook.Run('PlayerNoClip', @, not data\GetFly()) or hook.Run('PPM2Fly', @, not data\GetFly())
				if not can
					if lastMessage2 < RealTime()
						lastMessage2 = RealTime() + 1
						PPM2.ChatPrint("You can not #{data\GetFly() and 'land' or 'fly'} right now.")
					return
			RunConsoleCommand('ppm2_fly')
			lastDouble = 0
			return
		lastDouble = RealTime() + 0.2


plugins/data: fix syntax

plugins/data: fix syntax

import IRCClient from require 'irc'

unpack = unpack or table.unpack

IRCClient\add_hook 'CONNECT', =>
	@channels = {}
	@users    = {}
	@server   = {
		caps:       {}
		ircv3_caps: {}
		batches:    {}
	}

IRCClient\add_hook 'CONNECT', =>
	-- Welcome
	@data = {} if not @data
	@data.last_connect = os.time()
	@send_raw 'CAP LS 302'

IRCClient\add_handler 'BATCH', (prefix, args, tags)=>
	-- ::TODO:: add in hook system for BATCH
	tag_type, tag = args[1]\match '(.)(.+)'
	if tag_type == '+'
		@server.batches[tag] = {unpack(args, 2)}
	elseif tag_type == '-'
		@server.batches[tag] = nil

IRCClient\add_handler '005', (prefix, args)=>
	-- Capabilities
	isupport_caps = {select 2, unpack args}
	for cap in *isupport_caps
		if cap\sub(1, 1) == "-"
			-- remove support
			if cap\find "=" then
				@server.caps[cap\match "^%-(.+)="] = nil
			else
				@server.caps[cap\sub 2] = nil
		elseif cap\find "="
			key, value = cap\match '^(.-)=(.+)'
			@server.caps[key] = value
		else
			@server.caps[cap] = true

IRCClient\add_handler 'AWAY', (prefix, args)=>
	{:nick} = prefix
	@users[nick].away = args[#args]

IRCClient\add_handler 'ACCOUNT', (prefix, args)=>
	@users[nick].account = args[1] != "*" and args[1] or nil

IRCClient\add_handler 'RENAME', (prefix, args)=>
	{old, new} = args
	for _, user in pairs @channels[old].users
		user.channels[new] = user.channels[old]
		user.channels[old] = nil
	@channels[new] = @channels[old]
	@channels[old] = nil

IRCClient\add_handler 'JOIN', (prefix, args, tags={})=>
	-- user JOINs a channel
	local channel
	local account
	if @server.ircv3_caps['extended-join']
		account = args[2] if args[2] != '*'
		channel = args[1]
	elseif @server.ircv3_caps['account-tag'] and tags.account
		account = tags.account
		channel = args[1]
	else
		channel = args[1]
	{:nick, :user, :host} = prefix
	if host
		if not @users[nick] then
			@users[nick] = {
				account: account
				channels: {
					[channel]: {
						status: ""
					}
				},
				:user,
				:host
			}
		else
			if not @users[nick].channels
				@users[nick].channels = {
					[channel]: {
						status: ""
					}
				}
			else
				@users[nick].channels[channel] = status: ""
		@users[nick].account = account if account
	if not @channels[channel]
		if @server.caps['WHOX']
			@send_raw "WHO #{channel} %nat,001"
		elseif @server.ircv3_caps['userhost-in-names']
			@send_raw ('NAMES %s')\format channel
		else
			@send_raw ('WHO %s')\format channel

		@channels[channel] = {
			users: {
				[nick]: @users[nick]
			}
		}
	else
		@channels[channel].users[nick] = @users[nick]

IRCClient\add_hook 'WHOX_001', (nick, account)=>
	@users[nick].account = account if @users[nick] and account ~= '0'

IRCClient\add_handler 'NICK', (prefix, args)=>
	old = prefix.nick
	new = args[1]
	for channel_name in pairs @users[old].channels
		@channels[channel_name].users[new] = @channels[channel_name].users[old]
		@channels[channel_name].users[old] = nil
	@users[new] = @users[old]
	@users[old] = nil

IRCClient\add_handler 'MODE', (prefix, args)=>
	-- User or bot called /mode
	if args[1] and args[1]\sub(1,1) == "#"
		@send_raw ('NAMES %s')\format args[1]

IRCClient\add_handler '353', (prefix, args)=>
	-- Result of NAMES
	channel = args[3]
	statuses = @server.caps.PREFIX and @server.caps.PREFIX\match '%(.-%)(.+)' or "+@"
	statuses = "[" .. statuses\gsub("%p", "%%%1") .. "]"
	for text in args[#args]\gmatch '%S+'
		local status, pre, nick, user, host
		if text\match statuses
			status, pre = text\match ('^(%s+)(.+)')\format statuses
		else
			status, pre = '', text
		if @server.ircv3_caps['userhost-in-names']
			nick, user, host = pre\match '^(.-)!(.-)@(.-)$'
		else
			nick = pre
		if not @users[nick]
			@users[nick] = {channels: {}}
		@users[nick].user = user if user
		@users[nick].host = host if host
		if @channels[channel].users[nick]
			if @users[nick].channels[channel]
				@users[nick].channels[channel].status = status
			else
				@users[nick].channels[channel] = :status
		else
			@channels[channel].users[nick] = @users[nick]
			@users[nick].channels[channel] = :status

IRCClient\add_handler '352', (prefix, args)=>
	_, user, host, _, nick, away = unpack args
	@users[nick] = {channels: {}} if not @users[nick]
	@users[nick].user = user
	@users[nick].host = host
	@users[nick].away = away\sub(1, 1) == "G"

IRCClient\add_handler 'CHGHOST', (prefix, args)=>
	{:nick} = prefix
	@users[nick].user = args[1]
	@users[nick].host = args[2]

IRCClient\add_handler 'KICK', (prefix, args)=>
	channel = args[1]
	nick = args[2]
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil

IRCClient\add_handler 'PART', (prefix, args)=>
	-- User or bot parted channel, clear from lists
	channel = args[1]
	{:nick} = prefix
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil -- User left network, garbagecollect

IRCClient\add_handler 'QUIT', (prefix, args)=>
	-- User or bot parted network, nuke from lists
	{:nick} = prefix
	for channel in pairs @users[nick].channels
		@channels[channel].users[nick] = nil
	@users[nick] = nil

import IRCClient from require 'irc'

unpack = unpack or table.unpack

IRCClient\add_hook 'CONNECT', =>
	@channels = {}
	@users    = {}
	@server   = {
		caps:       {}
		ircv3_caps: {}
		batches:    {}
	}

IRCClient\add_hook 'CONNECT', =>
	-- Welcome
	@data = {} if not @data
	@data.last_connect = os.time()
	@send_raw 'CAP LS 302'

IRCClient\add_handler 'BATCH', (prefix, args, tags)=>
	-- ::TODO:: add in hook system for BATCH
	tag_type, tag = args[1]\match '(.)(.+)'
	if tag_type == '+'
		@server.batches[tag] = {unpack(args, 2)}
	elseif tag_type == '-'
		@server.batches[tag] = nil

IRCClient\add_handler '005', (prefix, args)=>
	-- Capabilities
	isupport_caps = {select 2, unpack args}
	for cap in *isupport_caps
		if cap\sub(1, 1) == "-"
			-- remove support
			if cap\find "=" then
				@server.caps[cap\match "^%-(.+)="] = nil
			else
				@server.caps[cap\sub 2] = nil
		elseif cap\find "="
			key, value = cap\match '^(.-)=(.+)'
			@server.caps[key] = value
		else
			@server.caps[cap] = true

IRCClient\add_handler 'AWAY', (prefix, args)=>
	{:nick} = prefix
	@users[nick].away = args[#args]

IRCClient\add_handler 'ACCOUNT', (prefix, args)=>
	@users[nick].account = args[1] != "*" and args[1] or nil

IRCClient\add_handler 'RENAME', (prefix, args)=>
	{old, new} = args
	for _, user in pairs @channels[old].users
		user.channels[new] = user.channels[old]
		user.channels[old] = nil
	@channels[new] = @channels[old]
	@channels[old] = nil

IRCClient\add_handler 'JOIN', (prefix, args, tags={})=>
	-- user JOINs a channel
	local channel
	local account
	if @server.ircv3_caps['extended-join']
		account = args[2] if args[2] != '*'
		channel = args[1]
	elseif @server.ircv3_caps['account-tag'] and tags.account
		account = tags.account
		channel = args[1]
	else
		channel = args[1]
	{:nick, :user, :host} = prefix
	if host
		if not @users[nick] then
			@users[nick] = {
				account: account
				channels: {
					[channel]: {
						status: ""
					}
				},
				:user,
				:host
			}
		else
			if not @users[nick].channels
				@users[nick].channels = {
					[channel]: {
						status: ""
					}
				}
			else
				@users[nick].channels[channel] = status: ""
		@users[nick].account = account if account
	if not @channels[channel]
		if @server.caps['WHOX']
			@send_raw "WHO #{channel} %nat,001"
		else @server.ircv3_caps['userhost-in-names']
			@send_raw ('NAMES %s')\format channel
		else
			@send_raw ('WHO %s')\format channel

		@channels[channel] = {
			users: {
				[nick]: @users[nick]
			}
		}
	else
		@channels[channel].users[nick] = @users[nick]

IRCClient\add_hook 'WHOX_001', (nick, account)=>
	@users[nick].account = account if @users[nick] and account ~= '0'

IRCClient\add_handler 'NICK', (prefix, args)=>
	old = prefix.nick
	new = args[1]
	for channel_name in pairs @users[old].channels
		@channels[channel_name].users[new] = @channels[channel_name].users[old]
		@channels[channel_name].users[old] = nil
	@users[new] = @users[old]
	@users[old] = nil

IRCClient\add_handler 'MODE', (prefix, args)=>
	-- User or bot called /mode
	if args[1] and args[1]\sub(1,1) == "#"
		@send_raw ('NAMES %s')\format args[1]

IRCClient\add_handler '353', (prefix, args)=>
	-- Result of NAMES
	channel = args[3]
	statuses = @server.caps.PREFIX and @server.caps.PREFIX\match '%(.-%)(.+)' or "+@"
	statuses = "[" .. statuses\gsub("%p", "%%%1") .. "]"
	for text in args[#args]\gmatch '%S+'
		local status, pre, nick, user, host
		if text\match statuses
			status, pre = text\match ('^(%s+)(.+)')\format statuses
		else
			status, pre = '', text
		if @server.ircv3_caps['userhost-in-names']
			nick, user, host = pre\match '^(.-)!(.-)@(.-)$'
		else
			nick = pre
		if not @users[nick]
			@users[nick] = {channels: {}}
		@users[nick].user = user if user
		@users[nick].host = host if host
		if @channels[channel].users[nick]
			if @users[nick].channels[channel]
				@users[nick].channels[channel].status = status
			else
				@users[nick].channels[channel] = :status
		else
			@channels[channel].users[nick] = @users[nick]
			@users[nick].channels[channel] = :status

IRCClient\add_handler '352', (prefix, args)=>
	_, user, host, _, nick, away = unpack args
	@users[nick] = {channels: {}} if not @users[nick]
	@users[nick].user = user
	@users[nick].host = host
	@users[nick].away = away\sub(1, 1) == "G"

IRCClient\add_handler 'CHGHOST', (prefix, args)=>
	{:nick} = prefix
	@users[nick].user = args[1]
	@users[nick].host = args[2]

IRCClient\add_handler 'KICK', (prefix, args)=>
	channel = args[1]
	nick = args[2]
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil

IRCClient\add_handler 'PART', (prefix, args)=>
	-- User or bot parted channel, clear from lists
	channel = args[1]
	{:nick} = prefix
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil -- User left network, garbagecollect

IRCClient\add_handler 'QUIT', (prefix, args)=>
	-- User or bot parted network, nuke from lists
	{:nick} = prefix
	for channel in pairs @users[nick].channels
		@channels[channel].users[nick] = nil
	@users[nick] = nil


Cleanup.

Cleanup.

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

import insert from table

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

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

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) ->
	for k, v in pairs structure
		func(v, map_listing)

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

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')
			if mode == 'directory'
				f = rex.gsub(file, regex, '')\match("^%s*(.-)%s*$")
				f = parent.out .. '/' .. f
				lfs.mkdir(f)
				process_files(data, regex, {in: parent.in .. '/' .. file, out: f})
			else
				ext = file_ext(file)
				newer_f = rex.gsub(file, regex, '')
				new_f = rex.match(newer_f, [[(.*)(v\d)]]) or newer_f
				new_f = new_f\gsub('[ ]', '')
				new_f = rex.gsub(new_f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
				old_f, new_f = parent.in .. '/' .. file, parent.out .. '/' .. new_f .. ext
				lfs.link(old_f, new_f) if not debug
				print "[D: " .. ((debug and "T") or "F") .. "] Link: " .. old_f .. " to: " .. new_f

		else
			print "ERR: ", file, mode

cli\set_name("regnamex.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories")
cli\add_flag("-t, --type", "action to take: cp, mv, symlink, defaults to symlink", "symlink")
cli\add_flag("-v, --version", "prints the program version")
cli\add_flag("-d, --debug", "script will simulate excution, print actions.")
args = cli\parse_args()
return if not args

ignored_files = switch type(args["IGNORED"])
	when "string"
		{}
	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 concat, insert from table

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

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

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) ->
	for k, v in pairs structure
		func(v, map_listing)

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

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')
			if mode == 'directory'
				f = rex.gsub(file, regex, '')\match("^%s*(.-)%s*$")
				f = parent.out .. '/' .. f
				lfs.mkdir(f)
				process_files(data, regex, {in: parent.in .. '/' .. file, out: f})
			else
				ext = file_ext(file)
				newer_f = rex.gsub(file, regex, '')
				new_f = rex.match(newer_f, [[(.*)(v\d)]]) or newer_f
				new_f = new_f\gsub('[ ]', '')
				new_f = rex.gsub(new_f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
				old_f, new_f = parent.in .. '/' .. file, parent.out .. '/' .. new_f .. ext
				lfs.link(old_f, new_f) if not debug
				print "[D: " .. ((debug and "T") or "F") .. "] Link: " .. old_f .. " to: " .. new_f

		else
			print "ERR: ", file, mode

cli\set_name("regnamex.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories")
cli\add_flag("-t, --type", "action to take: cp, mv, symlink, defaults to symlink", "symlink")
cli\add_flag("-v, --version", "prints the program version")
cli\add_flag("-d, --debug", "script will simulate excution, print actions.")
args = cli\parse_args()
return if not args

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

export inputdir = args["DIR"]
export target = 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: target})
--print_listing(processed)