Small cleanup

Small cleanup

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

-- Texture indexes (-1)
-- 1    =   models/ppm/base/eye_l
-- 2    =   models/ppm/base/eye_r
-- 3    =   models/ppm/base/body
-- 4    =   models/ppm/base/horn
-- 5    =   models/ppm/base/wings
-- 6    =   models/ppm/base/hair_color_1
-- 7    =   models/ppm/base/hair_color_2
-- 8    =   models/ppm/base/tail_color_1
-- 9    =   models/ppm/base/tail_color_2
-- 10   =   models/ppm/base/cmark
-- 11   =   models/ppm/base/eyelashes

_M = PPM2.MaterialsRegistry
USE_HIGHRES_BODY = PPM2.USE_HIGHRES_BODY
USE_HIGHRES_TEXTURES = PPM2.USE_HIGHRES_TEXTURES

DrawTexturedRectRotated = (x = 0, y = 0, width = 0, height = 0, rotation = 0) -> surface.DrawTexturedRectRotated(x + width / 2, y + height / 2, width, height, rotation)

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

    @UPPER_MANE_MATERIALS = {i, [val1 for val1 in *val] for i, val in pairs _M.UPPER_MANE_DETAILS}
    @LOWER_MANE_MATERIALS = {i, [val1 for val1 in *val] for i, val in pairs _M.LOWER_MANE_DETAILS}
    @TAIL_DETAIL_MATERIALS = {i, [val1 for val1 in *val] for i, val in pairs _M.TAIL_DETAILS}

    @HAIR_MATERIAL_COLOR = _M.HAIR_MATERIAL_COLOR
    @TAIL_MATERIAL_COLOR = _M.TAIL_MATERIAL_COLOR
    @WINGS_MATERIAL_COLOR = _M.WINGS_MATERIAL_COLOR
    @HORN_MATERIAL_COLOR = _M.HORN_MATERIAL_COLOR
    @BODY_MATERIAL_MALE = _M.BODY_MATERIAL_MALE
    @BODY_MATERIAL_FEMALE = _M.BODY_MATERIAL_FEMALE
    @HORN_DETAIL_COLOR = _M.HORN_DETAIL_COLOR
    @EYE_OVAL = _M.EYE_OVAL
    @EYE_OVALS = _M.EYE_OVALS
    @EYE_GRAD = _M.EYE_GRAD
    @EYE_EFFECT = _M.EYE_EFFECT
    @EYE_REFLECTION = _M.EYE_REFLECTION
    @EYE_LINE_L_1 = _M.EYE_LINE_L_1
    @EYE_LINE_R_1 = _M.EYE_LINE_R_1
    @EYE_LINE_L_2 = _M.EYE_LINE_L_2
    @EYE_LINE_R_2 = _M.EYE_LINE_R_2
    @PONY_SOCKS = _M.PONY_SOCKS

    @SessionID = math.random(1, 1000)

    @MAT_INDEX_EYE_LEFT = 0
    @MAT_INDEX_EYE_RIGHT = 1
    @MAT_INDEX_BODY = 2
    @MAT_INDEX_HORN = 3
    @MAT_INDEX_WINGS = 4
    @MAT_INDEX_HAIR_COLOR1 = 5
    @MAT_INDEX_HAIR_COLOR2 = 6
    @MAT_INDEX_TAIL_COLOR1 = 7
    @MAT_INDEX_TAIL_COLOR2 = 8
    @MAT_INDEX_CMARK = 9
    @MAT_INDEX_EYELASHES = 10

    @NEXT_GENERATED_ID = 10000

    @MANE_UPDATE_TRIGGER = {'ManeType': true, 'ManeTypeLower': true}
    @TAIL_UPDATE_TRIGGER = {'TailType': true}
    @EYE_UPDATE_TRIGGER = {'SeparateEyes': true}
    @PHONG_UPDATE_TRIGGER = {
        'SeparateHornPhong': true
        'SeparateWingsPhong': true
        'SeparateManePhong': true
        'SeparateTailPhong': true
    }

    for ttype in *{'Body', 'Horn', 'Wings', 'BatWingsSkin', 'Socks', 'Mane', 'Tail', 'UpperMane', 'LowerMane'}
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongExponent'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongBoost'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongTint'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongFront'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongMiddle'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongSliding'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'Lightwarp'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'LightwarpURL'] = true

    for publicName in *{'', 'Left', 'Right'}
        @EYE_UPDATE_TRIGGER["EyeType#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleWidth#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["IrisSize#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeLines#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleSize#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeBackground#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisTop#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisBottom#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisLine1#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisLine2#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeHole#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["DerpEyesStrength#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["DerpEyes#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeReflection#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeEffect#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeURL#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["IrisWidth#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["IrisHeight#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleHeight#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleShiftX#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleShiftY#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeRotation#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["PonySize#{publicName}"] = true

    for i = 1, 6
        @MANE_UPDATE_TRIGGER["ManeColor#{i}"] = true
        @MANE_UPDATE_TRIGGER["ManeDetailColor#{i}"] = true
        @MANE_UPDATE_TRIGGER["ManeURLColor#{i}"] = true
        @MANE_UPDATE_TRIGGER["ManeURL#{i}"] = true
        @MANE_UPDATE_TRIGGER["TailURL#{i}"] = true
        @MANE_UPDATE_TRIGGER["TailURLColor#{i}"] = true

        @TAIL_UPDATE_TRIGGER["TailColor#{i}"] = true
        @TAIL_UPDATE_TRIGGER["TailDetailColor#{i}"] = true

    @BODY_UPDATE_TRIGGER = {}
    for i = 1, PPM2.MAX_BODY_DETAILS
        @BODY_UPDATE_TRIGGER["BodyDetail#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailColor#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailURLColor#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailURL#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailGlow#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailGlowStrength#{i}"] = true

    for i = 1, PPM2.MAX_TATTOOS
        @BODY_UPDATE_TRIGGER["TattooType#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooPosX#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooPosY#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooRotate#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooScaleX#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooScaleY#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooColor#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooGlow#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooGlowStrength#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooOverDetail#{i}"] = true

    DelayCompile: (func = '', ...) =>
        return if not @[func]
        args = {...}
        for data in *@delayCompilation
            if data.func == func and #args == #data.args
                hit = false
                for i = 1, #args
                    if args[i] ~= data.args[i]
                        hit = true
                        break
                for i = 1, #data.args
                    if args[i] ~= data.args[i]
                        hit = true
                        break
                return if not hit

        table.insert(@delayCompilation, {:func, :args})
        timer.Create "#{@}_DelayCompile", 0, 1, ->
            return if not @IsValid()
            return if not IsValid(@ent)
            for data in *@delayCompilation
                @[data.func](@, unpack(data.args))
            @delayCompilation = {}

    DataChanges: (state) =>
        return unless @isValid
        return if not @ent
        key = state\GetKey()
        switch key
            when 'BodyColor'
                @DelayCompile('CompileBody')
                @DelayCompile('CompileWings')
                @DelayCompile('CompileHorn')
            when 'Socks', 'Bodysuit'
                @DelayCompile('CompileBody')
            when 'CMark', 'CMarkType', 'CMarkURL', 'CMarkColor', 'CMarkSize'
                @DelayCompile('CompileCMark')
            when 'SocksColor', 'SocksTextureURL', 'SocksTexture', 'SocksDetailColor1', 'SocksDetailColor2', 'SocksDetailColor3', 'SocksDetailColor4', 'SocksDetailColor5', 'SocksDetailColor6'
                @DelayCompile('CompileSocks')
            when 'HornURL1', 'SeparateHorn', 'HornColor', 'HornURL2', 'HornURL3', 'HornURLColor1', 'HornURLColor2', 'HornURLColor3', 'UseHornDetail', 'HornGlow', 'HornGlowSrength', 'HornDetailColor'
                @DelayCompile('CompileHorn')
            when 'WingsURL1', 'WingsURL2', 'WingsURL3', 'WingsURLColor1', 'WingsURLColor2', 'WingsURLColor3', 'SeparateWings', 'WingsColor'
                @DelayCompile('CompileWings')
            else
                if @@MANE_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileHair')
                elseif @@TAIL_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileTail')
                elseif @@EYE_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileEye', true)
                    @DelayCompile('CompileEye', false)
                elseif @@BODY_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileBody')
                elseif @@PHONG_UPDATE_TRIGGER[key]
                    @UpdatePhongData()

    @HTML_MATERIAL_QUEUE = {}
    @URL_MATERIAL_CACHE = {}
    @ALREADY_DOWNLOADING = {}
    @FAILED_TO_DOWNLOAD = {}
    @LoadURL: (url, width = @QUAD_SIZE_CONST, height = @QUAD_SIZE_CONST, callback = (->)) =>
        error('Must specify URL') if not url or url == ''
        @URL_MATERIAL_CACHE[width] = @URL_MATERIAL_CACHE[width] or {}
        @URL_MATERIAL_CACHE[width][height] = @URL_MATERIAL_CACHE[width][height] or {}
        @ALREADY_DOWNLOADING[width] = @ALREADY_DOWNLOADING[width] or {}
        @ALREADY_DOWNLOADING[width][height] = @ALREADY_DOWNLOADING[width][height] or {}
        @FAILED_TO_DOWNLOAD[width] = @FAILED_TO_DOWNLOAD[width] or {}
        @FAILED_TO_DOWNLOAD[width][height] = @FAILED_TO_DOWNLOAD[width][height] or {}
        if @FAILED_TO_DOWNLOAD[width][height][url]
            callback(@FAILED_TO_DOWNLOAD[width][height][url].texture, nil, @FAILED_TO_DOWNLOAD[width][height][url].material)
            return
        if @ALREADY_DOWNLOADING[width][height][url]
            for data in *@HTML_MATERIAL_QUEUE
                if data.url == url
                    table.insert(data.callbacks, callback)
                    break
            return
        if @URL_MATERIAL_CACHE[width][height][url]
            callback(@URL_MATERIAL_CACHE[width][height][url].texture, nil, @URL_MATERIAL_CACHE[width][height][url].material)
            return
        @ALREADY_DOWNLOADING[width][height][url] = true
        table.insert(@HTML_MATERIAL_QUEUE, {:url, :width, :height, callbacks: {callback}})
        --PPM2.Message 'Queuing to download ', url
    @BuildURLHTML = (url = 'https://dbot.serealia.ca/illuminati.jpg', width = @QUAD_SIZE_CONST, height = @QUAD_SIZE_CONST) =>
        url = url\Replace('%', '%25')\Replace(' ', '%20')\Replace('"', '%22')\Replace("'", '%27')\Replace('#', '%23')\Replace('<', '%3C')\Replace('=', '%3D')\Replace('>', '%3E')
        return "

                "

    @SHOULD_WAIT_WEB = false
    hook.Add 'Think', 'PPM2.WebMaterialThink', ->
        return if @SHOULD_WAIT_WEB
        data = @HTML_MATERIAL_QUEUE[1]
        return if not data
        if IsValid(data.panel)
            panel = data.panel
            return if panel\IsLoading()
            if data.timerid
                timer.Remove(data.timerid)
                data.timerid = nil
            return if data.frame < 20
            @SHOULD_WAIT_WEB = true
            timer.Simple 1, ->
                @SHOULD_WAIT_WEB = false
                return unless IsValid(panel)
                panel\UpdateHTMLTexture()
                htmlmat = panel\GetHTMLMaterial()
                return if not htmlmat
                texture = htmlmat\GetTexture('$basetexture')
                texture\Download()
                newMat = CreateMaterial("PPM2.URLMaterial.#{texture\GetName()}_#{math.random(1, 100000)}", 'UnlitGeneric', {
                    '$basetexture': 'models/debug/debugwhite'
                    '$ignorez': 1
                    '$vertexcolor': 1
                    '$vertexalpha': 1
                    '$nolod': 1
                })

                newMat\SetTexture('$basetexture', texture)
                @URL_MATERIAL_CACHE[data.width] = @URL_MATERIAL_CACHE[data.width] or {}
                @URL_MATERIAL_CACHE[data.width][data.height] = @URL_MATERIAL_CACHE[data.width][data.height] or {}
                @URL_MATERIAL_CACHE[data.width][data.height][data.url] = {
                    texture: texture
                    material: newMat
                }

                @ALREADY_DOWNLOADING[data.width][data.height][data.url] = false
                --PPM2.Message 'Finished downloading ', data.url

                for callback in *data.callbacks
                    callback(texture, panel, newMat)
                table.remove(@HTML_MATERIAL_QUEUE, 1)
                timer.Simple 0, -> panel\Remove() if IsValid(panel)
            return
        data.frame = 0
        panel = vgui.Create('DHTML')
        data.timerid = "PPM2.TextureMaterialTimeout.#{math.random(1, 100000)}"
        timer.Create data.timerid, 8, 1, ->
            return unless IsValid(panel)
            panel\Remove()
            --PPM2.Message 'Failed to download', data.url, '!'
            newMat = CreateMaterial("PPM2.URLMaterial_Failed_#{math.random(1, 100000)}", 'UnlitGeneric', {
                '$basetexture': 'models/ppm/partrender/null'
                '$ignorez': 1
                '$vertexcolor': 1
                '$vertexalpha': 1
                '$nolod': 1
                '$translucent': 1
            })

            @FAILED_TO_DOWNLOAD[data.width][data.height][data.url] = {
                texture: newMat\GetTexture('$basetexture')
                material: newMat
            }

            for callback in *data.callbacks
                callback(newMat\GetTexture('$basetexture'), nil, newMat)
        panel\SetVisible(false)
        panel\SetSize(data.width, data.height)
        panel\SetHTML(@BuildURLHTML(data.url, data.width, data.height))
        panel\Refresh()
        panel.ConsoleMessage = (pnl, msg) ->
            if msg == 'FRAME'
                data.frame += 1
        data.panel = panel
        --PPM2.Message 'Downloading ', data.url

    new: (controller, compile = true) =>
        @isValid = true
        @ent = controller\GetEntity()
        @cachedENT = controller\GetEntity()
        @networkedData = controller\GetData()
        @id = @ent\EntIndex()
        if @id == -1
            @clientsideID = true
            @id = @@NEXT_GENERATED_ID
            @@NEXT_GENERATED_ID += 1
        @compiled = false
        @lastMaterialUpdate = 0
        @lastMaterialUpdateEnt = NULL
        @delayCompilation = {}
        @CompileTextures() if compile
        PPM2.DebugPrint('Created new texture controller for ', @ent, ' as part of ', controller, '; internal ID is ', @id)
    __tostring: => "[#{@@__name}:#{@id}|#{@GetData()}]"
    Remove: =>
        @isValid = false
        @ResetTextures()
    IsValid: => IsValid(@ent) and @isValid and @compiled
    GetID: =>
        return @id if @clientsideID
        if @ent ~= @cachedENT
            @cachedENT = @ent
            @id = @ent\EntIndex()
            if @id == -1
                @id = @@NEXT_GENERATED_ID
                @@NEXT_GENERATED_ID += 1
        return @id
    GetData: =>
        @ent = @networkedData\GetEntity()
        return @networkedData
    GrabData: (str, ...) => @GetData()['Get' .. str](@GetData(), ...)
    GetEntity: => @ent
    GetBody: =>
        if @GrabData('Gender') == PPM2.GENDER_FEMALE
            return @FemaleMaterial
        else
            return @MaleMaterial
    GetBodyName: =>
        if @GrabData('Gender') == PPM2.GENDER_FEMALE
            return @FemaleMaterialName
        else
            return @MaleMaterialName
    GetSocks: => @SocksMaterial
    GetSocksName: => @SocksMaterialName
    GetCMark: => @CMarkTexture
    GetCMarkName: => @CMarkTextureName
    GetGUICMark: => @CMarkTextureGUI
    GetGUICMarkName: => @CMarkTextureGUIName
    GetCMarkGUI: => @CMarkTextureGUI
    GetCMarkGUIName: => @CMarkTextureGUIName
    GetHair: (index = 1) =>
        if index == 2
            return @HairColor2Material
        else
            return @HairColor1Material
    GetHairName: (index = 1) =>
        if index == 2
            return @HairColor2MaterialName
        else
            return @HairColor1MaterialName
    GetMane: (index = 1) =>
        if index == 2
            return @HairColor2Material
        else
            return @HairColor1Material
    GetManeName: (index = 1) =>
        if index == 2
            return @HairColor2MaterialName
        else
            return @HairColor1MaterialName
    GetTail: (index = 1) =>
        if index == 2
            return @TailColor2Material
        else
            return @TailColor1Material
    GetTailName: (index = 1) =>
        if index == 2
            return @TailColor2MaterialName
        else
            return @TailColor1MaterialName
    GetEye: (left = false) =>
        if left
            return @EyeMaterialL
        else
            return @EyeMaterialR
    GetEyeName: (left = false) =>
        if left
            return @EyeMaterialLName
        else
            return @EyeMaterialRName
    GetHorn: => @HornMaterial
    GetHornName: => @HornMaterialName
    GetWings: => @WingsMaterial
    GetWingsName: => @WingsMaterialName
    CompileTextures: =>
        @CompileBody()
        @CompileHair()
        @CompileTail()
        @CompileHorn()
        @CompileWings()
        @CompileCMark()
        @CompileSocks()
        @CompileEye(false)
        @CompileEye(true)
        @compiled = true

    PreDraw: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        if @lastMaterialUpdate < RealTime() or @lastMaterialUpdateEnt ~= ent or PPM2.ALTERNATIVE_RENDER\GetBool()
            @lastMaterialUpdateEnt = ent
            @lastMaterialUpdate = RealTime() + 1
            ent\SetSubMaterial(@@MAT_INDEX_EYE_LEFT, @GetEyeName(true))
            ent\SetSubMaterial(@@MAT_INDEX_EYE_RIGHT, @GetEyeName(false))
            ent\SetSubMaterial(@@MAT_INDEX_BODY, @GetBodyName())
            ent\SetSubMaterial(@@MAT_INDEX_HORN, @GetHornName())
            ent\SetSubMaterial(@@MAT_INDEX_WINGS, @GetWingsName())
            ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR1, @GetHairName(1))
            ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR2, @GetHairName(2))
            ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR1, @GetTailName(1))
            ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR2, @GetTailName(2))
            ent\SetSubMaterial(@@MAT_INDEX_CMARK, @GetCMarkName())
            ent\SetSubMaterial(@@MAT_INDEX_EYELASHES)
        if PPM2.ALTERNATIVE_RENDER\GetBool()
            render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_LEFT, @GetEye(true))
            render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_RIGHT, @GetEye(false))
            render.MaterialOverrideByIndex(@@MAT_INDEX_BODY, @GetBody())
            render.MaterialOverrideByIndex(@@MAT_INDEX_HORN, @GetHorn())
            render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS, @GetWings())
            render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR1, @GetHair(1))
            render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR2, @GetHair(2))
            render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR1, @GetTail(1))
            render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR2, @GetTail(2))
            render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK, @GetCMark())
            render.MaterialOverrideByIndex(@@MAT_INDEX_EYELASHES)

    PostDraw: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        return if not PPM2.ALTERNATIVE_RENDER\GetBool()
        render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_LEFT)
        render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_RIGHT)
        render.MaterialOverrideByIndex(@@MAT_INDEX_BODY)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HORN)
        render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR1)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR2)
        render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR1)
        render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR2)
        render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK)

    ResetTextures: (ent = @ent) =>
        return if not IsValid(ent)
        @lastMaterialUpdateEnt = NULL
        @lastMaterialUpdate = 0
        ent\SetSubMaterial(@@MAT_INDEX_EYE_LEFT)
        ent\SetSubMaterial(@@MAT_INDEX_EYE_RIGHT)
        ent\SetSubMaterial(@@MAT_INDEX_BODY)
        ent\SetSubMaterial(@@MAT_INDEX_HORN)
        ent\SetSubMaterial(@@MAT_INDEX_WINGS)
        ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR1)
        ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR2)
        ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR1)
        ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR2)
        ent\SetSubMaterial(@@MAT_INDEX_CMARK)
        ent\SetSubMaterial(@@MAT_INDEX_EYELASHES)

    PreDrawLegs: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        render.MaterialOverrideByIndex(@@MAT_INDEX_BODY, @GetBody())
        render.MaterialOverrideByIndex(@@MAT_INDEX_HORN, @GetHorn())
        render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS, @GetWings())
        render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK, @GetCMark())

    PostDrawLegs: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        render.MaterialOverrideByIndex(@@MAT_INDEX_BODY)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HORN)
        render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS)
        render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK)

    @MAT_INDEX_SOCKS = 0

    UpdateSocks: (ent = @ent, socksEnt) =>
        return unless @compiled
        return unless @isValid
        socksEnt\SetSubMaterial(@@MAT_INDEX_SOCKS, @GetSocksName())

    @QUAD_SIZE_EYES = 256
    @QUAD_SIZE_EYES_HIRES = 512

    @QUAD_SIZE_SOCKS = 512
    @QUAD_SIZE_SOCKS_HIRES = 1024

    @QUAD_SIZE_CMARK = 256
    @QUAD_SIZE_CMARK_HIRES = 512

    @QUAD_SIZE_WING = 128
    @QUAD_SIZE_WING_HIRES = 256

    @QUAD_SIZE_HORN = 128
    @QUAD_SIZE_HORN_HIRES = 256

    @QUAD_SIZE_HAIR = 256
    @QUAD_SIZE_HAIR_HIRES = 512

    @QUAD_SIZE_TAIL = 256
    @QUAD_SIZE_TAIL_HIRES = 512

    @QUAD_SIZE_CONST = 512
    @QUAD_SIZE_CONST_HIRES = 1024

    @QUAD_SIZE_BODY = 1024
    @QUAD_SIZE_BODY_HIRES = 2048
    @QUAD_SIZE_BODY_HIRES2 = 4096

    @TATTOO_DEF_SIZE = 128
    @TATTOO_MEDIUM_SIZE = 256
    @TATTOO_BIG_SIZE = 512

    @GetTextureSize = => USE_HIGHRES_TEXTURES\GetBool() and @QUAD_SIZE_CONST_HIRES or @QUAD_SIZE_CONST
    @GetBodySize = => USE_HIGHRES_BODY\GetBool() and @QUAD_SIZE_BODY_HIRES2 or USE_HIGHRES_TEXTURES\GetBool() and @QUAD_SIZE_BODY_HIRES or @QUAD_SIZE_BODY

    DrawTattoo: (index = 1, drawingGlow = false, texSize = @@GetBodySize()) =>
        mat = _M.TATTOOS[@GrabData("TattooType#{index}")]
        return if not mat
        X, Y = @GrabData("TattooPosX#{index}"), @GrabData("TattooPosY#{index}")
        TattooRotate = @GrabData("TattooRotate#{index}")
        TattooScaleX = @GrabData("TattooScaleX#{index}")
        TattooScaleY = @GrabData("TattooScaleY#{index}")
        if not drawingGlow
            {:r, :g, :b} = @GrabData("TattooColor#{index}")
            surface.SetDrawColor(r, g, b)
        else
            if @GrabData("TattooGlow#{index}")
                surface.SetDrawColor(255, 255, 255, 255 * @GrabData("TattooGlowStrength#{index}"))
            else
                surface.SetDrawColor(0, 0, 0)
        surface.SetMaterial(mat)
        tSize = USE_HIGHRES_BODY\GetBool() and @@TATTOO_BIG_SIZE or USE_HIGHRES_TEXTURES\GetBool() and @@TATTOO_MEDIUM_SIZE or @@TATTOO_DEF_SIZE
        sizeX, sizeY = tSize * TattooScaleX, tSize * TattooScaleY
        surface.DrawTexturedRectRotated((X * texSize / 2) / 100 + texSize / 2, -(Y * texSize / 2) / 100 + texSize / 2, sizeX, sizeY, TattooRotate)

    GetPhongData: (prefix = 'Body') =>
        PhongExponent = @GrabData(prefix .. 'PhongExponent')
        PhongBoost = @GrabData(prefix .. 'PhongBoost')
        PhongTint = @GrabData(prefix .. 'PhongTint')
        PhongFront = @GrabData(prefix .. 'PhongFront')
        PhongMiddle = @GrabData(prefix .. 'PhongMiddle')
        Lightwarp = @GrabData(prefix .. 'Lightwarp')
        LightwarpURL = @GrabData(prefix .. 'LightwarpURL')
        PhongSliding = @GrabData(prefix .. 'PhongSliding')
        {:r, :g, :b} = PhongTint
        r /= 255
        g /= 255
        b /= 255
        PhongTint = Vector(r, g, b)
        PhongFresnel = Vector(PhongFront, PhongMiddle, PhongSliding)
        return PhongExponent, PhongBoost, PhongTint, PhongFresnel, Lightwarp, LightwarpURL

    ApplyPhongData: (matTarget, prefix = 'Body') =>
        return if not matTarget
        PhongExponent, PhongBoost, PhongTint, PhongFresnel, Lightwarp, LightwarpURL = @GetPhongData(prefix)
        with matTarget
            \SetFloat('$phongexponent', PhongExponent)
            \SetFloat('$phongboost', PhongBoost)
            \SetVector('$phongtint', PhongTint)
            \SetVector('$phongfresnelranges', PhongFresnel)

        if LightwarpURL == '' or not LightwarpURL\find('^https?://')
            myTex = PPM2.AvaliableLightwarpsPaths[Lightwarp + 1] or PPM2.AvaliableLightwarpsPaths[1]
            matTarget\SetTexture('$lightwarptexture', myTex)
        else
            @@LoadURL LightwarpURL, 256, 16, (tex, panel, mat) ->
                matTarget\SetTexture('$lightwarptexture', tex)

    GetBodyPhongMaterials: (output = {}) =>
        table.insert(output, @MaleMaterial) if @MaleMaterial
        table.insert(output, @FemaleMaterial) if @FemaleMaterial
        table.insert(output, @HornMaterial) if @HornMaterial and not @GrabData('SeparateHornPhong')
        table.insert(output, @WingsMaterial) if @WingsMaterial and not @GrabData('SeparateWingsPhong')
        if not @GrabData('SeparateManePhong')
            table.insert(output, @HairColor1Material) if @HairColor1Material
            table.insert(output, @HairColor2Material) if @HairColor2Material
        if not @GrabData('SeparateTailPhong')
            table.insert(output, @TailColor1Material) if @TailColor1Material
            table.insert(output, @TailColor2Material) if @TailColor2Material

    UpdatePhongData: =>
        proceed = {}
        @GetBodyPhongMaterials(proceed)
        for mat in *proceed
            @ApplyPhongData(mat, 'Body')

        if @GrabData('SeparateHornPhong') and @HornMaterial
            @ApplyPhongData(@HornMaterial, 'Horn')

        if @GrabData('SeparateWingsPhong') and @WingsMaterial
            @ApplyPhongData(@WingsMaterial, 'Wings')

        if @SocksMaterial
            @ApplyPhongData(@SocksMaterial, 'Socks')

        if @GrabData('SeparateManePhong')
            @ApplyPhongData(@HairColor1Material, 'Mane')
            @ApplyPhongData(@HairColor2Material, 'Mane')

        if @GrabData('SeparateTailPhong')
            @ApplyPhongData(@TailColor1Material, 'Tail')
            @ApplyPhongData(@TailColor2Material, 'Tail')

    __compileBodyInternal: (bType = false) =>
        return unless @isValid
        prefix = bType and 'Female' or 'Male'
        prefixUP = bType and 'FEMALE' or 'MALE'
        urlTextures = {}
        left = 0
        bodysize = @@GetBodySize()

        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Body_#{prefix}"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/bodym'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
                '$selfillum': '1'
                '$selfillummask': 'models/ppm/partrender/null'

                '$color': '{255 255 255}'
                '$color2': '{255 255 255}'
                '$model': '1'
                '$phong': '1'
                '$basemapalphaphongmask': '1'
                '$phongexponent': '3'
                '$phongboost': '0.15'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'

                '$rimlight': '1'
                '$rimlightexponent': '2'
                '$rimlightboost': '1'
            }
        }

        textureData.data['$basetexture'] = 'models/ppm/base/body' if not bType

        @["#{prefix}MaterialName"] = "!#{textureData.name\lower()}"
        @["#{prefix}Material"] = CreateMaterial(textureData.name, textureData.shader, textureData.data)

        continueCompilation = ->
            return unless @isValid
            {:r, :g, :b} = @GrabData('BodyColor')
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_Body_#{prefix}_rt_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", bodysize, bodysize, false)
            rt\Download()

            render.PushRenderTarget(rt)

            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.DisableClipping(true)
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, bodysize, bodysize)

            surface.SetMaterial(@@["BODY_MATERIAL_#{prefixUP}"])
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            for i = 1, PPM2.MAX_TATTOOS
                if not @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i)

            for i = 1, PPM2.MAX_BODY_DETAILS
                detailID = @GetData()["GetBodyDetail#{i}"](@GetData())
                if mat = _M.BODY_DETAILS[detailID]
                    surface.SetDrawColor(@GetData()["GetBodyDetailColor#{i}"](@GetData()))
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            surface.SetDrawColor(255, 255, 255)

            for i, mat in pairs urlTextures
                {:r, :g, :b, :a} = @GetData()["GetBodyDetailURLColor#{i}"](@GetData())
                surface.SetDrawColor(r, g, b, a)
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            for i = 1, PPM2.MAX_TATTOOS
                if @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i)

            if suit = _M.SUITS[@GrabData('Bodysuit')]
                surface.SetDrawColor(255, 255, 255)
                surface.SetMaterial(suit)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            if @GrabData('Socks')
                surface.SetDrawColor(255, 255, 255)
                surface.SetMaterial(@@PONY_SOCKS)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            surface.DisableClipping(false)
            cam.End2D()
            render.PopRenderTarget()

            @["#{prefix}Material"]\SetTexture('$basetexture', rt)

            rtIllum = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_Body_#{prefix}_rtIllum_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", bodysize, bodysize, false)
            rtIllum\Download()

            render.PushRenderTarget(rtIllum)

            r, g, b = 0, 0, 0
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.DisableClipping(true)
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, bodysize, bodysize)

            surface.SetDrawColor(255, 255, 255)

            for i = 1, PPM2.MAX_TATTOOS
                if not @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i, true)

            for i = 1, PPM2.MAX_BODY_DETAILS
                if mat = _M.BODY_DETAILS[@GetData()["GetBodyDetail#{i}"](@GetData())]
                    alpha = @GetData()["GetBodyDetailGlowStrength#{i}"](@GetData())

                    if @GetData()["GetBodyDetailGlow#{i}"](@GetData())
                        surface.SetDrawColor(255, 255, 255, alpha * 255)
                        surface.SetMaterial(mat)
                        surface.DrawTexturedRect(0, 0, bodysize, bodysize)
                    else
                        surface.SetDrawColor(0, 0, 0, alpha * 255)
                        surface.SetMaterial(mat)
                        surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            for i = 1, PPM2.MAX_TATTOOS
                if @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i, true)

            surface.DisableClipping(false)
            cam.End2D()
            render.PopRenderTarget()

            @["#{prefix}Material"]\SetTexture('$selfillummask', rtIllum)

            PPM2.DebugPrint('Compiled body texture for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, PPM2.MAX_BODY_DETAILS
            detailURL = data["GetBodyDetailURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, bodysize, bodysize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()

        if left == 0
            continueCompilation()
        return @["#{prefix}Material"]
    CompileBody: =>
        return unless @isValid
        @__compileBodyInternal(true)
        @__compileBodyInternal(false)
        @UpdatePhongData()
    CompileHorn: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Horn"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/horn'
                '$selfillum': '1'
                '$selfillummask': 'models/ppm/partrender/null'

                '$model': '1'
                '$phong': '1'
                '$phongexponent': '3'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'
                '$alpha': '1'
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_HORN_HIRES or @@QUAD_SIZE_HORN
        urlTextures = {}
        left = 0

        @HornMaterialName = "!#{textureData.name\lower()}"
        @HornMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @UpdatePhongData()

        continueCompilation = ->
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Horn_rt", texSize, texSize, false)
            rt\Download()

            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)
            {:r, :g, :b} = @GrabData('BodyColor')
            {:r, :g, :b} = @GrabData('HornColor') if @GrabData('SeparateHorn')
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@HORN_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            if @GrabData('UseHornDetail')
                {:r, :g, :b} = @GrabData('HornDetailColor')
                surface.SetDrawColor(r, g, b)
                surface.SetMaterial(@@HORN_DETAIL_COLOR)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            for i, mat in pairs urlTextures
                {:r, :g, :b, :a} = @GetData()["GetHornURLColor#{i}"](@GetData())
                surface.SetDrawColor(r, g, b, a)
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            @HornMaterial\SetTexture('$basetexture', rt)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()

            rtIllum = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Horn_rtIllum", texSize, texSize, false)
            rtIllum\Download()

            render.PushRenderTarget(rtIllum)

            r, g, b = 0, 0, 0
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.DisableClipping(true)
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            if @GrabData('HornGlow')
                surface.SetDrawColor(255, 255, 255, @GrabData('HornGlowSrength') * 255)
                surface.SetMaterial(@@HORN_DETAIL_COLOR)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            surface.DisableClipping(false)
            cam.End2D()
            render.PopRenderTarget()

            @HornMaterial\SetTexture('$selfillummask', rtIllum)

            PPM2.DebugPrint('Compiled Horn texture for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 3
            detailURL = data["GetHornURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()
        return @HornMaterial
    CompileSocks: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Socks"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/props_pony/ppm/ppm_socks/socks_striped'

                '$model': '1'
                '$ambientocclusion': '1'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$phong': '1'
                '$phongexponent': '6'
                '$phongboost': '0.1'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[1 5 10]'
                '$rimlight': '1'
                '$rimlightexponent': '4.0'
                '$rimlightboost': '2'
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
                '$cloakPassEnabled': '1'
            }
        }

        @SocksMaterialName = "!#{textureData.name\lower()}"
        @SocksMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @UpdatePhongData()
        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_SOCKS_HIRES or @@QUAD_SIZE_SOCKS

        {:r, :g, :b} = @GrabData('SocksColor')
        @SocksMaterial\SetFloat('$alpha', 1)

        url = @GrabData('SocksTextureURL')
        if url == '' or not url\find('^https?://')
            @SocksMaterial\SetVector('$color', Vector(1, 1, 1))
            @SocksMaterial\SetVector('$color2', Vector(1, 1, 1))
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Socks_rt", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            socksType = @GrabData('SocksTexture') + 1
            surface.SetMaterial(_M.SOCKS_MATERIALS[socksType] or _M.SOCKS_MATERIALS[1])
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            if details = _M.SOCKS_DETAILS[socksType]
                for i, id in pairs details
                    {:r, :g, :b} = @GetData()['GetSocksDetailColor' .. i](@GetData())
                    surface.SetDrawColor(r, g, b)
                    surface.SetMaterial(id)
                    surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.PopRenderTarget()

            @SocksMaterial\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled socks texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL url, texSize, texSize, (texture, panel, material) ->
                @SocksMaterial\SetVector('$color', Vector(r / 255, g / 255, b / 255))
                @SocksMaterial\SetVector('$color2', Vector(r / 255, g / 255, b / 255))
                @SocksMaterial\SetTexture('$basetexture', texture)

        return @SocksMaterial
    CompileWings: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Wings"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/debug/debugwhite'

                '$model': '1'
                '$phong': '1'
                '$phongexponent': '3'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'
                '$alpha': '1'
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        urlTextures = {}
        left = 0
        @WingsMaterialName = "!#{textureData.name\lower()}"
        @WingsMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @UpdatePhongData()

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_WING_HIRES or @@QUAD_SIZE_WING

        continueCompilation = ->
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Wings_rt", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)
            {:r, :g, :b} = @GrabData('BodyColor')
            {:r, :g, :b} = @GrabData('WingsColor') if @GrabData('SeparateWings')
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@WINGS_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            for i, mat in pairs urlTextures
                {:r, :g, :b, :a} = @GetData()["GetWingsURLColor#{i}"](@GetData())
                surface.SetDrawColor(r, g, b, a)
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            @WingsMaterial\SetTexture('$basetexture', rt)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()

            PPM2.DebugPrint('Compiled wings texture for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 3
            detailURL = data["GetWingsURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()

        return @WingsMaterial

    GetManeType: => @GrabData('ManeType')
    GetManeTypeLower: => @GrabData('ManeTypeLower')
    GetTailType: => @GrabData('TailType')
    CompileHair: =>
        return unless @isValid
        textureFirst = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Mane_1"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/debug/debugwhite'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
                '$model': '1'
                '$phong': '1'
                '$basemapalphaphongmask': '1'
                '$phongexponent': '6'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'

                '$rimlight': 1
                '$rimlightexponent': 2
                '$rimlightboost': 1
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        textureSecond = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Mane_2"
            'shader': 'VertexLitGeneric'
            'data': {k, v for k, v in pairs textureFirst.data}
        }

        @HairColor1MaterialName = "!#{textureFirst.name\lower()}"
        @HairColor2MaterialName = "!#{textureSecond.name\lower()}"
        @HairColor1Material = CreateMaterial(textureFirst.name, textureFirst.shader, textureFirst.data)
        @HairColor2Material = CreateMaterial(textureSecond.name, textureSecond.shader, textureSecond.data)

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_HAIR_HIRES or @@QUAD_SIZE_HAIR

        urlTextures = {}
        left = 0

        continueCompilation = ->
            return unless @isValid
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Mane_rt_1", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            -- First mane pass
            {:r, :g, :b} = @GrabData('ManeColor1')
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            maneTypeUpper = @GetManeType()
            if @@UPPER_MANE_MATERIALS[maneTypeUpper]
                i = 1
                for mat in *@@UPPER_MANE_MATERIALS[maneTypeUpper]
                    continue if type(mat) == 'number'
                    {:r, :g, :b, :a} = @GetData()["GetManeDetailColor#{i}"](@GetData())
                    surface.SetDrawColor(r, g, b, a)
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetManeURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @HairColor1Material\SetTexture('$basetexture', rt)

            -- Second mane pass
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Mane_rt_2", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b} = @GrabData('ManeColor2')
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            maneTypeLower = @GetManeTypeLower()
            if @@LOWER_MANE_MATERIALS[maneTypeLower]
                i = 1
                for mat in *@@LOWER_MANE_MATERIALS[maneTypeLower]
                    continue if type(mat) == 'number'
                    {:r, :g, :b, :a} = @GetData()["GetManeDetailColor#{i}"](@GetData())
                    surface.SetDrawColor(r, g, b, a)
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetManeURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @HairColor2Material\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled mane textures for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 6
            detailURL = data["GetManeURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()
        return @HairColor1Material, @HairColor2Material
    CompileTail: =>
        return unless @isValid
        textureFirst = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Tail_1"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/debug/debugwhite'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
                '$model': '1'
                '$phong': '1'
                '$basemapalphaphongmask': '1'
                '$phongexponent': '6'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'

                '$rimlight': 1
                '$rimlightexponent': 2
                '$rimlightboost': 1
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        textureSecond = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Tail_2"
            'shader': 'VertexLitGeneric'
            'data': {k, v for k, v in pairs textureFirst.data}
        }

        @TailColor1MaterialName = "!#{textureFirst.name\lower()}"
        @TailColor2MaterialName = "!#{textureSecond.name\lower()}"
        @TailColor1Material = CreateMaterial(textureFirst.name, textureFirst.shader, textureFirst.data)
        @TailColor2Material = CreateMaterial(textureSecond.name, textureSecond.shader, textureSecond.data)

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_TAIL_HIRES or @@QUAD_SIZE_TAIL

        urlTextures = {}
        left = 0

        continueCompilation = ->
            return unless @isValid
            oldW, oldH = ScrW(), ScrH()

            -- First tail pass
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Tail_rt_1", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b} = @GrabData('TailColor1')
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@HAIR_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            tailType = @GetTailType()
            if @@TAIL_DETAIL_MATERIALS[tailType]
                i = 1
                for mat in *@@TAIL_DETAIL_MATERIALS[tailType]
                    continue if type(mat) == 'number'
                    surface.SetMaterial(mat)
                    surface.SetDrawColor(@GetData()["GetTailDetailColor#{i}"](@GetData()))
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetTailURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @TailColor1Material\SetTexture('$basetexture', rt)

            -- Second tail pass
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Tail_rt_2", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b} = @GrabData('TailColor2')
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@HAIR_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            if @@TAIL_DETAIL_MATERIALS[tailType]
                i = 1
                for mat in *@@TAIL_DETAIL_MATERIALS[tailType]
                    continue if type(mat) == 'number'
                    surface.SetMaterial(mat)
                    surface.SetDrawColor(@GetData()["GetTailDetailColor#{i}"](@GetData()))
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetTailURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @TailColor2Material\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled tail textures for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 6
            detailURL = data["GetTailURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()
        return @TailColor1Material, @TailColor2Material
    CompileEye: (left = false) =>
        return unless @isValid
        prefix = left and 'l' or 'r'
        prefixUpper = left and 'L' or 'R'

        separated = @GrabData('SeparateEyes')
        prefixData = ''
        prefixData = left and 'Left' or 'Right' if separated

        EyeType =           @GrabData("EyeType#{prefixData}")
        EyeBackground =     @GrabData("EyeBackground#{prefixData}")
        EyeHole =           @GrabData("EyeHole#{prefixData}")
        HoleWidth =         @GrabData("HoleWidth#{prefixData}")
        IrisSize =          @GrabData("IrisSize#{prefixData}") * .75
        EyeIris1 =          @GrabData("EyeIrisTop#{prefixData}")
        EyeIris2 =          @GrabData("EyeIrisBottom#{prefixData}")
        EyeIrisLine1 =      @GrabData("EyeIrisLine1#{prefixData}")
        EyeIrisLine2 =      @GrabData("EyeIrisLine2#{prefixData}")
        EyeLines =          @GrabData("EyeLines#{prefixData}")
        HoleSize =          @GrabData("HoleSize#{prefixData}")
        EyeReflection =     @GrabData("EyeReflection#{prefixData}")
        EyeEffect =         @GrabData("EyeEffect#{prefixData}")
        DerpEyes =          @GrabData("DerpEyes#{prefixData}")
        DerpEyesStrength =  @GrabData("DerpEyesStrength#{prefixData}")
        EyeURL =            @GrabData("EyeURL#{prefixData}")
        IrisWidth =         @GrabData("IrisWidth#{prefixData}")
        IrisHeight =        @GrabData("IrisHeight#{prefixData}")
        HoleHeight =        @GrabData("HoleHeight#{prefixData}")
        HoleShiftX =        @GrabData("HoleShiftX#{prefixData}")
        HoleShiftY =        @GrabData("HoleShiftY#{prefixData}")
        EyeRotation =       @GrabData("EyeRotation#{prefixData}")
        PonySize =          @GrabData('PonySize')
        PonySize = 1 if IsValid(@ent) and @ent\IsRagdoll()

        oldW, oldH = ScrW(), ScrH()

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_EYES_HIRES or @@QUAD_SIZE_EYES

        shiftX, shiftY = (1 - IrisWidth) * texSize / 2, (1 - IrisHeight) * texSize / 2
        shiftY += DerpEyesStrength * .15 * texSize if DerpEyes and left
        shiftY -= DerpEyesStrength * .15 * texSize if DerpEyes and not left

        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Eye_#{prefix}"
            'shader': 'eyes'
            'data': {
                '$iris': 'models/ppm/base/face/p_base'
                '$irisframe': '0'

                '$ambientoccltexture': 'models/ppm/partrender/null'
                --'$envmap': 'models/ppm/partrender/null'
                '$corneatexture': 'models/ppm/partrender/null'
                '$lightwarptexture': 'models/ppm/clothes/lightwarp'

                '$eyeballradius': '3.7'
                '$ambientocclcolor': '[0.3 0.3 0.3]'
                '$dilation': '0.5'
                '$glossiness': '1'
                '$parallaxstrength': '0.1'
                '$corneabumpstrength': '0.1'

                '$halflambert': '1'
                '$nodecal': '1'

                '$raytracesphere': '0'
                '$spheretexkillcombo': '0'
                '$eyeorigin': '[0 0 0]'
                '$irisu': '[0 1 0 0]'
                '$irisv': '[0 0 1 0]'
                '$entityorigin': '1.0'
            }
        }

        @["EyeMaterial#{prefixUpper}Name"] = "!#{textureData.name\lower()}"
        createdMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @["EyeMaterial#{prefixUpper}"] = createdMaterial

        if EyeURL == '' or not EyeURL\find('^https?://')
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Eye_#{prefix}", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b, :a} = EyeBackground
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetDrawColor(EyeIris1)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            IrisPos = texSize / 2 - texSize * IrisSize * PonySize / 2
            IrisQuadSize = texSize * IrisSize * PonySize
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeIris2)
            surface.SetMaterial(@@EYE_GRAD)
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            if EyeLines
                surface.SetDrawColor(EyeIrisLine1)
                surface.SetMaterial(@@["EYE_LINE_#{prefixUpper}_1"])
                DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

                surface.SetDrawColor(EyeIrisLine2)
                surface.SetMaterial(@@["EYE_LINE_#{prefixUpper}_2"])
                DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeHole)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            HoleQuadSize = texSize * IrisSize * HoleSize * PonySize
            HolePos = texSize / 2
            do
                holeX = HoleQuadSize * HoleWidth / 2
                holeY = texSize * (IrisSize * HoleSize * HoleHeight * PonySize) / 2
                cx = HolePos - holeX + holeX * HoleShiftX + shiftX
                cy = HolePos - holeY + holeY * HoleShiftY + shiftY
                DrawTexturedRectRotated(cx, cy, HoleQuadSize * HoleWidth * IrisWidth, HoleQuadSize * HoleHeight * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeEffect)
            surface.SetMaterial(@@EYE_EFFECT)
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeReflection)
            surface.SetMaterial(@@EYE_REFLECTION)
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @["EyeMaterial#{prefixUpper}"]\SetTexture('$iris', rt)

            PPM2.DebugPrint('Compiled eyes texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL EyeURL, texSize, texSize, (texture, panel, material) ->
                @["EyeMaterial#{prefixUpper}"]\SetTexture('$iris', texture)
        return @["EyeMaterial#{prefixUpper}"]
    CompileCMark: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_CMark"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/partrender/null'
                '$translucent': '1'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
            }
        }

        textureDataGUI = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_CMark_GUI"
            'shader': 'UnlitGeneric'
            'data': {
                '$basetexture': 'models/ppm/partrender/null'
                '$translucent': '1'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
            }
        }

        @CMarkTextureName = "!#{textureData.name\lower()}"
        @CMarkTexture = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @CMarkTextureGUIName = "!#{textureDataGUI.name\lower()}"
        @CMarkTextureGUI = CreateMaterial(textureDataGUI.name, textureDataGUI.shader, textureDataGUI.data)

        unless @GrabData('CMark')
            @CMarkTexture\SetTexture('$basetexture', 'models/ppm/partrender/null')
            @CMarkTextureGUI\SetTexture('$basetexture', 'models/ppm/partrender/null')
            return @CMarkTexture, @CMarkTextureGUI

        URL = @GrabData('CMarkURL')
        size = @GrabData('CMarkSize')

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_CMARK_HIRES or @@QUAD_SIZE_CMARK
        sizeQuad = texSize * size
        shift = (texSize - sizeQuad) / 2

        if URL == '' or not URL\find('^https?://')
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_CMark", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)
            render.Clear(0, 0, 0, 0, true, true)
            cam.Start2D()

            if mark = _M.CUTIEMARKS[@GrabData('CMarkType') + 1]
                surface.SetDrawColor(@GrabData('CMarkColor'))
                surface.SetMaterial(mark)
                surface.DrawTexturedRect(shift, shift, sizeQuad, sizeQuad)

            cam.End2D()
            render.PopRenderTarget()
            render.SetViewPort(0, 0, oldW, oldH)

            @CMarkTexture\SetTexture('$basetexture', rt)
            @CMarkTextureGUI\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled cutiemark texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL URL, texSize, texSize, (texture, panel, material) ->
                oldW, oldH = ScrW(), ScrH()

                rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_CMark", texSize, texSize, false)
                rt\Download()
                render.PushRenderTarget(rt)
                render.SetViewPort(0, 0, texSize, texSize)
                render.Clear(0, 0, 0, 0, true, true)
                cam.Start2D()

                surface.SetDrawColor(@GrabData('CMarkColor'))
                surface.SetMaterial(material)
                surface.DrawTexturedRect(shift, shift, sizeQuad, sizeQuad)

                cam.End2D()
                render.PopRenderTarget()
                render.SetViewPort(0, 0, oldW, oldH)

                @CMarkTexture\SetTexture('$basetexture', rt)
                @CMarkTextureGUI\SetTexture('$basetexture', rt)

                PPM2.DebugPrint('Compiled cutiemark texture for ', @ent, ' as part of ', @)

        return @CMarkTexture, @CMarkTextureGUI

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

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

-- Texture indexes (-1)
-- 1    =   models/ppm/base/eye_l
-- 2    =   models/ppm/base/eye_r
-- 3    =   models/ppm/base/body
-- 4    =   models/ppm/base/horn
-- 5    =   models/ppm/base/wings
-- 6    =   models/ppm/base/hair_color_1
-- 7    =   models/ppm/base/hair_color_2
-- 8    =   models/ppm/base/tail_color_1
-- 9    =   models/ppm/base/tail_color_2
-- 10   =   models/ppm/base/cmark
-- 11   =   models/ppm/base/eyelashes

_M = PPM2.MaterialsRegistry
USE_HIGHRES_BODY = PPM2.USE_HIGHRES_BODY
USE_HIGHRES_TEXTURES = PPM2.USE_HIGHRES_TEXTURES

DrawTexturedRectRotated = (x = 0, y = 0, width = 0, height = 0, rotation = 0) -> surface.DrawTexturedRectRotated(x + width / 2, y + height / 2, width, height, rotation)

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

    @UPPER_MANE_MATERIALS = {i, [val1 for val1 in *val] for i, val in pairs _M.UPPER_MANE_DETAILS}
    @LOWER_MANE_MATERIALS = {i, [val1 for val1 in *val] for i, val in pairs _M.LOWER_MANE_DETAILS}
    @TAIL_DETAIL_MATERIALS = {i, [val1 for val1 in *val] for i, val in pairs _M.TAIL_DETAILS}

    @HAIR_MATERIAL_COLOR = _M.HAIR_MATERIAL_COLOR
    @TAIL_MATERIAL_COLOR = _M.TAIL_MATERIAL_COLOR
    @WINGS_MATERIAL_COLOR = _M.WINGS_MATERIAL_COLOR
    @HORN_MATERIAL_COLOR = _M.HORN_MATERIAL_COLOR
    @BODY_MATERIAL_MALE = _M.BODY_MATERIAL_MALE
    @BODY_MATERIAL_FEMALE = _M.BODY_MATERIAL_FEMALE
    @HORN_DETAIL_COLOR = _M.HORN_DETAIL_COLOR
    @EYE_OVAL = _M.EYE_OVAL
    @EYE_OVALS = _M.EYE_OVALS
    @EYE_GRAD = _M.EYE_GRAD
    @EYE_EFFECT = _M.EYE_EFFECT
    @EYE_REFLECTION = _M.EYE_REFLECTION
    @EYE_LINE_L_1 = _M.EYE_LINE_L_1
    @EYE_LINE_R_1 = _M.EYE_LINE_R_1
    @EYE_LINE_L_2 = _M.EYE_LINE_L_2
    @EYE_LINE_R_2 = _M.EYE_LINE_R_2
    @PONY_SOCKS = _M.PONY_SOCKS

    @SessionID = math.random(1, 1000)

    @MAT_INDEX_EYE_LEFT = 0
    @MAT_INDEX_EYE_RIGHT = 1
    @MAT_INDEX_BODY = 2
    @MAT_INDEX_HORN = 3
    @MAT_INDEX_WINGS = 4
    @MAT_INDEX_HAIR_COLOR1 = 5
    @MAT_INDEX_HAIR_COLOR2 = 6
    @MAT_INDEX_TAIL_COLOR1 = 7
    @MAT_INDEX_TAIL_COLOR2 = 8
    @MAT_INDEX_CMARK = 9
    @MAT_INDEX_EYELASHES = 10

    @NEXT_GENERATED_ID = 10000

    @MANE_UPDATE_TRIGGER = {'ManeType': true, 'ManeTypeLower': true}
    @TAIL_UPDATE_TRIGGER = {'TailType': true}
    @EYE_UPDATE_TRIGGER = {'SeparateEyes': true}
    @PHONG_UPDATE_TRIGGER = {
        'SeparateHornPhong': true
        'SeparateWingsPhong': true
        'SeparateManePhong': true
        'SeparateTailPhong': true
    }

    for ttype in *{'Body', 'Horn', 'Wings', 'BatWingsSkin', 'Socks', 'Mane', 'Tail', 'UpperMane', 'LowerMane'}
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongExponent'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongBoost'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongTint'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongFront'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongMiddle'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'PhongSliding'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'Lightwarp'] = true
        @PHONG_UPDATE_TRIGGER[ttype .. 'LightwarpURL'] = true

    for publicName in *{'', 'Left', 'Right'}
        @EYE_UPDATE_TRIGGER["EyeType#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleWidth#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["IrisSize#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeLines#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleSize#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeBackground#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisTop#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisBottom#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisLine1#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeIrisLine2#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeHole#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["DerpEyesStrength#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["DerpEyes#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeReflection#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeEffect#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeURL#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["IrisWidth#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["IrisHeight#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleHeight#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleShiftX#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["HoleShiftY#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeRotation#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["PonySize#{publicName}"] = true

    for i = 1, 6
        @MANE_UPDATE_TRIGGER["ManeColor#{i}"] = true
        @MANE_UPDATE_TRIGGER["ManeDetailColor#{i}"] = true
        @MANE_UPDATE_TRIGGER["ManeURLColor#{i}"] = true
        @MANE_UPDATE_TRIGGER["ManeURL#{i}"] = true
        @MANE_UPDATE_TRIGGER["TailURL#{i}"] = true
        @MANE_UPDATE_TRIGGER["TailURLColor#{i}"] = true

        @TAIL_UPDATE_TRIGGER["TailColor#{i}"] = true
        @TAIL_UPDATE_TRIGGER["TailDetailColor#{i}"] = true

    @BODY_UPDATE_TRIGGER = {}
    for i = 1, PPM2.MAX_BODY_DETAILS
        @BODY_UPDATE_TRIGGER["BodyDetail#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailColor#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailURLColor#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailURL#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailGlow#{i}"] = true
        @BODY_UPDATE_TRIGGER["BodyDetailGlowStrength#{i}"] = true

    for i = 1, PPM2.MAX_TATTOOS
        @BODY_UPDATE_TRIGGER["TattooType#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooPosX#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooPosY#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooRotate#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooScaleX#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooScaleY#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooColor#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooGlow#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooGlowStrength#{i}"] = true
        @BODY_UPDATE_TRIGGER["TattooOverDetail#{i}"] = true

    DelayCompile: (func = '', ...) =>
        return if not @[func]
        args = {...}
        for data in *@delayCompilation
            if data.func == func and #args == #data.args
                hit = false
                for i = 1, #args
                    if args[i] ~= data.args[i]
                        hit = true
                        break
                for i = 1, #data.args
                    if args[i] ~= data.args[i]
                        hit = true
                        break
                return if not hit

        table.insert(@delayCompilation, {:func, :args})
        timer.Create "#{@}_DelayCompile", 0, 1, ->
            return if not @IsValid()
            return if not IsValid(@ent)
            for data in *@delayCompilation
                @[data.func](@, unpack(data.args))
            @delayCompilation = {}

    DataChanges: (state) =>
        return unless @isValid
        return if not @ent
        key = state\GetKey()
        switch key
            when 'BodyColor'
                @DelayCompile('CompileBody')
                @DelayCompile('CompileWings')
                @DelayCompile('CompileHorn')
            when 'Socks', 'Bodysuit'
                @DelayCompile('CompileBody')
            when 'CMark', 'CMarkType', 'CMarkURL', 'CMarkColor', 'CMarkSize'
                @DelayCompile('CompileCMark')
            when 'SocksColor', 'SocksTextureURL', 'SocksTexture', 'SocksDetailColor1', 'SocksDetailColor2', 'SocksDetailColor3', 'SocksDetailColor4', 'SocksDetailColor5', 'SocksDetailColor6'
                @DelayCompile('CompileSocks')
            when 'HornURL1', 'SeparateHorn', 'HornColor', 'HornURL2', 'HornURL3', 'HornURLColor1', 'HornURLColor2', 'HornURLColor3', 'UseHornDetail', 'HornGlow', 'HornGlowSrength', 'HornDetailColor'
                @DelayCompile('CompileHorn')
            when 'WingsURL1', 'WingsURL2', 'WingsURL3', 'WingsURLColor1', 'WingsURLColor2', 'WingsURLColor3', 'SeparateWings', 'WingsColor'
                @DelayCompile('CompileWings')
            else
                if @@MANE_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileHair')
                elseif @@TAIL_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileTail')
                elseif @@EYE_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileEye', true)
                    @DelayCompile('CompileEye', false)
                elseif @@BODY_UPDATE_TRIGGER[key]
                    @DelayCompile('CompileBody')
                elseif @@PHONG_UPDATE_TRIGGER[key]
                    @UpdatePhongData()

    @HTML_MATERIAL_QUEUE = {}
    @URL_MATERIAL_CACHE = {}
    @ALREADY_DOWNLOADING = {}
    @FAILED_TO_DOWNLOAD = {}
    @LoadURL: (url, width = @QUAD_SIZE_CONST, height = @QUAD_SIZE_CONST, callback = (->)) =>
        error('Must specify URL') if not url or url == ''
        @URL_MATERIAL_CACHE[width] = @URL_MATERIAL_CACHE[width] or {}
        @URL_MATERIAL_CACHE[width][height] = @URL_MATERIAL_CACHE[width][height] or {}
        @ALREADY_DOWNLOADING[width] = @ALREADY_DOWNLOADING[width] or {}
        @ALREADY_DOWNLOADING[width][height] = @ALREADY_DOWNLOADING[width][height] or {}
        @FAILED_TO_DOWNLOAD[width] = @FAILED_TO_DOWNLOAD[width] or {}
        @FAILED_TO_DOWNLOAD[width][height] = @FAILED_TO_DOWNLOAD[width][height] or {}
        if @FAILED_TO_DOWNLOAD[width][height][url]
            callback(@FAILED_TO_DOWNLOAD[width][height][url].texture, nil, @FAILED_TO_DOWNLOAD[width][height][url].material)
            return
        if @ALREADY_DOWNLOADING[width][height][url]
            for data in *@HTML_MATERIAL_QUEUE
                if data.url == url
                    table.insert(data.callbacks, callback)
                    break
            return
        if @URL_MATERIAL_CACHE[width][height][url]
            callback(@URL_MATERIAL_CACHE[width][height][url].texture, nil, @URL_MATERIAL_CACHE[width][height][url].material)
            return
        @ALREADY_DOWNLOADING[width][height][url] = true
        table.insert(@HTML_MATERIAL_QUEUE, {:url, :width, :height, callbacks: {callback}})
        --PPM2.Message 'Queuing to download ', url
    @BuildURLHTML = (url = 'https://dbot.serealia.ca/illuminati.jpg', width = @QUAD_SIZE_CONST, height = @QUAD_SIZE_CONST) =>
        url = url\Replace('%', '%25')\Replace(' ', '%20')\Replace('"', '%22')\Replace("'", '%27')\Replace('#', '%23')\Replace('<', '%3C')\Replace('=', '%3D')\Replace('>', '%3E')
        return "

                "

    @SHOULD_WAIT_WEB = false
    hook.Add 'Think', 'PPM2.WebMaterialThink', ->
        return if @SHOULD_WAIT_WEB
        data = @HTML_MATERIAL_QUEUE[1]
        return if not data
        if IsValid(data.panel)
            panel = data.panel
            return if panel\IsLoading()
            if data.timerid
                timer.Remove(data.timerid)
                data.timerid = nil
            return if data.frame < 20
            @SHOULD_WAIT_WEB = true
            timer.Simple 1, ->
                @SHOULD_WAIT_WEB = false
                return unless IsValid(panel)
                panel\UpdateHTMLTexture()
                htmlmat = panel\GetHTMLMaterial()
                return if not htmlmat
                texture = htmlmat\GetTexture('$basetexture')
                texture\Download()
                newMat = CreateMaterial("PPM2.URLMaterial.#{texture\GetName()}_#{math.random(1, 100000)}", 'UnlitGeneric', {
                    '$basetexture': 'models/debug/debugwhite'
                    '$ignorez': 1
                    '$vertexcolor': 1
                    '$vertexalpha': 1
                    '$nolod': 1
                })

                newMat\SetTexture('$basetexture', texture)
                @URL_MATERIAL_CACHE[data.width] = @URL_MATERIAL_CACHE[data.width] or {}
                @URL_MATERIAL_CACHE[data.width][data.height] = @URL_MATERIAL_CACHE[data.width][data.height] or {}
                @URL_MATERIAL_CACHE[data.width][data.height][data.url] = {
                    texture: texture
                    material: newMat
                }

                @ALREADY_DOWNLOADING[data.width][data.height][data.url] = false
                --PPM2.Message 'Finished downloading ', data.url

                for callback in *data.callbacks
                    callback(texture, panel, newMat)
                table.remove(@HTML_MATERIAL_QUEUE, 1)
                timer.Simple 0, -> panel\Remove() if IsValid(panel)
            return
        data.frame = 0
        panel = vgui.Create('DHTML')
        data.timerid = "PPM2.TextureMaterialTimeout.#{math.random(1, 100000)}"
        timer.Create data.timerid, 8, 1, ->
            return unless IsValid(panel)
            panel\Remove()
            --PPM2.Message 'Failed to download', data.url, '!'
            newMat = CreateMaterial("PPM2.URLMaterial_Failed_#{math.random(1, 100000)}", 'UnlitGeneric', {
                '$basetexture': 'models/ppm/partrender/null'
                '$ignorez': 1
                '$vertexcolor': 1
                '$vertexalpha': 1
                '$nolod': 1
                '$translucent': 1
            })

            @FAILED_TO_DOWNLOAD[data.width][data.height][data.url] = {
                texture: newMat\GetTexture('$basetexture')
                material: newMat
            }

            for callback in *data.callbacks
                callback(newMat\GetTexture('$basetexture'), nil, newMat)
        panel\SetVisible(false)
        panel\SetSize(data.width, data.height)
        panel\SetHTML(@BuildURLHTML(data.url, data.width, data.height))
        panel\Refresh()
        panel.ConsoleMessage = (pnl, msg) ->
            if msg == 'FRAME'
                data.frame += 1
        data.panel = panel
        --PPM2.Message 'Downloading ', data.url

    new: (controller, compile = true) =>
        @isValid = true
        @ent = controller\GetEntity()
        @cachedENT = controller\GetEntity()
        @networkedData = controller\GetData()
        @id = @ent\EntIndex()
        if @id == -1
            @clientsideID = true
            @id = @@NEXT_GENERATED_ID
            @@NEXT_GENERATED_ID += 1
        @compiled = false
        @lastMaterialUpdate = 0
        @lastMaterialUpdateEnt = NULL
        @delayCompilation = {}
        @CompileTextures() if compile
        PPM2.DebugPrint('Created new texture controller for ', @ent, ' as part of ', controller, '; internal ID is ', @id)
    __tostring: => "[#{@@__name}:#{@id}|#{@GetData()}]"
    Remove: =>
        @isValid = false
        @ResetTextures()
    IsValid: => IsValid(@ent) and @isValid and @compiled
    GetID: =>
        return @id if @clientsideID
        if @ent ~= @cachedENT
            @cachedENT = @ent
            @id = @ent\EntIndex()
            if @id == -1
                @id = @@NEXT_GENERATED_ID
                @@NEXT_GENERATED_ID += 1
        return @id
    GetData: =>
        @ent = @networkedData\GetEntity()
        return @networkedData
    GrabData: (str, ...) => @GetData()['Get' .. str](@GetData(), ...)
    GetEntity: => @ent
    GetBody: =>
        if @GetData()\GetGender() == PPM2.GENDER_FEMALE
            return @FemaleMaterial
        else
            return @MaleMaterial
    GetBodyName: =>
        if @GetData()\GetGender() == PPM2.GENDER_FEMALE
            return @FemaleMaterialName
        else
            return @MaleMaterialName
    GetSocks: => @SocksMaterial
    GetSocksName: => @SocksMaterialName
    GetCMark: => @CMarkTexture
    GetCMarkName: => @CMarkTextureName
    GetGUICMark: => @CMarkTextureGUI
    GetGUICMarkName: => @CMarkTextureGUIName
    GetCMarkGUI: => @CMarkTextureGUI
    GetCMarkGUIName: => @CMarkTextureGUIName
    GetHair: (index = 1) =>
        if index == 2
            return @HairColor2Material
        else
            return @HairColor1Material
    GetHairName: (index = 1) =>
        if index == 2
            return @HairColor2MaterialName
        else
            return @HairColor1MaterialName
    GetMane: (index = 1) =>
        if index == 2
            return @HairColor2Material
        else
            return @HairColor1Material
    GetManeName: (index = 1) =>
        if index == 2
            return @HairColor2MaterialName
        else
            return @HairColor1MaterialName
    GetTail: (index = 1) =>
        if index == 2
            return @TailColor2Material
        else
            return @TailColor1Material
    GetTailName: (index = 1) =>
        if index == 2
            return @TailColor2MaterialName
        else
            return @TailColor1MaterialName
    GetEye: (left = false) =>
        if left
            return @EyeMaterialL
        else
            return @EyeMaterialR
    GetEyeName: (left = false) =>
        if left
            return @EyeMaterialLName
        else
            return @EyeMaterialRName
    GetHorn: => @HornMaterial
    GetHornName: => @HornMaterialName
    GetWings: => @WingsMaterial
    GetWingsName: => @WingsMaterialName
    CompileTextures: =>
        @CompileBody()
        @CompileHair()
        @CompileTail()
        @CompileHorn()
        @CompileWings()
        @CompileCMark()
        @CompileSocks()
        @CompileEye(false)
        @CompileEye(true)
        @compiled = true

    PreDraw: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        if @lastMaterialUpdate < RealTime() or @lastMaterialUpdateEnt ~= ent or PPM2.ALTERNATIVE_RENDER\GetBool()
            @lastMaterialUpdateEnt = ent
            @lastMaterialUpdate = RealTime() + 1
            ent\SetSubMaterial(@@MAT_INDEX_EYE_LEFT, @GetEyeName(true))
            ent\SetSubMaterial(@@MAT_INDEX_EYE_RIGHT, @GetEyeName(false))
            ent\SetSubMaterial(@@MAT_INDEX_BODY, @GetBodyName())
            ent\SetSubMaterial(@@MAT_INDEX_HORN, @GetHornName())
            ent\SetSubMaterial(@@MAT_INDEX_WINGS, @GetWingsName())
            ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR1, @GetHairName(1))
            ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR2, @GetHairName(2))
            ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR1, @GetTailName(1))
            ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR2, @GetTailName(2))
            ent\SetSubMaterial(@@MAT_INDEX_CMARK, @GetCMarkName())
            ent\SetSubMaterial(@@MAT_INDEX_EYELASHES)
        if PPM2.ALTERNATIVE_RENDER\GetBool()
            render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_LEFT, @GetEye(true))
            render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_RIGHT, @GetEye(false))
            render.MaterialOverrideByIndex(@@MAT_INDEX_BODY, @GetBody())
            render.MaterialOverrideByIndex(@@MAT_INDEX_HORN, @GetHorn())
            render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS, @GetWings())
            render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR1, @GetHair(1))
            render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR2, @GetHair(2))
            render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR1, @GetTail(1))
            render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR2, @GetTail(2))
            render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK, @GetCMark())
            render.MaterialOverrideByIndex(@@MAT_INDEX_EYELASHES)

    PostDraw: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        return if not PPM2.ALTERNATIVE_RENDER\GetBool()
        render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_LEFT)
        render.MaterialOverrideByIndex(@@MAT_INDEX_EYE_RIGHT)
        render.MaterialOverrideByIndex(@@MAT_INDEX_BODY)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HORN)
        render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR1)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HAIR_COLOR2)
        render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR1)
        render.MaterialOverrideByIndex(@@MAT_INDEX_TAIL_COLOR2)
        render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK)

    ResetTextures: (ent = @ent) =>
        return if not IsValid(ent)
        @lastMaterialUpdateEnt = NULL
        @lastMaterialUpdate = 0
        ent\SetSubMaterial(@@MAT_INDEX_EYE_LEFT)
        ent\SetSubMaterial(@@MAT_INDEX_EYE_RIGHT)
        ent\SetSubMaterial(@@MAT_INDEX_BODY)
        ent\SetSubMaterial(@@MAT_INDEX_HORN)
        ent\SetSubMaterial(@@MAT_INDEX_WINGS)
        ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR1)
        ent\SetSubMaterial(@@MAT_INDEX_HAIR_COLOR2)
        ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR1)
        ent\SetSubMaterial(@@MAT_INDEX_TAIL_COLOR2)
        ent\SetSubMaterial(@@MAT_INDEX_CMARK)
        ent\SetSubMaterial(@@MAT_INDEX_EYELASHES)

    PreDrawLegs: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        render.MaterialOverrideByIndex(@@MAT_INDEX_BODY, @GetBody())
        render.MaterialOverrideByIndex(@@MAT_INDEX_HORN, @GetHorn())
        render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS, @GetWings())
        render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK, @GetCMark())

    PostDrawLegs: (ent = @ent) =>
        return unless @compiled
        return unless @isValid
        render.MaterialOverrideByIndex(@@MAT_INDEX_BODY)
        render.MaterialOverrideByIndex(@@MAT_INDEX_HORN)
        render.MaterialOverrideByIndex(@@MAT_INDEX_WINGS)
        render.MaterialOverrideByIndex(@@MAT_INDEX_CMARK)

    @MAT_INDEX_SOCKS = 0

    UpdateSocks: (ent = @ent, socksEnt) =>
        return unless @compiled
        return unless @isValid
        socksEnt\SetSubMaterial(@@MAT_INDEX_SOCKS, @GetSocksName())

    @QUAD_SIZE_EYES = 256
    @QUAD_SIZE_EYES_HIRES = 512

    @QUAD_SIZE_SOCKS = 512
    @QUAD_SIZE_SOCKS_HIRES = 1024

    @QUAD_SIZE_CMARK = 256
    @QUAD_SIZE_CMARK_HIRES = 512

    @QUAD_SIZE_WING = 128
    @QUAD_SIZE_WING_HIRES = 256

    @QUAD_SIZE_HORN = 128
    @QUAD_SIZE_HORN_HIRES = 256

    @QUAD_SIZE_HAIR = 256
    @QUAD_SIZE_HAIR_HIRES = 512

    @QUAD_SIZE_TAIL = 256
    @QUAD_SIZE_TAIL_HIRES = 512

    @QUAD_SIZE_CONST = 512
    @QUAD_SIZE_CONST_HIRES = 1024

    @QUAD_SIZE_BODY = 1024
    @QUAD_SIZE_BODY_HIRES = 2048
    @QUAD_SIZE_BODY_HIRES2 = 4096

    @TATTOO_DEF_SIZE = 128
    @TATTOO_MEDIUM_SIZE = 256
    @TATTOO_BIG_SIZE = 512

    @GetTextureSize = => USE_HIGHRES_TEXTURES\GetBool() and @QUAD_SIZE_CONST_HIRES or @QUAD_SIZE_CONST
    @GetBodySize = => USE_HIGHRES_BODY\GetBool() and @QUAD_SIZE_BODY_HIRES2 or USE_HIGHRES_TEXTURES\GetBool() and @QUAD_SIZE_BODY_HIRES or @QUAD_SIZE_BODY

    DrawTattoo: (index = 1, drawingGlow = false, texSize = @@GetBodySize()) =>
        mat = _M.TATTOOS[@GrabData("TattooType#{index}")]
        return if not mat
        X, Y = @GrabData("TattooPosX#{index}"), @GrabData("TattooPosY#{index}")
        TattooRotate = @GrabData("TattooRotate#{index}")
        TattooScaleX = @GrabData("TattooScaleX#{index}")
        TattooScaleY = @GrabData("TattooScaleY#{index}")
        if not drawingGlow
            {:r, :g, :b} = @GrabData("TattooColor#{index}")
            surface.SetDrawColor(r, g, b)
        else
            if @GrabData("TattooGlow#{index}")
                surface.SetDrawColor(255, 255, 255, 255 * @GrabData("TattooGlowStrength#{index}"))
            else
                surface.SetDrawColor(0, 0, 0)
        surface.SetMaterial(mat)
        tSize = USE_HIGHRES_BODY\GetBool() and @@TATTOO_BIG_SIZE or USE_HIGHRES_TEXTURES\GetBool() and @@TATTOO_MEDIUM_SIZE or @@TATTOO_DEF_SIZE
        sizeX, sizeY = tSize * TattooScaleX, tSize * TattooScaleY
        surface.DrawTexturedRectRotated((X * texSize / 2) / 100 + texSize / 2, -(Y * texSize / 2) / 100 + texSize / 2, sizeX, sizeY, TattooRotate)

    GetPhongData: (prefix = 'Body') =>
        PhongExponent = @GrabData(prefix .. 'PhongExponent')
        PhongBoost = @GrabData(prefix .. 'PhongBoost')
        PhongTint = @GrabData(prefix .. 'PhongTint')
        PhongFront = @GrabData(prefix .. 'PhongFront')
        PhongMiddle = @GrabData(prefix .. 'PhongMiddle')
        Lightwarp = @GrabData(prefix .. 'Lightwarp')
        LightwarpURL = @GrabData(prefix .. 'LightwarpURL')
        PhongSliding = @GrabData(prefix .. 'PhongSliding')
        {:r, :g, :b} = PhongTint
        r /= 255
        g /= 255
        b /= 255
        PhongTint = Vector(r, g, b)
        PhongFresnel = Vector(PhongFront, PhongMiddle, PhongSliding)
        return PhongExponent, PhongBoost, PhongTint, PhongFresnel, Lightwarp, LightwarpURL

    ApplyPhongData: (matTarget, prefix = 'Body') =>
        return if not matTarget
        PhongExponent, PhongBoost, PhongTint, PhongFresnel, Lightwarp, LightwarpURL = @GetPhongData(prefix)
        with matTarget
            \SetFloat('$phongexponent', PhongExponent)
            \SetFloat('$phongboost', PhongBoost)
            \SetVector('$phongtint', PhongTint)
            \SetVector('$phongfresnelranges', PhongFresnel)

        if LightwarpURL == '' or not LightwarpURL\find('^https?://')
            myTex = PPM2.AvaliableLightwarpsPaths[Lightwarp + 1] or PPM2.AvaliableLightwarpsPaths[1]
            matTarget\SetTexture('$lightwarptexture', myTex)
        else
            @@LoadURL LightwarpURL, 256, 16, (tex, panel, mat) ->
                matTarget\SetTexture('$lightwarptexture', tex)

    GetBodyPhongMaterials: (output = {}) =>
        table.insert(output, @MaleMaterial) if @MaleMaterial
        table.insert(output, @FemaleMaterial) if @FemaleMaterial
        table.insert(output, @HornMaterial) if @HornMaterial and not @GrabData('SeparateHornPhong')
        table.insert(output, @WingsMaterial) if @WingsMaterial and not @GrabData('SeparateWingsPhong')
        if not @GrabData('SeparateManePhong')
            table.insert(output, @HairColor1Material) if @HairColor1Material
            table.insert(output, @HairColor2Material) if @HairColor2Material
        if not @GrabData('SeparateTailPhong')
            table.insert(output, @TailColor1Material) if @TailColor1Material
            table.insert(output, @TailColor2Material) if @TailColor2Material

    UpdatePhongData: =>
        proceed = {}
        @GetBodyPhongMaterials(proceed)
        for mat in *proceed
            @ApplyPhongData(mat, 'Body')

        if @GrabData('SeparateHornPhong') and @HornMaterial
            @ApplyPhongData(@HornMaterial, 'Horn')

        if @GrabData('SeparateWingsPhong') and @WingsMaterial
            @ApplyPhongData(@WingsMaterial, 'Wings')

        if @SocksMaterial
            @ApplyPhongData(@SocksMaterial, 'Socks')

        if @GrabData('SeparateManePhong')
            @ApplyPhongData(@HairColor1Material, 'Mane')
            @ApplyPhongData(@HairColor2Material, 'Mane')

        if @GrabData('SeparateTailPhong')
            @ApplyPhongData(@TailColor1Material, 'Tail')
            @ApplyPhongData(@TailColor2Material, 'Tail')

    __compileBodyInternal: (bType = false) =>
        return unless @isValid
        prefix = bType and 'Female' or 'Male'
        prefixUP = bType and 'FEMALE' or 'MALE'
        urlTextures = {}
        left = 0
        bodysize = @@GetBodySize()

        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Body_#{prefix}"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/bodym'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
                '$selfillum': '1'
                '$selfillummask': 'models/ppm/partrender/null'

                '$color': '{255 255 255}'
                '$color2': '{255 255 255}'
                '$model': '1'
                '$phong': '1'
                '$basemapalphaphongmask': '1'
                '$phongexponent': '3'
                '$phongboost': '0.15'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'

                '$rimlight': '1'
                '$rimlightexponent': '2'
                '$rimlightboost': '1'
            }
        }

        textureData.data['$basetexture'] = 'models/ppm/base/body' if not bType

        @["#{prefix}MaterialName"] = "!#{textureData.name\lower()}"
        @["#{prefix}Material"] = CreateMaterial(textureData.name, textureData.shader, textureData.data)

        continueCompilation = ->
            return unless @isValid
            {:r, :g, :b} = @GetData()\GetBodyColor()
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_Body_#{prefix}_rt_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", bodysize, bodysize, false)
            rt\Download()

            render.PushRenderTarget(rt)

            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.DisableClipping(true)
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, bodysize, bodysize)

            surface.SetMaterial(@@["BODY_MATERIAL_#{prefixUP}"])
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            for i = 1, PPM2.MAX_TATTOOS
                if not @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i)

            for i = 1, PPM2.MAX_BODY_DETAILS
                detailID = @GetData()["GetBodyDetail#{i}"](@GetData())
                if mat = _M.BODY_DETAILS[detailID]
                    surface.SetDrawColor(@GetData()["GetBodyDetailColor#{i}"](@GetData()))
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            surface.SetDrawColor(255, 255, 255)

            for i, mat in pairs urlTextures
                {:r, :g, :b, :a} = @GetData()["GetBodyDetailURLColor#{i}"](@GetData())
                surface.SetDrawColor(r, g, b, a)
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            for i = 1, PPM2.MAX_TATTOOS
                if @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i)

            if suit = _M.SUITS[@GetData()\GetBodysuit()]
                surface.SetDrawColor(255, 255, 255)
                surface.SetMaterial(suit)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            if @GetData()\GetSocks()
                surface.SetDrawColor(255, 255, 255)
                surface.SetMaterial(@@PONY_SOCKS)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            surface.DisableClipping(false)
            cam.End2D()
            render.PopRenderTarget()

            @["#{prefix}Material"]\SetTexture('$basetexture', rt)

            rtIllum = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_Body_#{prefix}_rtIllum_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", bodysize, bodysize, false)
            rtIllum\Download()

            render.PushRenderTarget(rtIllum)

            r, g, b = 0, 0, 0
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.DisableClipping(true)
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, bodysize, bodysize)

            surface.SetDrawColor(255, 255, 255)

            for i = 1, PPM2.MAX_TATTOOS
                if not @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i, true)

            for i = 1, PPM2.MAX_BODY_DETAILS
                if mat = _M.BODY_DETAILS[@GetData()["GetBodyDetail#{i}"](@GetData())]
                    alpha = @GetData()["GetBodyDetailGlowStrength#{i}"](@GetData())

                    if @GetData()["GetBodyDetailGlow#{i}"](@GetData())
                        surface.SetDrawColor(255, 255, 255, alpha * 255)
                        surface.SetMaterial(mat)
                        surface.DrawTexturedRect(0, 0, bodysize, bodysize)
                    else
                        surface.SetDrawColor(0, 0, 0, alpha * 255)
                        surface.SetMaterial(mat)
                        surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            for i = 1, PPM2.MAX_TATTOOS
                if @GrabData("TattooOverDetail#{i}")
                    @DrawTattoo(i, true)

            surface.DisableClipping(false)
            cam.End2D()
            render.PopRenderTarget()

            @["#{prefix}Material"]\SetTexture('$selfillummask', rtIllum)

            PPM2.DebugPrint('Compiled body texture for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, PPM2.MAX_BODY_DETAILS
            detailURL = data["GetBodyDetailURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, bodysize, bodysize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()

        if left == 0
            continueCompilation()
        return @["#{prefix}Material"]
    CompileBody: =>
        return unless @isValid
        @__compileBodyInternal(true)
        @__compileBodyInternal(false)
        @UpdatePhongData()
    CompileHorn: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Horn"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/horn'
                '$selfillum': '1'
                '$selfillummask': 'models/ppm/partrender/null'

                '$model': '1'
                '$phong': '1'
                '$phongexponent': '3'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'
                '$alpha': '1'
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_HORN_HIRES or @@QUAD_SIZE_HORN
        urlTextures = {}
        left = 0

        @HornMaterialName = "!#{textureData.name\lower()}"
        @HornMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @UpdatePhongData()

        continueCompilation = ->
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Horn_rt", texSize, texSize, false)
            rt\Download()

            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)
            {:r, :g, :b} = @GetData()\GetBodyColor()
            {:r, :g, :b} = @GetData()\GetHornColor() if @GetData()\GetSeparateHorn()
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@HORN_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            if @GetData()\GetUseHornDetail()
                {:r, :g, :b} = @GetData()\GetHornDetailColor()
                surface.SetDrawColor(r, g, b)
                surface.SetMaterial(@@HORN_DETAIL_COLOR)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            for i, mat in pairs urlTextures
                {:r, :g, :b, :a} = @GetData()["GetHornURLColor#{i}"](@GetData())
                surface.SetDrawColor(r, g, b, a)
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            @HornMaterial\SetTexture('$basetexture', rt)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()

            rtIllum = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Horn_rtIllum", texSize, texSize, false)
            rtIllum\Download()

            render.PushRenderTarget(rtIllum)

            r, g, b = 0, 0, 0
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.DisableClipping(true)
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            if @GetData()\GetHornGlow()
                surface.SetDrawColor(255, 255, 255, @GetData()\GetHornGlowSrength() * 255)
                surface.SetMaterial(@@HORN_DETAIL_COLOR)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            surface.DisableClipping(false)
            cam.End2D()
            render.PopRenderTarget()

            @HornMaterial\SetTexture('$selfillummask', rtIllum)

            PPM2.DebugPrint('Compiled Horn texture for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 3
            detailURL = data["GetHornURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()
        return @HornMaterial
    CompileSocks: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Socks"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/props_pony/ppm/ppm_socks/socks_striped'

                '$model': '1'
                '$ambientocclusion': '1'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$phong': '1'
                '$phongexponent': '6'
                '$phongboost': '0.1'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[1 5 10]'
                '$rimlight': '1'
                '$rimlightexponent': '4.0'
                '$rimlightboost': '2'
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
                '$cloakPassEnabled': '1'
            }
        }

        @SocksMaterialName = "!#{textureData.name\lower()}"
        @SocksMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @UpdatePhongData()
        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_SOCKS_HIRES or @@QUAD_SIZE_SOCKS

        {:r, :g, :b} = @GetData()\GetSocksColor()
        @SocksMaterial\SetFloat('$alpha', 1)

        url = @GetData()\GetSocksTextureURL()
        if url == '' or not url\find('^https?://')
            @SocksMaterial\SetVector('$color', Vector(1, 1, 1))
            @SocksMaterial\SetVector('$color2', Vector(1, 1, 1))
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Socks_rt", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            socksType = @GetData()\GetSocksTexture() + 1
            surface.SetMaterial(_M.SOCKS_MATERIALS[socksType] or _M.SOCKS_MATERIALS[1])
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            if details = _M.SOCKS_DETAILS[socksType]
                for i, id in pairs details
                    {:r, :g, :b} = @GetData()['GetSocksDetailColor' .. i](@GetData())
                    surface.SetDrawColor(r, g, b)
                    surface.SetMaterial(id)
                    surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.PopRenderTarget()

            @SocksMaterial\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled socks texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL url, texSize, texSize, (texture, panel, material) ->
                @SocksMaterial\SetVector('$color', Vector(r / 255, g / 255, b / 255))
                @SocksMaterial\SetVector('$color2', Vector(r / 255, g / 255, b / 255))
                @SocksMaterial\SetTexture('$basetexture', texture)

        return @SocksMaterial
    CompileWings: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Wings"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/debug/debugwhite'

                '$model': '1'
                '$phong': '1'
                '$phongexponent': '3'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'
                '$alpha': '1'
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        urlTextures = {}
        left = 0
        @WingsMaterialName = "!#{textureData.name\lower()}"
        @WingsMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @UpdatePhongData()

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_WING_HIRES or @@QUAD_SIZE_WING

        continueCompilation = ->
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Wings_rt", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)
            {:r, :g, :b} = @GetData()\GetBodyColor()
            {:r, :g, :b} = @GetData()\GetWingsColor() if @GetData()\GetSeparateWings()
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@WINGS_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            for i, mat in pairs urlTextures
                {:r, :g, :b, :a} = @GetData()["GetWingsURLColor#{i}"](@GetData())
                surface.SetDrawColor(r, g, b, a)
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            @WingsMaterial\SetTexture('$basetexture', rt)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()

            PPM2.DebugPrint('Compiled wings texture for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 3
            detailURL = data["GetWingsURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()

        return @WingsMaterial

    GetManeType: => @GetData()\GetManeType()
    GetManeTypeLower: => @GetData()\GetManeTypeLower()
    GetTailType: => @GetData()\GetTailType()
    CompileHair: =>
        return unless @isValid
        textureFirst = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Mane_1"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/debug/debugwhite'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
                '$model': '1'
                '$phong': '1'
                '$basemapalphaphongmask': '1'
                '$phongexponent': '6'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'

                '$rimlight': 1
                '$rimlightexponent': 2
                '$rimlightboost': 1
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        textureSecond = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Mane_2"
            'shader': 'VertexLitGeneric'
            'data': {k, v for k, v in pairs textureFirst.data}
        }

        @HairColor1MaterialName = "!#{textureFirst.name\lower()}"
        @HairColor2MaterialName = "!#{textureSecond.name\lower()}"
        @HairColor1Material = CreateMaterial(textureFirst.name, textureFirst.shader, textureFirst.data)
        @HairColor2Material = CreateMaterial(textureSecond.name, textureSecond.shader, textureSecond.data)

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_HAIR_HIRES or @@QUAD_SIZE_HAIR

        urlTextures = {}
        left = 0

        continueCompilation = ->
            return unless @isValid
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Mane_rt_1", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            -- First mane pass
            {:r, :g, :b} = @GetData()\GetManeColor1()
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            maneTypeUpper = @GetManeType()
            if @@UPPER_MANE_MATERIALS[maneTypeUpper]
                i = 1
                for mat in *@@UPPER_MANE_MATERIALS[maneTypeUpper]
                    continue if type(mat) == 'number'
                    {:r, :g, :b, :a} = @GetData()["GetManeDetailColor#{i}"](@GetData())
                    surface.SetDrawColor(r, g, b, a)
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetManeURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @HairColor1Material\SetTexture('$basetexture', rt)

            -- Second mane pass
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Mane_rt_2", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b} = @GetData()\GetManeColor2()
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            maneTypeLower = @GetManeTypeLower()
            if @@LOWER_MANE_MATERIALS[maneTypeLower]
                i = 1
                for mat in *@@LOWER_MANE_MATERIALS[maneTypeLower]
                    continue if type(mat) == 'number'
                    {:r, :g, :b, :a} = @GetData()["GetManeDetailColor#{i}"](@GetData())
                    surface.SetDrawColor(r, g, b, a)
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetManeURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @HairColor2Material\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled mane textures for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 6
            detailURL = data["GetManeURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()
        return @HairColor1Material, @HairColor2Material
    CompileTail: =>
        return unless @isValid
        textureFirst = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Tail_1"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/debug/debugwhite'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
                '$model': '1'
                '$phong': '1'
                '$basemapalphaphongmask': '1'
                '$phongexponent': '6'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'

                '$rimlight': 1
                '$rimlightexponent': 2
                '$rimlightboost': 1
                '$color': '[1 1 1]'
                '$color2': '[1 1 1]'
            }
        }

        textureSecond = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Tail_2"
            'shader': 'VertexLitGeneric'
            'data': {k, v for k, v in pairs textureFirst.data}
        }

        @TailColor1MaterialName = "!#{textureFirst.name\lower()}"
        @TailColor2MaterialName = "!#{textureSecond.name\lower()}"
        @TailColor1Material = CreateMaterial(textureFirst.name, textureFirst.shader, textureFirst.data)
        @TailColor2Material = CreateMaterial(textureSecond.name, textureSecond.shader, textureSecond.data)

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_TAIL_HIRES or @@QUAD_SIZE_TAIL

        urlTextures = {}
        left = 0

        continueCompilation = ->
            return unless @isValid
            oldW, oldH = ScrW(), ScrH()

            -- First tail pass
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Tail_rt_1", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b} = @GetData()\GetTailColor1()
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@HAIR_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            tailType = @GetTailType()
            if @@TAIL_DETAIL_MATERIALS[tailType]
                i = 1
                for mat in *@@TAIL_DETAIL_MATERIALS[tailType]
                    continue if type(mat) == 'number'
                    surface.SetMaterial(mat)
                    surface.SetDrawColor(@GetData()["GetTailDetailColor#{i}"](@GetData()))
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetTailURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @TailColor1Material\SetTexture('$basetexture', rt)

            -- Second tail pass
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Tail_rt_2", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b} = @GetData()\GetTailColor2()
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetMaterial(@@HAIR_MATERIAL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

            if @@TAIL_DETAIL_MATERIALS[tailType]
                i = 1
                for mat in *@@TAIL_DETAIL_MATERIALS[tailType]
                    continue if type(mat) == 'number'
                    surface.SetMaterial(mat)
                    surface.SetDrawColor(@GetData()["GetTailDetailColor#{i}"](@GetData()))
                    surface.DrawTexturedRect(0, 0, texSize, texSize)
                    i += 1

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GetData()["GetTailURLColor#{i}"](@GetData()))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, texSize, texSize)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @TailColor2Material\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled tail textures for ', @ent, ' as part of ', @)

        data = @GetData()
        validURLS = for i = 1, 6
            detailURL = data["GetTailURL#{i}"](data)
            continue if detailURL == '' or not detailURL\find('^https?://')
            left += 1
            {detailURL, i}

        for {url, i} in *validURLS
            @@LoadURL url, texSize, texSize, (texture, panel, mat) ->
                left -= 1
                urlTextures[i] = mat
                if left == 0
                    continueCompilation()
        if left == 0
            continueCompilation()
        return @TailColor1Material, @TailColor2Material
    CompileEye: (left = false) =>
        return unless @isValid
        prefix = left and 'l' or 'r'
        prefixUpper = left and 'L' or 'R'

        separated = @GetData()\GetSeparateEyes()
        prefixData = ''
        prefixData = left and 'Left' or 'Right' if separated

        EyeType =           @GetData()["GetEyeType#{prefixData}"](@GetData())
        EyeBackground =     @GetData()["GetEyeBackground#{prefixData}"](@GetData())
        EyeHole =           @GetData()["GetEyeHole#{prefixData}"](@GetData())
        HoleWidth =         @GetData()["GetHoleWidth#{prefixData}"](@GetData())
        IrisSize =          @GetData()["GetIrisSize#{prefixData}"](@GetData()) * .75
        EyeIris1 =          @GetData()["GetEyeIrisTop#{prefixData}"](@GetData())
        EyeIris2 =          @GetData()["GetEyeIrisBottom#{prefixData}"](@GetData())
        EyeIrisLine1 =      @GetData()["GetEyeIrisLine1#{prefixData}"](@GetData())
        EyeIrisLine2 =      @GetData()["GetEyeIrisLine2#{prefixData}"](@GetData())
        EyeLines =          @GetData()["GetEyeLines#{prefixData}"](@GetData())
        HoleSize =          @GetData()["GetHoleSize#{prefixData}"](@GetData())
        EyeReflection =     @GetData()["GetEyeReflection#{prefixData}"](@GetData())
        EyeEffect =         @GetData()["GetEyeEffect#{prefixData}"](@GetData())
        DerpEyes =          @GetData()["GetDerpEyes#{prefixData}"](@GetData())
        DerpEyesStrength =  @GetData()["GetDerpEyesStrength#{prefixData}"](@GetData())
        EyeURL =            @GetData()["GetEyeURL#{prefixData}"](@GetData())
        IrisWidth =         @GetData()["GetIrisWidth#{prefixData}"](@GetData())
        IrisHeight =        @GetData()["GetIrisHeight#{prefixData}"](@GetData())
        HoleHeight =        @GetData()["GetHoleHeight#{prefixData}"](@GetData())
        HoleShiftX =        @GetData()["GetHoleShiftX#{prefixData}"](@GetData())
        HoleShiftY =        @GetData()["GetHoleShiftY#{prefixData}"](@GetData())
        EyeRotation =       @GetData()["GetEyeRotation#{prefixData}"](@GetData())
        PonySize =          @GetData()\GetPonySize()
        PonySize = 1 if IsValid(@ent) and @ent\IsRagdoll()

        oldW, oldH = ScrW(), ScrH()

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_EYES_HIRES or @@QUAD_SIZE_EYES

        shiftX, shiftY = (1 - IrisWidth) * texSize / 2, (1 - IrisHeight) * texSize / 2
        shiftY += DerpEyesStrength * .15 * texSize if DerpEyes and left
        shiftY -= DerpEyesStrength * .15 * texSize if DerpEyes and not left

        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Eye_#{prefix}"
            'shader': 'eyes'
            'data': {
                '$iris': 'models/ppm/base/face/p_base'
                '$irisframe': '0'

                '$ambientoccltexture': 'models/ppm/partrender/null'
                --'$envmap': 'models/ppm/partrender/null'
                '$corneatexture': 'models/ppm/partrender/null'
                '$lightwarptexture': 'models/ppm/clothes/lightwarp'

                '$eyeballradius': '3.7'
                '$ambientocclcolor': '[0.3 0.3 0.3]'
                '$dilation': '0.5'
                '$glossiness': '1'
                '$parallaxstrength': '0.1'
                '$corneabumpstrength': '0.1'

                '$halflambert': '1'
                '$nodecal': '1'

                '$raytracesphere': '0'
                '$spheretexkillcombo': '0'
                '$eyeorigin': '[0 0 0]'
                '$irisu': '[0 1 0 0]'
                '$irisv': '[0 0 1 0]'
                '$entityorigin': '1.0'
            }
        }

        @["EyeMaterial#{prefixUpper}Name"] = "!#{textureData.name\lower()}"
        createdMaterial = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @["EyeMaterial#{prefixUpper}"] = createdMaterial

        if EyeURL == '' or not EyeURL\find('^https?://')
            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_Eye_#{prefix}", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)

            {:r, :g, :b, :a} = EyeBackground
            render.Clear(r, g, b, 255, true, true)
            cam.Start2D()
            surface.SetDrawColor(r, g, b)
            surface.DrawRect(0, 0, texSize, texSize)

            surface.SetDrawColor(EyeIris1)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            IrisPos = texSize / 2 - texSize * IrisSize * PonySize / 2
            IrisQuadSize = texSize * IrisSize * PonySize
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeIris2)
            surface.SetMaterial(@@EYE_GRAD)
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            if EyeLines
                surface.SetDrawColor(EyeIrisLine1)
                surface.SetMaterial(@@["EYE_LINE_#{prefixUpper}_1"])
                DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

                surface.SetDrawColor(EyeIrisLine2)
                surface.SetMaterial(@@["EYE_LINE_#{prefixUpper}_2"])
                DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeHole)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            HoleQuadSize = texSize * IrisSize * HoleSize * PonySize
            HolePos = texSize / 2
            do
                holeX = HoleQuadSize * HoleWidth / 2
                holeY = texSize * (IrisSize * HoleSize * HoleHeight * PonySize) / 2
                cx = HolePos - holeX + holeX * HoleShiftX + shiftX
                cy = HolePos - holeY + holeY * HoleShiftY + shiftY
                DrawTexturedRectRotated(cx, cy, HoleQuadSize * HoleWidth * IrisWidth, HoleQuadSize * HoleHeight * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeEffect)
            surface.SetMaterial(@@EYE_EFFECT)
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            surface.SetDrawColor(EyeReflection)
            surface.SetMaterial(@@EYE_REFLECTION)
            DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

            cam.End2D()
            render.SetViewPort(0, 0, oldW, oldH)
            render.PopRenderTarget()
            @["EyeMaterial#{prefixUpper}"]\SetTexture('$iris', rt)

            PPM2.DebugPrint('Compiled eyes texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL EyeURL, texSize, texSize, (texture, panel, material) ->
                @["EyeMaterial#{prefixUpper}"]\SetTexture('$iris', texture)
        return @["EyeMaterial#{prefixUpper}"]
    CompileCMark: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_CMark"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/partrender/null'
                '$translucent': '1'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
            }
        }

        textureDataGUI = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_CMark_GUI"
            'shader': 'UnlitGeneric'
            'data': {
                '$basetexture': 'models/ppm/partrender/null'
                '$translucent': '1'
                '$lightwarptexture': 'models/ppm/base/lightwrap'
                '$halflambert': '1'
            }
        }

        @CMarkTextureName = "!#{textureData.name\lower()}"
        @CMarkTexture = CreateMaterial(textureData.name, textureData.shader, textureData.data)
        @CMarkTextureGUIName = "!#{textureDataGUI.name\lower()}"
        @CMarkTextureGUI = CreateMaterial(textureDataGUI.name, textureDataGUI.shader, textureDataGUI.data)

        unless @GetData()\GetCMark()
            @CMarkTexture\SetTexture('$basetexture', 'models/ppm/partrender/null')
            @CMarkTextureGUI\SetTexture('$basetexture', 'models/ppm/partrender/null')
            return @CMarkTexture, @CMarkTextureGUI

        URL = @GetData()\GetCMarkURL()
        size = @GetData()\GetCMarkSize()

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_CMARK_HIRES or @@QUAD_SIZE_CMARK
        sizeQuad = texSize * size
        shift = (texSize - sizeQuad) / 2

        if URL == '' or not URL\find('^https?://')
            oldW, oldH = ScrW(), ScrH()

            rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_CMark", texSize, texSize, false)
            rt\Download()
            render.PushRenderTarget(rt)
            render.SetViewPort(0, 0, texSize, texSize)
            render.Clear(0, 0, 0, 0, true, true)
            cam.Start2D()

            if mark = _M.CUTIEMARKS[@GetData()\GetCMarkType() + 1]
                surface.SetDrawColor(@GetData()\GetCMarkColor())
                surface.SetMaterial(mark)
                surface.DrawTexturedRect(shift, shift, sizeQuad, sizeQuad)

            cam.End2D()
            render.PopRenderTarget()
            render.SetViewPort(0, 0, oldW, oldH)

            @CMarkTexture\SetTexture('$basetexture', rt)
            @CMarkTextureGUI\SetTexture('$basetexture', rt)

            PPM2.DebugPrint('Compiled cutiemark texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL URL, texSize, texSize, (texture, panel, material) ->
                oldW, oldH = ScrW(), ScrH()

                rt = GetRenderTarget("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_CMark", texSize, texSize, false)
                rt\Download()
                render.PushRenderTarget(rt)
                render.SetViewPort(0, 0, texSize, texSize)
                render.Clear(0, 0, 0, 0, true, true)
                cam.Start2D()

                surface.SetDrawColor(@GetData()\GetCMarkColor())
                surface.SetMaterial(material)
                surface.DrawTexturedRect(shift, shift, sizeQuad, sizeQuad)

                cam.End2D()
                render.PopRenderTarget()
                render.SetViewPort(0, 0, oldW, oldH)

                @CMarkTexture\SetTexture('$basetexture', rt)
                @CMarkTextureGUI\SetTexture('$basetexture', rt)

                PPM2.DebugPrint('Compiled cutiemark texture for ', @ent, ' as part of ', @)

        return @CMarkTexture, @CMarkTextureGUI

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