Updated binding position

Updated binding position

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

    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 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)
                return ent

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

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

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

        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 CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        @socksModel\SetParent(@ent) 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
        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: =>
        return unless @isValid
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
    SlowUpdate: =>
        @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()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @SlowUpdate()

    Remove: =>
        @socksModel\Remove() if IsValid(@socksModel)
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        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: =>
        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'}

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

    __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 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)
                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"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = true
            .upperMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \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
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        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)
                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"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = true
            .lowerMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \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
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        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)
                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"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = true
            .isTail = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \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
                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 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 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 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 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) if @maneModelUP\GetParent() ~= @ent and IsValid(@ent)
        @GetData()\SetUpperManeModel(@maneModelUP)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        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) if IsValid(@ent)
        @GetData()\SetLowerManeModel(@maneModelLower)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        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) if IsValid(@ent)
        @GetData()\SetTailModel(@tailModel)
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

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

    SlowUpdate: =>
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE,          @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
        @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)
        @ApplyRace()
        @UpdateUpperMane()
        @UpdateLowerMane()
        @UpdateTailModel()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @SlowUpdate()

    DataChanges: (state) =>
        return unless @isValid
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                @ent\SetFlexWeight(@@FLEX_ID_MALE, @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
            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()
            when 'ManeTypeLowerNew'
                @UpdateLowerMane()
            when 'TailTypeNew'
                @UpdateTailModel()
            when 'TailSize'
                @UpdateTailModel()
            when 'Race'
                @ApplyRace()
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
    Remove: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()

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

    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 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)
                return ent

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

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

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @socksModel = ents.Create('prop_dynamic') if SERVER
        @socksModel = ClientsideModel(model) if CLIENT
        with @socksModel
            .isPonyPropModel = true
            .isSocks = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        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 CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        @socksModel\SetParent(@ent) 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
        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: =>
        return unless @isValid
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
    SlowUpdate: =>
        @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()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @SlowUpdate()

    Remove: =>
        @socksModel\Remove() if IsValid(@socksModel)
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        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: =>
        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'}

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

    __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 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)
                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"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = 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
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        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)
                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"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = 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
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        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)
                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"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = 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
                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 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 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 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 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) if @maneModelUP\GetParent() ~= @ent and IsValid(@ent)
        @GetData()\SetUpperManeModel(@maneModelUP)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        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) if IsValid(@ent)
        @GetData()\SetLowerManeModel(@maneModelLower)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        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) if IsValid(@ent)
        @GetData()\SetTailModel(@tailModel)
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

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

    SlowUpdate: =>
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE,          @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
        @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)
        @ApplyRace()
        @UpdateUpperMane()
        @UpdateLowerMane()
        @UpdateTailModel()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @SlowUpdate()

    DataChanges: (state) =>
        return unless @isValid
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                @ent\SetFlexWeight(@@FLEX_ID_MALE, @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
            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()
            when 'ManeTypeLowerNew'
                @UpdateLowerMane()
            when 'TailTypeNew'
                @UpdateTailModel()
            when 'TailSize'
                @UpdateTailModel()
            when 'Race'
                @ApplyRace()
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
    Remove: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

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


Send data immidiately

Send data immidiately

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

net = DLib.net

DLib.RegisterAddonName('PPM/2')

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.Unsupported', 600, 4, ->
	if mat_dxlevel\GetInt() >= 90
		timer.Remove 'PPM2.Unsupported'
		return

	Derma_Message('gui.ppm2.dxlevel.not_supported', 'gui.ppm2.dxlevel.toolow')

timer.Create 'PPM2.ModelChecks', 1, 0, ->
	for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
		ent = task.ent
		if IsValid(ent)
			ent.__cachedIsPony = ent\IsPony()

	for _, ply in ipairs player.GetAll()
		if not ply\IsDormant()
			ply.__cachedIsPony = ply\IsPony()
			ponydata = ply\GetPonyData()

			if ply.__cachedIsPony
				if (not ponydata or ponydata\GetHideWeapons()) and not hook.Run('SuppressPonyWeaponsHide', ply) and not ply.RenderOverride
					for _, wep in ipairs ply\GetWeapons()
						if wep
							if not hook.Run('ShouldDrawPonyWeapon', ply, wep) and (not wep.ShouldPonyDraw or not wep\ShouldPonyDraw(ply))
								wep\SetNoDraw(true)
								wep.__ppm2_weapon_hit = true
							elseif wep.__ppm2_weapon_hit
								wep\SetNoDraw(false)
								ply.__ppm2_weapon_hit = false
				else
					for _, wep in ipairs ply\GetWeapons()
						if wep and wep.__ppm2_weapon_hit
							wep\SetNoDraw(false)
							ply.__ppm2_weapon_hit = false

PlayerRespawn = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
	net.Start('PPM2.Require')
	net.SendToServer()
	PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
	return if lastDataSend > RealTimeL()
	lastDataSend = RealTimeL() + 10
	instance = PPM2.GetMainData()
	newData = instance\CreateNetworkObject()
	newData\Create()
	instance\SetNetworkObject(newData)
	PPM2.Message 'Sending pony data to server...'

timer.Simple 0, ->
	RunConsoleCommand('ppm2_reload')
	timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

net.receive 'PPM2.EditorCamPos', ->
	ply = net.ReadPlayer()
	return if not ply\IsValid()
	pVector, pAngle = net.ReadVector(), net.ReadAngle()

	if not IsValid(ply.__ppm2_cam)
		ply.__ppm2_cam = ClientsideModel('models/tools/camera/camera.mdl', RENDERGROUP_BOTH)
		ply.__ppm2_cam\SetModelScale(0.4)
		ply.__ppm2_cam.RenderOverride = =>
			return if not ply.__ppm2_campos_lerp
			render.DrawLine(ply.__ppm2_campos_lerp, ply\EyePos(), color_white, true)
			@DrawModel()

		hook.Add 'Think', ply.__ppm2_cam, =>
			return @Remove() if not IsValid(ply) or not ply\GetNWBool('PPM2.InEditor')
			findPos, findAng = LocalToWorld(pVector, pAngle, ply\GetPos(), ply\EyeAngles())
			ply.__ppm2_campos_lerp = Lerp(RealFrameTime() * 22, ply.__ppm2_campos_lerp or findPos, findPos)
			@SetPos(ply.__ppm2_campos_lerp)
			@SetAngles(findAng)

hook.Add 'HUDPaint', 'PPM2.EditorStatus', ->
	lply = LocalPlayer()
	lpos = lply\EyePos()

	editortext = DLib.i18n.localize('tip.ppm2.in_editor')

	for ply in *player.GetAll()
		if ply ~= lply and ply\GetNWBool('PPM2.InEditor')
			pos = ply\EyePos()
			dist = pos\Distance(lpos)

			if dist < 250
				pos.z += 10
				alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
				{:x, :y} = pos\ToScreen()
				draw.DrawText(editortext, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
				draw.DrawText(editortext, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

			if ply.__ppm2_campos
				pos = Vector(ply.__ppm2_campos)
				dist = pos\Distance(lpos)

				if dist < 250
					pos.z += 9
					alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
					{:x, :y} = pos\ToScreen()
					text = DLib.i18n.localize('tip.ppm2.camera', ply\Nick())
					draw.DrawText(text, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
					draw.DrawText(text, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

hook.Add 'PACChooseDeathRagdoll', 'PPM2.DeathRagdoll', (ragdoll) => @GetNWEntity('PPM2.DeathRagdoll') if IsValid(@GetNWEntity('PPM2.DeathRagdoll'))

concommand.Add 'ppm2_cleanup', ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()
	PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()

cvars.AddChangeCallback('mat_picmip', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_generic', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_body', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

-- Jazztronauts cutscenes support
timer.Simple 0, ->
	if dialog and dialog.CreatePlayerProxy
		if info = debug.getinfo(dialog.CreatePlayerProxy)
			if info.short_src and (info.short_src\find('jazztronauts') or info.short_src\find('ppm2'))
				dialog._PPM2_CreatePlayerProxy = dialog._PPM2_CreatePlayerProxy or dialog.CreatePlayerProxy
				dialog.CreatePlayerProxy = (...) ->
					ent = dialog._PPM2_CreatePlayerProxy(...)

					if IsValid(ent)
						if data = LocalPonyData()
							ment = ent.Get and ent\Get() or ent
							newdata = PPM2.NetworkedPonyData(nil, ment) if not ment\GetPonyData()
							newdata = ment\GetPonyData() if ment\GetPonyData()
							ment.__ppm2RenderOverride = nil
							ment.__ppm2_oldRenderOverride = nil
							data\ApplyDataToObject(newdata)
							newdata\SetHideManes(false)
							newdata\SetHideManesSocks(false)

					return ent

return

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

net = DLib.net

DLib.RegisterAddonName('PPM/2')

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.Unsupported', 600, 4, ->
	if mat_dxlevel\GetInt() >= 90
		timer.Remove 'PPM2.Unsupported'
		return

	Derma_Message('gui.ppm2.dxlevel.not_supported', 'gui.ppm2.dxlevel.toolow')

timer.Create 'PPM2.ModelChecks', 1, 0, ->
	for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
		ent = task.ent
		if IsValid(ent)
			ent.__cachedIsPony = ent\IsPony()

	for _, ply in ipairs player.GetAll()
		if not ply\IsDormant()
			ply.__cachedIsPony = ply\IsPony()
			ponydata = ply\GetPonyData()

			if ply.__cachedIsPony
				if (not ponydata or ponydata\GetHideWeapons()) and not hook.Run('SuppressPonyWeaponsHide', ply) and not ply.RenderOverride
					for _, wep in ipairs ply\GetWeapons()
						if wep
							if not hook.Run('ShouldDrawPonyWeapon', ply, wep) and (not wep.ShouldPonyDraw or not wep\ShouldPonyDraw(ply))
								wep\SetNoDraw(true)
								wep.__ppm2_weapon_hit = true
							elseif wep.__ppm2_weapon_hit
								wep\SetNoDraw(false)
								ply.__ppm2_weapon_hit = false
				else
					for _, wep in ipairs ply\GetWeapons()
						if wep and wep.__ppm2_weapon_hit
							wep\SetNoDraw(false)
							ply.__ppm2_weapon_hit = false

PlayerRespawn = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
	net.Start('PPM2.Require')
	net.SendToServer()
	PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
	return if lastDataSend > RealTimeL()
	lastDataSend = RealTimeL() + 10
	instance = PPM2.GetMainData()
	newData = instance\CreateNetworkObject()
	newData\Create()
	instance\SetNetworkObject(newData)
	PPM2.Message 'Sending pony data to server...'

if not IsValid(LocalPlayer())
	times = 0
	hook.Add 'Think', 'PPM2.RequireData', ->
		ply = LocalPlayer()
		return if not IsValid(ply)
		times += 1 if ply\GetVelocity()\Length() > 5

		return if times < 200
		hook.Remove 'Think', 'PPM2.RequireData'
		hook.Add 'KeyPress', 'PPM2.RequireData', ->
			hook.Remove 'KeyPress', 'PPM2.RequireData'
			RunConsoleCommand('ppm2_reload')
			timer.Simple 3, -> RunConsoleCommand('ppm2_require')
else
	timer.Simple 0, ->
		RunConsoleCommand('ppm2_reload')
		timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

net.receive 'PPM2.EditorCamPos', ->
	ply = net.ReadPlayer()
	return if not ply\IsValid()
	pVector, pAngle = net.ReadVector(), net.ReadAngle()

	if not IsValid(ply.__ppm2_cam)
		ply.__ppm2_cam = ClientsideModel('models/tools/camera/camera.mdl', RENDERGROUP_BOTH)
		ply.__ppm2_cam\SetModelScale(0.4)
		ply.__ppm2_cam.RenderOverride = =>
			return if not ply.__ppm2_campos_lerp
			render.DrawLine(ply.__ppm2_campos_lerp, ply\EyePos(), color_white, true)
			@DrawModel()

		hook.Add 'Think', ply.__ppm2_cam, =>
			return @Remove() if not IsValid(ply) or not ply\GetNWBool('PPM2.InEditor')
			findPos, findAng = LocalToWorld(pVector, pAngle, ply\GetPos(), ply\EyeAngles())
			ply.__ppm2_campos_lerp = Lerp(RealFrameTime() * 22, ply.__ppm2_campos_lerp or findPos, findPos)
			@SetPos(ply.__ppm2_campos_lerp)
			@SetAngles(findAng)

hook.Add 'HUDPaint', 'PPM2.EditorStatus', ->
	lply = LocalPlayer()
	lpos = lply\EyePos()

	editortext = DLib.i18n.localize('tip.ppm2.in_editor')

	for ply in *player.GetAll()
		if ply ~= lply and ply\GetNWBool('PPM2.InEditor')
			pos = ply\EyePos()
			dist = pos\Distance(lpos)

			if dist < 250
				pos.z += 10
				alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
				{:x, :y} = pos\ToScreen()
				draw.DrawText(editortext, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
				draw.DrawText(editortext, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

			if ply.__ppm2_campos
				pos = Vector(ply.__ppm2_campos)
				dist = pos\Distance(lpos)

				if dist < 250
					pos.z += 9
					alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
					{:x, :y} = pos\ToScreen()
					text = DLib.i18n.localize('tip.ppm2.camera', ply\Nick())
					draw.DrawText(text, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
					draw.DrawText(text, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

hook.Add 'PACChooseDeathRagdoll', 'PPM2.DeathRagdoll', (ragdoll) => @GetNWEntity('PPM2.DeathRagdoll') if IsValid(@GetNWEntity('PPM2.DeathRagdoll'))

concommand.Add 'ppm2_cleanup', ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()
	PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()

cvars.AddChangeCallback('mat_picmip', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_generic', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_body', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

-- Jazztronauts cutscenes support
timer.Simple 0, ->
	if dialog and dialog.CreatePlayerProxy
		if info = debug.getinfo(dialog.CreatePlayerProxy)
			if info.short_src and (info.short_src\find('jazztronauts') or info.short_src\find('ppm2'))
				dialog._PPM2_CreatePlayerProxy = dialog._PPM2_CreatePlayerProxy or dialog.CreatePlayerProxy
				dialog.CreatePlayerProxy = (...) ->
					ent = dialog._PPM2_CreatePlayerProxy(...)

					if IsValid(ent)
						if data = LocalPonyData()
							ment = ent.Get and ent\Get() or ent
							newdata = PPM2.NetworkedPonyData(nil, ment) if not ment\GetPonyData()
							newdata = ment\GetPonyData() if ment\GetPonyData()
							ment.__ppm2RenderOverride = nil
							ment.__ppm2_oldRenderOverride = nil
							data\ApplyDataToObject(newdata)
							newdata\SetHideManes(false)
							newdata\SetHideManesSocks(false)

					return ent

return


Pass the event type to the listener during dispatch

require "LovePunk.utils.List"

export ^

class eventlistener
	new: =>
		@__listeners = {}

	add: (type, f) =>
		@__listeners[type] = List! if @__listeners[type] == nil
		@__listeners[type]\push f

	remove: (type, f) =>
		l = @__listeners[type]
		return if l == nil
		for i, v in l\ipairs!
			if v == f
				l\remove i
				break

	dispatch: (type) =>
		return if @__listeners[type] == nil
		for e in @__listeners[type]\pairs!
			e type

EventListener = eventlistener!

AddEventListener = (type, f) -> EventListener\add type, f
RemoveEventListener = (type, f) -> EventListener\remove type, f
DispatchEvent = (event) -> EventListener\dispatch event\type! if event.type != nil

require "LovePunk.utils.List"

export ^

class eventlistener
	new: =>
		@__listeners = {}

	add: (type, f) =>
		@__listeners[type] = List! if @__listeners[type] == nil
		@__listeners[type]\push f

	remove: (type, f) =>
		l = @__listeners[type]
		return if l == nil
		for i, v in l\ipairs!
			if v == f
				l\remove i
				break

	dispatch: (type) =>
		return if @__listeners[type] == nil
		for e in @__listeners[type]\pairs!
			e!

EventListener = eventlistener!

AddEventListener = (type, f) -> EventListener\add type, f
RemoveEventListener = (type, f) -> EventListener\remove type, f
DispatchEvent = (event) -> EventListener\dispatch event\type! if event.type != nil


Always load files relative to the project directory

import app, signal, Project from howl
import File from howl.io

tstack = {}

handler = (args) ->
  tstack[#tstack+1] = args.file
  if args.file.basename == '-'
    app.window\remove_view! if #app.window.views > 1
    fn = tstack[#tstack-1] or File '.howl-proj'
    file = fn\open!
    app\open_file File '.howl-proj' unless tstack[#tstack-1]
    table.insert Project.roots, File '.'
    script = ''
    iscript = false
    for line in file\lines!
      continue if line\gsub("^%s*(.-)%s*$", "%1") == ''
      if iscript
        script ..= line .. '\n'
      elseif line == '!:'
        iscript = true
      else
        app\open_file fn.parent\join line
        app.window\remove_view! if #app.window.views > 1
    app\close_buffer args.buffer
    if script != ''
      f = require('moonscript').loadstring script
      assert f
      f!

signal.connect 'file-opened', handler

{
  info:
    author: 'Ryan Gonzalez'
    description: 'An extended project system'
    license: 'MIT'
  unload: ->
    signal.disconnect 'file-opened', handler
}

import app, signal, Project from howl
import File from howl.io

tstack = {}

handler = (args) ->
  tstack[#tstack+1] = args.file
  if args.file.basename == '-'
    app.window\remove_view! if #app.window.views > 1
    fn = tstack[#tstack-1] or File '.howl-proj'
    file = fn\open!
    app\open_file File '.howl-proj' unless tstack[#tstack-1]
    table.insert Project.roots, File '.'
    script = ''
    iscript = false
    for line in file\lines!
      continue if line\gsub("^%s*(.-)%s*$", "%1") == ''
      if iscript
        script ..= line .. '\n'
      elseif line == '!:'
        iscript = true
      else
        app\open_file File line
        app.window\remove_view! if #app.window.views > 1
    app\close_buffer args.buffer
    if script != ''
      f = require('moonscript').loadstring script
      assert f
      f!

signal.connect 'file-opened', handler

{
  info:
    author: 'Ryan Gonzalez'
    description: 'An extended project system'
    license: 'MIT'
  unload: ->
    signal.disconnect 'file-opened', handler
}