Fixed old models are not working

Fixed old models are not working

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

-- 0	LrigPelvis
-- 1	LrigSpine1
-- 2	LrigSpine2
-- 3	LrigRibcage
-- 4	LrigNeck1
-- 5	LrigNeck2
-- 6	LrigNeck3
-- 7	LrigScull
-- 8	Lrig_LEG_BL_Femur
-- 9	Lrig_LEG_BL_Tibia
-- 10	Lrig_LEG_BL_LargeCannon
-- 11	Lrig_LEG_BL_PhalanxPrima
-- 12	Lrig_LEG_BL_RearHoof
-- 13	Lrig_LEG_BR_Femur
-- 14	Lrig_LEG_BR_Tibia
-- 15	Lrig_LEG_BR_LargeCannon
-- 16	Lrig_LEG_BR_PhalanxPrima
-- 17	Lrig_LEG_BR_RearHoof
-- 18	Lrig_LEG_FL_Scapula
-- 19	Lrig_LEG_FL_Humerus
-- 20	Lrig_LEG_FL_Radius
-- 21	Lrig_LEG_FL_Metacarpus
-- 22	Lrig_LEG_FL_PhalangesManus
-- 23	Lrig_LEG_FL_FrontHoof
-- 24	Lrig_LEG_FR_Scapula
-- 25	Lrig_LEG_FR_Humerus
-- 26	Lrig_LEG_FR_Radius
-- 27	Lrig_LEG_FR_Metacarpus
-- 28	Lrig_LEG_FR_PhalangesManus
-- 29	Lrig_LEG_FR_FrontHoof
-- 30	Mane01
-- 31	Mane02
-- 32	Mane03
-- 33	Mane04
-- 34	Mane05
-- 35	Mane06
-- 36	Mane07
-- 37	Mane03_tip
-- 38	Tail01
-- 39	Tail02
-- 40	Tail03

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

    @NEXT_OBJ_ID = 0

    @COOLDOWN_TIME = 5
    @COOLDOWN_MAX_COUNT = 4

    @BONE_TAIL_1 = 38
    @BONE_TAIL_2 = 39
    @BONE_TAIL_3 = 40

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @SocksModelUpdateCooldown = 0
        @SocksModelUpdateCount = 0
        PPM2.DebugPrint('Created new bodygroups controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)

    UpdateCooldowns: =>
        if CLIENT
            @SocksModelUpdateCooldown = 0
            @SocksModelUpdateCount = 0
        rTime = RealTime()
        if @SocksModelUpdateCooldown < rTime
            @SocksModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @SocksModelUpdateCount = 0

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    CreateSocksModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @socksModel if IsValid(@socksModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isSocks and ent.manePlayer == @ent
                @socksModel = ent
                @GetData()\SetSocksModel(@socksModel)
                PPM2.DebugPrint('Resuing ', @socksModel, ' as socks model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @SocksModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @SocksModelUpdateCount += 1

        model = 'models/props_pony/ppm/cosmetics/ppm_socks.mdl'

        @socksModel = ents.Create('prop_dynamic') if SERVER
        @socksModel = ClientsideModel(model) if CLIENT
        with @socksModel
            .isPonyPropModel = true
            .isSocks = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new socks model for ', @ent, ' as ', @socksModel)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@socksModel)
                @GetData()\SetSocksModel(@socksModel)
                @ent\SetNWEntity('PPM2.SocksModel', @socksModel) if IsValid(@ent)
        else
            @GetData()\SetSocksModel(@socksModel)

        return @socksModel
    CreateSocksModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        return NULL if not IsValid(@socksModel)
        @socksModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @socksModel\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        @GetData()\SetSocksModel(@socksModel)
        return @socksModel

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        return unless IsValid(targetEnt)
        socks = @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
        if IsValid(socks)
            socks\SetParent(targetEnt)
            socks\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    GetSocks: => @socksModel or NULL

    ApplyRace: =>
        return unless @isValid
        return NULL if not IsValid(@ent)
        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)

    ResetTail: =>
        return if not CLIENT
        @ent\ManipulateBoneScale(@@BONE_TAIL_1, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_2, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, Vector(1, 1, 1))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_1, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_2, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_3, Angle(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_1, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, Vector(0, 0, 0))
    ResetBodygroups: =>
        return unless @isValid
        return unless IsValid(@ent)
        return unless @ent\GetBodyGroups()
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
        @ResetTail()
    Reset: => @ResetBodygroups()
    RemoveModels: =>
        @socksModel\Remove() if IsValid(@socksModel)

    UpdateTailSize: =>
        return if not CLIENT
        size = @GetData()\GetTailSize() * @GetData()\GetPonySize()
        vec = Vector(1, 1, 1)
        vecTail = vec * size
        vecTailPos = Vector((size - 1) * 8, 0, 0)

        @ent\ManipulateBoneScale(@@BONE_TAIL_2, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_1, vecTail)

        --@ent\ManipulateBonePosition(@@BONE_TAIL_1, vecTail)
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, vecTailPos)
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, vecTailPos)

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @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())
        @UpdateTailSize()
        @ApplyRace()
        @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return if not @ent\IsPony()
        @SlowUpdate(createModels)

    Remove: =>
        @RemoveModels()
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        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 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
            when 'Race'
                @ApplyRace()

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

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

    ApplyRace: =>
        return unless @isValid
        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)

--
-- 0	LrigPelvis
-- 1	Lrig_LEG_BL_Femur
-- 2	Lrig_LEG_BL_Tibia
-- 3	Lrig_LEG_BL_LargeCannon
-- 4	Lrig_LEG_BL_PhalanxPrima
-- 5	Lrig_LEG_BL_RearHoof
-- 6	Lrig_LEG_BR_Femur
-- 7	Lrig_LEG_BR_Tibia
-- 8	Lrig_LEG_BR_LargeCannon
-- 9	Lrig_LEG_BR_PhalanxPrima
-- 10	Lrig_LEG_BR_RearHoof
-- 11	LrigSpine1
-- 12	LrigSpine2
-- 13	LrigRibcage
-- 14	Lrig_LEG_FL_Scapula
-- 15	Lrig_LEG_FL_Humerus
-- 16	Lrig_LEG_FL_Radius
-- 17	Lrig_LEG_FL_Metacarpus
-- 18	Lrig_LEG_FL_PhalangesManus
-- 19	Lrig_LEG_FL_FrontHoof
-- 20	Lrig_LEG_FR_Scapula
-- 21	Lrig_LEG_FR_Humerus
-- 22	Lrig_LEG_FR_Radius
-- 23	Lrig_LEG_FR_Metacarpus
-- 24	Lrig_LEG_FR_PhalangesManus
-- 25	Lrig_LEG_FR_FrontHoof
-- 26	LrigNeck1
-- 27	LrigNeck2
-- 28	LrigNeck3
-- 29	LrigScull
-- 30	Jaw
-- 31	Ear_L
-- 32	Ear_R
-- 33	Mane02
-- 34	Mane03
-- 35	Mane03_tip
-- 36	Mane04
-- 37	Mane05
-- 38	Mane06
-- 39	Mane07
-- 40	Mane01
-- 41	Lrigweaponbone
-- 42	Tail01
-- 43	Tail02
-- 44	Tail03
--

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

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

    @BONE_TAIL_1 = 42
    @BONE_TAIL_2 = 43
    @BONE_TAIL_3 = 44

    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    new: (...) =>
        super(...)
        @UpperManeModelUpdateCooldown = 0
        @UpperManeModelUpdateCount = 0
        @LowerManeModelUpdateCooldown = 0
        @LowerManeModelUpdateCount = 0
        @TailModelUpdateCooldown = 0
        @TailModelUpdateCount = 0

    UpdateCooldowns: =>
        super()
        if CLIENT
            @UpperManeModelUpdateCooldown = 0
            @UpperManeModelUpdateCount = 0
            @LowerManeModelUpdateCooldown = 0
            @LowerManeModelUpdateCount = 0
            @TailModelUpdateCooldown = 0
            @TailModelUpdateCount = 0
        rTime = RealTime()
        if @UpperManeModelUpdateCooldown < rTime
            @UpperManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @UpperManeModelUpdateCount = 0
        if @LowerManeModelUpdateCooldown < rTime
            @LowerManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @LowerManeModelUpdateCount = 0
        if @TailModelUpdateCooldown < rTime
            @TailModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @TailModelUpdateCount = 0

    CreateUpperManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelUP if IsValid(@maneModelUP)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.upperMane and ent.manePlayer == @ent
                @maneModelUP = ent
                @GetData()\SetUpperManeModel(@maneModelUP)
                PPM2.DebugPrint('Resuing ', @maneModelUP, ' as upper mane model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @UpperManeModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @UpperManeModelUpdateCount += 1

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

        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = true
            .upperMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new upper mane model for ', @ent, ' as ', @maneModelUP)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelLower if IsValid(@maneModelLower)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.lowerMane and ent.manePlayer == @ent
                @maneModelLower = ent
                @GetData()\SetLowerManeModel(@maneModelLower)
                PPM2.DebugPrint('Resuing ', @maneModelLower, ' as lower mane model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @LowerManeModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @LowerManeModelUpdateCount += 1

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

        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = true
            .lowerMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetNoDraw(true) if CLIENT
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new lower mane model for ', @ent, ' as ', @maneModelLower)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @tailModel if IsValid(@tailModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isTail and ent.manePlayer == @ent
                @tailModel = ent
                @GetData()\SetTailModel(@tailModel)
                PPM2.DebugPrint('Resuing ', @tailModel, ' as tail model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @TailModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @TailModelUpdateCount += 1

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

        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = true
            .isTail = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new tail model for ', @ent, ' as ', @tailModel)

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

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        @GetData()\SetUpperManeModel(@maneModelUP) if IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        @GetData()\SetLowerManeModel(@maneModelLower) if IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        @GetData()\SetTailModel(@tailModel) if IsValid(@tailModel)
        return @tailModel

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

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        super(targetEnt)
        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 NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        return NULL if not IsValid(@maneModelUP)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model) if model ~= @maneModelUP\GetModel()
        @maneModelUP\SetBodygroup(1, bodygroupID) if @maneModelUP\GetBodygroup(1) ~= bodygroupID
        @maneModelUP\SetParent(@ent\GetEntity()) if @maneModelUP\GetParent() ~= @ent and IsValid(@ent)
        @GetData()\SetUpperManeModel(@maneModelUP)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        return NULL if not IsValid(@maneModelLower)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model) if model ~= @maneModelLower\GetModel()
        @maneModelLower\SetBodygroup(1, bodygroupID) if @maneModelLower\GetBodygroup(1) ~= bodygroupID
        @maneModelLower\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetLowerManeModel(@maneModelLower)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        return NULL if not IsValid(@tailModel)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model) if model ~= @tailModel\GetModel()
        @tailModel\SetBodygroup(1, bodygroupID) if @tailModel\GetBodygroup(1) ~= bodygroupID
        @tailModel\SetModelScale(@GetData()\GetTailSize())
        @tailModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetTailModel(@tailModel)
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_MALE_2 = 35
    @FLEX_ID_MALE_BODY = 36
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

    ResetBodygroups: =>
        return unless @isValid
        return unless IsValid(@ent)
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, 0)
        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, 0)
        super()

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0

        if @GetData()\GetNewMuzzle()
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        else
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)

        @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY,     maleModifier * @GetData()\GetMaleBuff())

        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS,         @GetData()\GetFangs() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GetData()\GetClawTeeth() and 1 or 0)

        @UpdateTailSize()

        @ApplyRace()
        if createModels
            @UpdateUpperMane()
            @UpdateLowerMane()
            @UpdateTailModel()
            @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    RemoveModels: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return @RemoveModels() if not @ent\IsPony()
        @SlowUpdate(createModels)

    ApplyRace: =>
        return unless @isValid
        return if not IsValid(@ent)
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())

    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'NewMuzzle'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'BatPonyEars'
                @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
            when 'Fangs'
                @ent\SetFlexWeight(@@FLEX_ID_FANGS, @GetData()\GetFangs() and 1 or 0)
            when 'ClawTeeth'
                @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GetData()\GetClawTeeth() and 1 or 0)
            when 'ManeTypeNew'
                @UpdateUpperMane() if CLIENT
            when 'ManeTypeLowerNew'
                @UpdateLowerMane() if CLIENT
            when 'TailTypeNew'
                @UpdateTailModel() if CLIENT
            when 'TailSize'
                @UpdateTailModel() if CLIENT
            when 'Race'
                @ApplyRace()
            when 'WingsType'
                @ApplyRace()
            when 'MaleBuff'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'TailSize'
                return if SERVER
                size = @GetData()\GetTailSize()
                vecTail = Vector(size, size, size)
                @ent\ManipulateBoneScale(@@BONE_TAIL_1, vecTail)
                @ent\ManipulateBoneScale(@@BONE_TAIL_2, vecTail)
                @ent\ManipulateBoneScale(@@BONE_TAIL_3, vecTail)
            when 'SocksAsModel'
                return if SERVER
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)

if CLIENT
    timer.Simple 0, ->
        timer.Simple 0, ->
            return if not pac
            pac.__ppm2_Bodygroups_ResetBones = pac.__ppm2_Bodygroups_ResetBones or pac.ResetBones
            pac.ResetBones = (ent, ...) ->
                status = pac.__ppm2_Bodygroups_ResetBones(ent, ...)
                if data = ent\GetPonyData()
                    if bodygroup = data\GetBodygroupController()
                        bodygroup\UpdateTailSize()
                return status

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

PPM2.GetBodygroupController = (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

    @NEXT_OBJ_ID = 0

    @COOLDOWN_TIME = 5
    @COOLDOWN_MAX_COUNT = 4

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @SocksModelUpdateCooldown = 0
        @SocksModelUpdateCount = 0
        PPM2.DebugPrint('Created new bodygroups controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)

    UpdateCooldowns: =>
        if CLIENT
            @SocksModelUpdateCooldown = 0
            @SocksModelUpdateCount = 0
        rTime = RealTime()
        if @SocksModelUpdateCooldown < rTime
            @SocksModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @SocksModelUpdateCount = 0

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    CreateSocksModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @socksModel if IsValid(@socksModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isSocks and ent.manePlayer == @ent
                @socksModel = ent
                @GetData()\SetSocksModel(@socksModel)
                PPM2.DebugPrint('Resuing ', @socksModel, ' as socks model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @SocksModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @SocksModelUpdateCount += 1

        model = 'models/props_pony/ppm/cosmetics/ppm_socks.mdl'

        @socksModel = ents.Create('prop_dynamic') if SERVER
        @socksModel = ClientsideModel(model) if CLIENT
        with @socksModel
            .isPonyPropModel = true
            .isSocks = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new socks model for ', @ent, ' as ', @socksModel)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@socksModel)
                @GetData()\SetSocksModel(@socksModel)
                @ent\SetNWEntity('PPM2.SocksModel', @socksModel) if IsValid(@ent)
        else
            @GetData()\SetSocksModel(@socksModel)

        return @socksModel
    CreateSocksModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        return NULL if not IsValid(@socksModel)
        @socksModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @socksModel\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        @GetData()\SetSocksModel(@socksModel)
        return @socksModel

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        return unless IsValid(targetEnt)
        socks = @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
        if IsValid(socks)
            socks\SetParent(targetEnt)
            socks\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    GetSocks: => @socksModel or NULL

    ApplyRace: =>
        return unless @isValid
        return NULL if not IsValid(@ent)
        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)

    ResetTail: =>
        return if not CLIENT
        @ent\ManipulateBoneScale(@@BONE_TAIL_1, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_2, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, Vector(1, 1, 1))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_1, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_2, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_3, Angle(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_1, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, Vector(0, 0, 0))
    ResetBodygroups: =>
        return unless @isValid
        return unless IsValid(@ent)
        return unless @ent\GetBodyGroups()
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
        @ResetTail()
    Reset: => @ResetBodygroups()
    RemoveModels: =>
        @socksModel\Remove() if IsValid(@socksModel)

    UpdateTailSize: =>
        return if not CLIENT
        size = @GetData()\GetTailSize() * @GetData()\GetPonySize()
        vec = Vector(1, 1, 1)
        vecTail = vec * size
        vecTailPos = Vector((size - 1) * 8, 0, 0)

        @ent\ManipulateBoneScale(@@BONE_TAIL_2, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_1, vecTail)

        --@ent\ManipulateBonePosition(@@BONE_TAIL_1, vecTail)
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, vecTailPos)
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, vecTailPos)

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @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())
        @UpdateTailSize()
        @ApplyRace()
        @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return if not @ent\IsPony()
        @SlowUpdate(createModels)

    Remove: =>
        @RemoveModels()
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        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 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
            when 'Race'
                @ApplyRace()

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

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

    ApplyRace: =>
        return unless @isValid
        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)

--
-- 0	LrigPelvis
-- 1	Lrig_LEG_BL_Femur
-- 2	Lrig_LEG_BL_Tibia
-- 3	Lrig_LEG_BL_LargeCannon
-- 4	Lrig_LEG_BL_PhalanxPrima
-- 5	Lrig_LEG_BL_RearHoof
-- 6	Lrig_LEG_BR_Femur
-- 7	Lrig_LEG_BR_Tibia
-- 8	Lrig_LEG_BR_LargeCannon
-- 9	Lrig_LEG_BR_PhalanxPrima
-- 10	Lrig_LEG_BR_RearHoof
-- 11	LrigSpine1
-- 12	LrigSpine2
-- 13	LrigRibcage
-- 14	Lrig_LEG_FL_Scapula
-- 15	Lrig_LEG_FL_Humerus
-- 16	Lrig_LEG_FL_Radius
-- 17	Lrig_LEG_FL_Metacarpus
-- 18	Lrig_LEG_FL_PhalangesManus
-- 19	Lrig_LEG_FL_FrontHoof
-- 20	Lrig_LEG_FR_Scapula
-- 21	Lrig_LEG_FR_Humerus
-- 22	Lrig_LEG_FR_Radius
-- 23	Lrig_LEG_FR_Metacarpus
-- 24	Lrig_LEG_FR_PhalangesManus
-- 25	Lrig_LEG_FR_FrontHoof
-- 26	LrigNeck1
-- 27	LrigNeck2
-- 28	LrigNeck3
-- 29	LrigScull
-- 30	Jaw
-- 31	Ear_L
-- 32	Ear_R
-- 33	Mane02
-- 34	Mane03
-- 35	Mane03_tip
-- 36	Mane04
-- 37	Mane05
-- 38	Mane06
-- 39	Mane07
-- 40	Mane01
-- 41	Lrigweaponbone
-- 42	Tail01
-- 43	Tail02
-- 44	Tail03
--

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

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

    @BONE_TAIL_1 = 42
    @BONE_TAIL_2 = 43
    @BONE_TAIL_3 = 44

    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    new: (...) =>
        super(...)
        @UpperManeModelUpdateCooldown = 0
        @UpperManeModelUpdateCount = 0
        @LowerManeModelUpdateCooldown = 0
        @LowerManeModelUpdateCount = 0
        @TailModelUpdateCooldown = 0
        @TailModelUpdateCount = 0

    UpdateCooldowns: =>
        super()
        if CLIENT
            @UpperManeModelUpdateCooldown = 0
            @UpperManeModelUpdateCount = 0
            @LowerManeModelUpdateCooldown = 0
            @LowerManeModelUpdateCount = 0
            @TailModelUpdateCooldown = 0
            @TailModelUpdateCount = 0
        rTime = RealTime()
        if @UpperManeModelUpdateCooldown < rTime
            @UpperManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @UpperManeModelUpdateCount = 0
        if @LowerManeModelUpdateCooldown < rTime
            @LowerManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @LowerManeModelUpdateCount = 0
        if @TailModelUpdateCooldown < rTime
            @TailModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @TailModelUpdateCount = 0

    CreateUpperManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelUP if IsValid(@maneModelUP)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.upperMane and ent.manePlayer == @ent
                @maneModelUP = ent
                @GetData()\SetUpperManeModel(@maneModelUP)
                PPM2.DebugPrint('Resuing ', @maneModelUP, ' as upper mane model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @UpperManeModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @UpperManeModelUpdateCount += 1

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

        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = true
            .upperMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new upper mane model for ', @ent, ' as ', @maneModelUP)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelLower if IsValid(@maneModelLower)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.lowerMane and ent.manePlayer == @ent
                @maneModelLower = ent
                @GetData()\SetLowerManeModel(@maneModelLower)
                PPM2.DebugPrint('Resuing ', @maneModelLower, ' as lower mane model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @LowerManeModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @LowerManeModelUpdateCount += 1

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

        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = true
            .lowerMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetNoDraw(true) if CLIENT
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new lower mane model for ', @ent, ' as ', @maneModelLower)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @tailModel if IsValid(@tailModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isTail and ent.manePlayer == @ent
                @tailModel = ent
                @GetData()\SetTailModel(@tailModel)
                PPM2.DebugPrint('Resuing ', @tailModel, ' as tail model for ', @ent)
                return ent

        @UpdateCooldowns()
        return NULL if @TailModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @TailModelUpdateCount += 1

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

        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = true
            .isTail = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new tail model for ', @ent, ' as ', @tailModel)

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

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        @GetData()\SetUpperManeModel(@maneModelUP) if IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        @GetData()\SetLowerManeModel(@maneModelLower) if IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        @GetData()\SetTailModel(@tailModel) if IsValid(@tailModel)
        return @tailModel

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

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        super(targetEnt)
        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 NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        return NULL if not IsValid(@maneModelUP)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model) if model ~= @maneModelUP\GetModel()
        @maneModelUP\SetBodygroup(1, bodygroupID) if @maneModelUP\GetBodygroup(1) ~= bodygroupID
        @maneModelUP\SetParent(@ent\GetEntity()) if @maneModelUP\GetParent() ~= @ent and IsValid(@ent)
        @GetData()\SetUpperManeModel(@maneModelUP)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        return NULL if not IsValid(@maneModelLower)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model) if model ~= @maneModelLower\GetModel()
        @maneModelLower\SetBodygroup(1, bodygroupID) if @maneModelLower\GetBodygroup(1) ~= bodygroupID
        @maneModelLower\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetLowerManeModel(@maneModelLower)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        return NULL if not IsValid(@tailModel)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model) if model ~= @tailModel\GetModel()
        @tailModel\SetBodygroup(1, bodygroupID) if @tailModel\GetBodygroup(1) ~= bodygroupID
        @tailModel\SetModelScale(@GetData()\GetTailSize())
        @tailModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetTailModel(@tailModel)
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_MALE_2 = 35
    @FLEX_ID_MALE_BODY = 36
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

    ResetBodygroups: =>
        return unless @isValid
        return unless IsValid(@ent)
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, 0)
        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, 0)
        super()

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0

        if @GetData()\GetNewMuzzle()
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        else
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)

        @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY,     maleModifier * @GetData()\GetMaleBuff())

        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS,         @GetData()\GetFangs() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GetData()\GetClawTeeth() and 1 or 0)

        @UpdateTailSize()

        @ApplyRace()
        if createModels
            @UpdateUpperMane()
            @UpdateLowerMane()
            @UpdateTailModel()
            @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    RemoveModels: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return @RemoveModels() if not @ent\IsPony()
        @SlowUpdate(createModels)

    ApplyRace: =>
        return unless @isValid
        return if not IsValid(@ent)
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())

    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'NewMuzzle'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'BatPonyEars'
                @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
            when 'Fangs'
                @ent\SetFlexWeight(@@FLEX_ID_FANGS, @GetData()\GetFangs() and 1 or 0)
            when 'ClawTeeth'
                @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GetData()\GetClawTeeth() and 1 or 0)
            when 'ManeTypeNew'
                @UpdateUpperMane() if CLIENT
            when 'ManeTypeLowerNew'
                @UpdateLowerMane() if CLIENT
            when 'TailTypeNew'
                @UpdateTailModel() if CLIENT
            when 'TailSize'
                @UpdateTailModel() if CLIENT
            when 'Race'
                @ApplyRace()
            when 'WingsType'
                @ApplyRace()
            when 'MaleBuff'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'TailSize'
                return if SERVER
                size = @GetData()\GetTailSize()
                vecTail = Vector(size, size, size)
                @ent\ManipulateBoneScale(@@BONE_TAIL_1, vecTail)
                @ent\ManipulateBoneScale(@@BONE_TAIL_2, vecTail)
                @ent\ManipulateBoneScale(@@BONE_TAIL_3, vecTail)
            when 'SocksAsModel'
                return if SERVER
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)

if CLIENT
    timer.Simple 0, ->
        timer.Simple 0, ->
            return if not pac
            pac.__ppm2_Bodygroups_ResetBones = pac.__ppm2_Bodygroups_ResetBones or pac.ResetBones
            pac.ResetBones = (ent, ...) ->
                status = pac.__ppm2_Bodygroups_ResetBones(ent, ...)
                if data = ent\GetPonyData()
                    if bodygroup = data\GetBodygroupController()
                        bodygroup\UpdateTailSize()
                return status

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

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


Fix typo in app.moon

Fix typo in app.moon

lapis = require "lapis"
config = (require "lapis.config").get!
content = require "content"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
import capture_errors, assert_error, yield_error from require "lapis.application"
safe_route = (fn) ->
    capture_errors {
        on_error: =>
            @app\error_handler self

        =>
            fn self
    }

class CSWeek extends lapis.Application
    layout: require "views.layout"

    error_handler: (context, code) =>
        code = code or 500
        context = context or self

        with context
            .m, err = content\get "error"
            if err then
                print "Could not load the error page (code #{code}): #{err}"
                .m = { }

            .m[code] = .m[code] or .m.default or { }
            .page_id = "error"
            .status = code
            .m.title = .m[code].title

        render: "error", status: code

    try_render: (template, context) =>
        context = context or self
        with context
            .nav = assert_error content\get "nav"
            .footer = assert_error content\get "footer"
        render: template

    @before_filter =>
        if #[n for n in *{'production-perftest', 'development-perftest'} when n == config._name] > 0
            after_dispatch ->
                print to_json(ngx.ctx.performance)

    [home: "/"]: safe_route =>
        @page_id = "home"
        @m = assert_error content\get "home"
        @lecturers = assert_error content\get "lecturers"
        @app\try_render "home", self

    [about: "/o-nedelji"]: safe_route =>
        @page_id = "about"
        @m = assert_error content\get "about"
        @app\try_render "about", self

    [lecturers: "/predavaci"]: safe_route =>
        @page_id = "lecturers"
        @m = assert_error content\get "lecturers_page"
        @lecturers = assert_error content\get "lecturers"
        @app\try_render "lecturers", self

    [lecturer: "/predavaci/:name"]: =>

    handle_404: =>
        @errors = "Route `#{self.req.parsed_url.path or 'unknown'}` not found"
        @app\error_handler self, 404

lapis = require "lapis"
config = (require "lapis.config").get!
content = require "content"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
import capture_errors, assert_error, yield_error from require "lapis.application"
safe_route = (fn) ->
    capture_errors {
        on_error: =>
            @app\error_handler self

        =>
            fn self
    }

class CSWeek extends lapis.Application
    layout: require "views.layout"

    error_handler: (context, code) =>
        code = code or 500
        context = context or self

        with context
            .m, err = content\get "error"
            if err then
                print "Could not load the error page (code #{code}): #{err}"
                .m = { }

            .m[code] = .m[code] or .m.default or { }
            .page_id = "error"
            .status = code
            .m.title = .m[code].title

        render: "error", status: code

    try_render: (template, context) =>
        context = context or self
        with context
            .nav = assert_error content\get "nav"
            .footer = assert_error content\get "footer"
        render: template

    @before_filter =>
        if #[n for n in *{'production-perftest', 'development-perftest'} when n == config._name] > 0
            after_dispatch ->
                print to_json(ngx.ctx.performance)

    [home: "/"]: safe_route =>
        @page_id = "home"
        @m = assert_error content\get "home"
        @lecturers = assert_error content\get "lecturers"
        @app\try_render "home", self

    [about: "/o-nedelji"]: safe_route =>
        @page_id = "about"
        @m = assert_error content\get "about"
        @app\try_render "about", self

    [lecturers: "/predavaci"]: safe_route =>
        @page_id = "lecturers"
        @m = assert_error content\get "lecturers_page"
        @lecturers = assert_error content\et "lecturers"
        @app\try_render "lecturers", self

    [lecturer: "/predavaci/:name"]: =>

    handle_404: =>
        @errors = "Route `#{self.req.parsed_url.path or 'unknown'}` not found"
        @app\error_handler self, 404


Got door offsets working.

Got door offsets working.

STI = require('vendor/sti/sti')
bump = require('vendor/bump/bump')
State = require('lib/states/state')
MapLayer = require('lib/map-layer')
PauseState = require('lib/states/pause')
entities = require('lib/entities')
DrawSystem = require('lib/systems/draw')
ControlSystem = require('lib/systems/control')
Color = require('lib/display/color')
Point = require('lib/geo/point')
Direction = require('lib/enums/direction')

class AreaState extends State
	new: (@mapName) =>
		super()

		@map = STI("assets/maps/#{@mapName}", { 'bump' })
		@world = bump.newWorld(@map.tilewidth)
		@map\bump_init(@world)
		@scale = 1

	enter: (previous, transition) =>
		super(previous)

		if transition.player
			@player = transition.player\clone(@)
		needsDoor = not not transition.player

		doors = {}
		for object in *@map.layers.entities.objects
			if object.type == 'Player' and @player then continue
			if object.type == 'zones/DoorZone' then _.push(doors, object)

			entity = entities[object.type](@, object)
			if object.type == 'Player'
				@player = entity
				continue
			@addEntityToWorld(entity, object)

		assert @player
		door = doors[1]
		if needsDoor
			@player.point.x = door.x
			@player.point.y = door.y

		-- Handle specific spawn points
		if toPoint = transition.toPoint
			if toPoint.x then @player.point.x = toPoint.x
			if toPoint.y then @player.point.y = toPoint.y
		-- Handle door offsets
		if transition.offset
			if transition.offset.x then @player.point.x += transition.offset.x
			if transition.offset.y then @player.point.y += transition.offset.y
		-- Handle direction
		if transition.direction
			if transition.direction == Direction.NORTH then @player.point.y += @player.data.height
			if transition.direction == Direction.SOUTH then @player.point.y -= @player.data.height
			if transition.direction == Direction.WEST then @player.point.x += @player.data.width
			if transition.direction == Direction.EAST then @player.point.x -= @player.data.width

		@addEntityToWorld(@player, @player\getData())

		entityLayer = MapLayer(@map, 'entities')
		entityLayer.engine\addSystem(DrawSystem())
		entityLayer.engine\addSystem(ControlSystem())
		entityLayer.engine\addEntity(@player)

	addEntityToWorld: (entity, data) =>
		@world\add(entity, data.x, data.y, data.width, data.height)

	update: (dt) =>
		super(dt)
		@map\update(dt)
		@windowWidth = love.graphics.getWidth()
		@windowHeight = love.graphics.getHeight()
		tx = math.floor(@player.point.x - @windowWidth / @scale / 2)
		ty = math.floor(@player.point.y - @windowHeight / @scale / 2)
		@translation = Point(tx, ty)

	draw: () =>
		love.graphics.push()
		love.graphics.scale(@scale)
		love.graphics.translate(-@translation.x, -@translation.y)
		@map\setDrawRange(@translation.x, @translation.y, @windowWidth, @windowHeight)
		@map\draw()

		if @DEBUG
			love.graphics.setColor(Color(255, 0, 0, 255))
			@map\bump_draw(@world)
			love.graphics.setColor(Color(255, 255, 255, 255))

		love.graphics.pop()

		super()

	keypressed: (key) =>
		super(key)
		if key == 'escape' then @push(PauseState())

	mousepressed: (x, y, button) =>
		if @DEBUG and button == 2
			eventPoint = Point(x, y)
			-- TODO: Make work not just for player
			point = eventPoint + @translation - Point(200, 150)

			items, len = @world\queryPoint(point\toTuple())
			if len > 0 then d items

STI = require('vendor/sti/sti')
bump = require('vendor/bump/bump')
State = require('lib/states/state')
MapLayer = require('lib/map-layer')
PauseState = require('lib/states/pause')
entities = require('lib/entities')
DrawSystem = require('lib/systems/draw')
ControlSystem = require('lib/systems/control')
Color = require('lib/display/color')
Point = require('lib/geo/point')
Direction = require('lib/enums/direction')

class AreaState extends State
	new: (@mapName) =>
		super()

		@map = STI("assets/maps/#{@mapName}", { 'bump' })
		@world = bump.newWorld(@map.tilewidth)
		@map\bump_init(@world)
		@scale = 1

	enter: (previous, transition) =>
		super(previous)

		if transition.player
			@player = transition.player\clone(@)
		needsDoor = not not transition.player

		doors = {}
		for object in *@map.layers.entities.objects
			if object.type == 'Player' and @player then continue
			if object.type == 'zones/DoorZone' then _.push(doors, object)

			entity = entities[object.type](@, object)
			if object.type == 'Player'
				@player = entity
				continue
			@addEntityToWorld(entity, object)

		assert @player
		door = doors[1]
		if needsDoor
			@player.point.x = door.x
			@player.point.y = door.y

		-- Handle specific spawn points
		if toPoint = transition.toPoint
			if toPoint.x then @player.point.x = toPoint.x
			if toPoint.y then @player.point.y = toPoint.y
		-- Handle door offsets
		-- if transition.offset
		-- 	if transition.offset.x then @player.point.x += transition.offset.x
		-- 	if transition.offset.y then @player.point.y += transition.offset.y
		-- Handle direction
		if transition.direction
			if transition.direction == Direction.NORTH then @player.point.y += @player.data.height
			if transition.direction == Direction.SOUTH then @player.point.y -= @player.data.height
			if transition.direction == Direction.WEST then @player.point.x += @player.data.width
			if transition.direction == Direction.EAST then @player.point.x -= @player.data.width

		@addEntityToWorld(@player, @player\getData())

		entityLayer = MapLayer(@map, 'entities')
		entityLayer.engine\addSystem(DrawSystem())
		entityLayer.engine\addSystem(ControlSystem())
		entityLayer.engine\addEntity(@player)

	addEntityToWorld: (entity, data) =>
		@world\add(entity, data.x, data.y, data.width, data.height)

	update: (dt) =>
		super(dt)
		@map\update(dt)
		@windowWidth = love.graphics.getWidth()
		@windowHeight = love.graphics.getHeight()
		tx = math.floor(@player.point.x - @windowWidth / @scale / 2)
		ty = math.floor(@player.point.y - @windowHeight / @scale / 2)
		@translation = Point(tx, ty)

	draw: () =>
		love.graphics.push()
		love.graphics.scale(@scale)
		love.graphics.translate(-@translation.x, -@translation.y)
		@map\setDrawRange(@translation.x, @translation.y, @windowWidth, @windowHeight)
		@map\draw()

		if @DEBUG
			love.graphics.setColor(Color(255, 0, 0, 255))
			@map\bump_draw(@world)
			love.graphics.setColor(Color(255, 255, 255, 255))

		love.graphics.pop()

		super()

	keypressed: (key) =>
		super(key)
		if key == 'escape' then @push(PauseState())

	mousepressed: (x, y, button) =>
		if @DEBUG and button == 2
			eventPoint = Point(x, y)
			-- TODO: Make work not just for player
			point = eventPoint + @translation - Point(200, 150)

			items, len = @world\queryPoint(point\toTuple())
			if len > 0 then d items


main layout

main layout


Fix logic error.

Fix logic error.

bktree = require "bk-tree"
rex = require "rex_pcre"
cli = require "cliargs"
xml = require "xml"
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"
			lfs.mkdir(new) if not debug
			"[mkdir]: #{old} to: #{new}"
		when "symlink", "hardlink"
			lfs.link(old, new, action != "hardlink") if not debug
			"#{action} [link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

prompt_for_alternatives = (names, original) ->

	if not names or #names == 0
		print "-- no results for #{original}, skipping -- "
		return original

	print "----------------------------"
	print "Original String: #{original}"
	for i, word in pairs names
		print "##{i} #{word.str}"

	io.stdout\write("Enter number to pick (blank to keep original): ")
	n = io.stdin\read()

	return if tonumber(n) then names[tonumber(n)].str else original

find_alt_names = (tree, name, max_dist) ->

	io.stdout\write("Skip: #{name}? (Y/N): ")
	if io.stdin\read() == "Y"
		io.stdin\flush()
		return {}
	max_dist = (max_dist > 0 and max_dist) or (max_dist < 0 and #name) or 100

	i = 0
	result = {}
	while #result == 0 and i < max_dist
		result = tree\query_sorted(name, i)
		i = i + 1

	[result[i] for i=1, #result when result[i].distance > 0]

process_xml = (string) ->
	shows = {}
	xtable = xml.load(string)

	if not xtable.xml == "anime-list" then error "incorrect XML"
	for k, t in pairs xtable
		if type(t) == "table"
			if t.xml == "anime" then
				for k2, st in pairs t
					if st.xml == "name"
						shows[#shows+1] = st[1] if type(st[1]) == "string"
						print shows[#shows]

	shows

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata

		if rex.gsub(file, regex, '')

			isdir = (md) -> md == 'directory'

			dir_transform = (f) ->
				f = rex.gsub(f, regex, '')
				f = f\gsub('[_]', ' ')
				f = f\match("^%s*(.-)%s*$")
				altf = prompt_for_alternatives(find_alt_names(search_tree, f, maxdist), f) if parent.in == inputdir
				f = altf or f
				parent.out .. '/' .. f

			file_transform = (f) ->
				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

			in_f = parent.in .. '/' .. file
			out_f = if isdir(mode) then dir_transform(file) else file_transform(file)

			action = if isdir(mode) then "mkdir" else args["a"]
			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("-n, --noprompt", "don't prompt when asking for new directory names, pick closest option.")
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

af = io.open("anime-list-full.xml", "r")
results = process_xml(af\read("*all"))

export search_tree = bktree\new()
{search_tree\insert(x) for k, x in pairs results}

io.stdout\write("Similarity Search Max Distance: ")
export maxdist = tonumber(io.stdin\read())

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

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

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

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

bktree = require "bk-tree"
rex = require "rex_pcre"
cli = require "cliargs"
xml = require "xml"
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"
			lfs.mkdir(new) if not debug
			"[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}"

prompt_for_alternatives = (names, original) ->

	if not names or #names == 0
		print "-- no results for #{original}, skipping -- "
		return original

	print "----------------------------"
	print "Original String: #{original}"
	for i, word in pairs names
		print "##{i} #{word.str}"

	io.stdout\write("Enter number to pick (blank to keep original): ")
	n = io.stdin\read()

	return if tonumber(n) then names[tonumber(n)].str else original

find_alt_names = (tree, name, max_dist) ->

	io.stdout\write("Skip: #{name}? (Y/N): ")
	if io.stdin\read() == "Y"
		io.stdin\flush()
		return {}
	max_dist = (max_dist > 0 and max_dist) or (max_dist < 0 and #name) or 100

	i = 0
	result = {}
	while #result == 0 and i < max_dist
		result = tree\query_sorted(name, i)
		i = i + 1

	[result[i] for i=1, #result when result[i].distance > 0]

process_xml = (string) ->
	shows = {}
	xtable = xml.load(string)

	if not xtable.xml == "anime-list" then error "incorrect XML"
	for k, t in pairs xtable
		if type(t) == "table"
			if t.xml == "anime" then
				for k2, st in pairs t
					if st.xml == "name"
						shows[#shows+1] = st[1] if type(st[1]) == "string"
						print shows[#shows]

	shows

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata

		if rex.gsub(file, regex, '')

			isdir = (md) -> md == 'directory'

			dir_transform = (f) ->
				f = rex.gsub(f, regex, '')
				f = f\gsub('[_]', ' ')
				f = f\match("^%s*(.-)%s*$")
				altf = prompt_for_alternatives(find_alt_names(search_tree, f, maxdist), f) if parent.in == inputdir
				f = altf or f
				parent.out .. '/' .. f

			file_transform = (f) ->
				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

			in_f = parent.in .. '/' .. file
			out_f = if isdir(mode) then dir_transform(file) else file_transform(file)

			action = if isdir(mode) then "mkdir" else args["a"]
			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("-n, --noprompt", "don't prompt when asking for new directory names, pick closest option.")
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

af = io.open("anime-list-full.xml", "r")
results = process_xml(af\read("*all"))

export search_tree = bktree\new()
{search_tree\insert(x) for k, x in pairs results}

io.stdout\write("Similarity Search Max Distance: ")
export maxdist = tonumber(io.stdin\read())

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

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

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

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


Add Tile class to script library.

Add Tile class to script library.

import terrain from require 'arms/constants'

class Tile
  new: (@terrain_type, @elevation) =>
  to_json: => { t: @terrain_type, e: @elevation }

class Map
  new: =>
    @size =
      x: 96
      y: 96
    @data =
      base_terrain: terrain.grass1
      base_elevation: 1
      tiles: {}

  base_terrain: (type) =>
    @data.base_terrain = type
  base_elevation: (level = 1) =>
    @data.base_elevation = level

  tile: (x, y) =>
    @data.tiles[y] or= {}
    @data.tiles[y][x] or= Tile @data.base_terrain, @data.base_elevation
    @data.tiles[y][x]

  finalize: =>
    for y = 1, @size.y
      @data.tiles[y] or= {}
      for x = 1, @size.x
        @data.tiles[y][x] or= Tile @data.base_terrain, @data.base_elevation

  to_json: =>
    @finalize!

    return {
      size: { @size.x, @size.y }
      base_terrain: @data.base_terrain
      base_elevation: @data.base_elevation
      tiles: [ [ tile\to_json! for tile in *row ] for row in *@data.tiles ]
    }

-- Exports
{ :Map }

import terrain from require 'arms/constants'

class Map
  new: =>
    @size =
      x: 96
      y: 96
    @data =
      base_terrain: terrain.grass1
      base_elevation: 1
      tiles: {}

  base_terrain: (type) =>
    @data.base_terrain = type
  base_elevation: (level = 1) =>
    @data.base_elevation = level

  finalize: =>
    for y = 1, @size.y
      @data.tiles[y] or= {}
      for x = 1, @size.x
        @data.tiles[y][x] or= {
          t: @data.base_terrain
          e: @data.base_elevation
        }

  to_json: =>
    @finalize!

    return {
      size: { @size.x, @size.y }
      base_terrain: @data.base_terrain
      base_elevation: @data.base_elevation
      tiles: @data.tiles
    }

-- Exports
{ :Map }