Use bigger render targets in hires mode

Use bigger render targets in hires mode

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

PPM2.REAL_TIME_EYE_REFLECTIONS = CreateConVar('ppm2_cl_reflections', '0', {FCVAR_ACRHIVE}, 'Calculate eye reflections in real time. Needs beefy computer.')
REAL_TIME_EYE_REFLECTIONS = PPM2.REAL_TIME_EYE_REFLECTIONS

PPM2.REAL_TIME_EYE_REFLECTIONS_SIZE = CreateConVar('ppm2_cl_reflections_size', '512', {FCVAR_ACRHIVE}, 'Reflections size. Must be multiple to 2! (16, 32, 64, 128, 256)')
REAL_TIME_EYE_REFLECTIONS_SIZE = PPM2.REAL_TIME_EYE_REFLECTIONS_SIZE

PPM2.REAL_TIME_EYE_REFLECTIONS_DIST = CreateConVar('ppm2_cl_reflections_drawdist', '192', {FCVAR_ACRHIVE}, 'Reflections maximal draw distance')
REAL_TIME_EYE_REFLECTIONS_DIST = PPM2.REAL_TIME_EYE_REFLECTIONS_DIST

PPM2.REAL_TIME_EYE_REFLECTIONS_RDIST = CreateConVar('ppm2_cl_reflections_renderdist', '1000', {FCVAR_ACRHIVE}, 'Reflection scene draw distance (ZFar)')
REAL_TIME_EYE_REFLECTIONS_RDIST = PPM2.REAL_TIME_EYE_REFLECTIONS_RDIST

reflectTasks = {}
lastReflectionFrame = 0

hook.Add 'DrawOverlay', 'PPM2.ReflectionsUpdate', (a, b) ->
    return if PPM2.__RENDERING_REFLECTIONS
    return if lastReflectionFrame == FrameNumber()
    lastReflectionFrame = FrameNumber()
    PPM2.__RENDERING_REFLECTIONS = true
    for task in *reflectTasks
        pcall task.ctrl.UpdateEyeReflections, task.ctrl, task.ent
    PPM2.__RENDERING_REFLECTIONS = false
    reflectTasks = {}

hook.Add 'PreDrawEffects', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawEffects', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PreDrawHalos', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawHalos', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PreDrawTranslucentRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawTranslucentRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1

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 = _M.BODY_MATERIAL
    @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"] = true
        @EYE_UPDATE_TRIGGER["EyeRefract#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeCornerA#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeLineDirection#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["LEyeLightwarp"] = true
        @EYE_UPDATE_TRIGGER["REyeLightwarp"] = true
        @EYE_UPDATE_TRIGGER["LEyeLightwarpURL"] = true
        @EYE_UPDATE_TRIGGER["REyeLightwarpURL"] = true
        @EYE_UPDATE_TRIGGER["BEyesLightwarp"] = true
        @EYE_UPDATE_TRIGGER["BEyesLightwarpURL"] = 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))
            @lastMaterialUpdate = 0
            @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', 'LipsColor', 'NoseColor', 'LipsColorInherit', 'NoseColorInherit'
                @DelayCompile('CompileBody')
            when 'CMark', 'CMarkType', 'CMarkURL', 'CMarkColor', 'CMarkSize'
                @DelayCompile('CompileCMark')
            when 'SocksColor', 'SocksTextureURL', 'SocksTexture', 'SocksDetailColor1', 'SocksDetailColor2', 'SocksDetailColor3', 'SocksDetailColor4', 'SocksDetailColor5', 'SocksDetailColor6'
                @DelayCompile('CompileSocks')
            when 'NewSocksColor1', 'NewSocksColor2', 'NewSocksColor3', 'NewSocksTextureURL'
                @DelayCompile('CompileNewSocks')
            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}, timeouts: 0})
        --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
                table.remove(@HTML_MATERIAL_QUEUE, 1)
                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][data.height][data.url] = {
                    texture: texture
                    material: newMat
                }

                @ALREADY_DOWNLOADING[data.width][data.height][data.url] = false

                for callback in *data.callbacks
                    callback(texture, panel, newMat)
                timer.Simple 0, -> panel\Remove() if IsValid(panel)
            return
        data.frame = 0
        panel = vgui.Create('DHTML')
        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
        data.timerid = "PPM2.TextureMaterialTimeout.#{math.random(1, 100000)}"
        timer.Create data.timerid, 8, 1, ->
            return unless IsValid(panel)
            panel\Remove()
            if data.timeouts >= 4
                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)

                table.remove(@HTML_MATERIAL_QUEUE, 1)
            else
                data.timeouts += 1
                table.remove(@HTML_MATERIAL_QUEUE, 1)
                table.insert(@HTML_MATERIAL_QUEUE, data)

    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: => @BodyMaterial
    GetBodyName: => @BodyMaterialName
    GetSocks: => @SocksMaterial
    GetSocksName: => @SocksMaterialName
    GetNewSocks: => @NewSocksColor1, @NewSocksColor2, @NewSocksBase
    GetNewSocksName: => @NewSocksColor1Name, @NewSocksColor2Name, @NewSocksBaseName
    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()
        @CompileNewSocks()
        @CompileEye(false)
        @CompileEye(true)
        @compiled = true

    StartRT: (name, texSize, r = 0, g = 0, b = 0, a = 255) =>
        error('Attempt to start new render target without finishing the old one!\nUPCOMING =======' .. debug.traceback() .. '\nCURRENT =======' .. @currentRTTrace) if @currentRT
        @currentRTTrace = debug.traceback()
        @oldW, @oldH = ScrW(), ScrH()
        render.SetViewPort(0, 0, texSize, texSize)
        rt = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{name}", texSize, texSize, false)
        rt\Download()
        render.PushRenderTarget(rt)
        render.Clear(r, g, b, a, true, true)
        surface.DisableClipping(true)
        cam.Start2D()
        surface.SetDrawColor(r, g, b, a)
        surface.DrawRect(0, 0, texSize, texSize)
        @currentRT = rt
        return rt

    EndRT: =>
        cam.End2D()
        render.SetViewPort(0, 0, @oldW, @oldH)
        render.PopRenderTarget()
        surface.DisableClipping(false)
        rt = @currentRT
        @currentRT = nil
        return rt

    CheckReflections: (ent = @ent) =>
        if REAL_TIME_EYE_REFLECTIONS\GetBool()
            @isInRealTimeReflections = true
            table.insert(reflectTasks, {ctrl: @, ent: ent})
        elseif @isInRealTimeReflections
            @isInRealTimeReflections = false
            @ResetEyeReflections()

    PreDraw: (ent = @ent, drawingNewTask = false) =>
        return unless @compiled
        return unless @isValid
        @CheckReflections(ent)

        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() or drawingNewTask
            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, drawingNewTask = false) =>
        return unless @compiled
        return unless @isValid
        return unless PPM2.ALTERNATIVE_RENDER\GetBool() or drawingNewTask
        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())

    UpdateNewSocks: (ent = @ent, socksEnt) =>
        return unless @compiled
        return unless @isValid
        socksEnt\SetSubMaterial(0, @NewSocksColor2Name)
        socksEnt\SetSubMaterial(1, @NewSocksColor1Name)
        socksEnt\SetSubMaterial(2, @NewSocksBaseName)

    @QUAD_SIZE_EYES = 256
    @QUAD_SIZE_EYES_HIRES = 1024

    @QUAD_SIZE_SOCKS = 512
    @QUAD_SIZE_SOCKS_HIRES = 1024

    @QUAD_SIZE_CMARK = 256
    @QUAD_SIZE_CMARK_HIRES = 1024

    @QUAD_SIZE_WING = 128
    @QUAD_SIZE_WING_HIRES = 256

    @QUAD_SIZE_HORN = 256
    @QUAD_SIZE_HORN_HIRES = 512

    @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', lightwarpsOnly = false) =>
        return if not matTarget
        PhongExponent, PhongBoost, PhongTint, PhongFresnel, Lightwarp, LightwarpURL = @GetPhongData(prefix)

        if not lightwarpsOnly
            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, @BodyMaterial) if @BodyMaterial
        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')

        @ApplyPhongData(@SocksMaterial, 'Socks') if @SocksMaterial
        @ApplyPhongData(@NewSocksColor1, 'Socks') if @NewSocksColor1
        @ApplyPhongData(@NewSocksColor2, 'Socks') if @NewSocksColor2
        @ApplyPhongData(@NewSocksBase, 'Socks') if @NewSocksBase

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

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

        if @GrabData('SeparateEyes')
            @ApplyPhongData(@EyeMaterialL, 'LEye', true)
            @ApplyPhongData(@EyeMaterialR, 'REye', true)
        else
            @ApplyPhongData(@EyeMaterialL, 'BEyes', true)
            @ApplyPhongData(@EyeMaterialR, 'BEyes', true)

    CompileBody: =>
        return unless @isValid
        urlTextures = {}
        left = 0
        bodysize = @@GetBodySize()

        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Body"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/body'
                '$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'
            }
        }

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

        continueCompilation = ->
            return unless @isValid
            {:r, :g, :b} = @GrabData('BodyColor')
            @StartRT("Body_rt_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", bodysize, r, g, b)

            surface.SetMaterial(@@BODY_MATERIAL)
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            if not @GrabData('LipsColorInherit')
                surface.SetDrawColor(@GrabData('LipsColor'))
            else
                {:r, :g, :b} = @GrabData('BodyColor')
                r, g, b = math.max(r - 30, 0), math.max(g - 30, 0), math.max(b - 30, 0)
                surface.SetDrawColor(r, g, b)

            surface.SetMaterial(_M.LIPS)
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            if not @GrabData('NoseColorInherit')
                surface.SetDrawColor(@GrabData('NoseColor'))
            else
                {:r, :g, :b} = @GrabData('BodyColor')
                r, g, b = math.max(r - 30, 0), math.max(g - 30, 0), math.max(b - 30, 0)
                surface.SetDrawColor(r, g, b)

            surface.SetMaterial(_M.NOSE)
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

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

            for i = 1, PPM2.MAX_BODY_DETAILS
                if mat = _M.BODY_DETAILS[@GrabData("BodyDetail#{i}")]
                    surface.SetDrawColor(@GrabData("BodyDetailColor#{i}"))
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            surface.SetDrawColor(255, 255, 255)

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GrabData("BodyDetailURLColor#{i}"))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            @DrawTattoo(i) for i = 1, PPM2.MAX_TATTOOS when @GrabData("TattooOverDetail#{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)

            @BodyMaterial\SetTexture('$basetexture', @EndRT())

            @StartRT("Body_rtIllum_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", 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)

            @BodyMaterial\SetTexture('$selfillummask', @EndRT())
            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()

        continueCompilation() if left == 0
        return @BodyMaterial

    @BUMP_COLOR = Color(127, 127, 255)
    CompileHorn: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Horn"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/horn'
                '$bumpmap': 'models/ppm/base/horn_normal'
                '$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 = ->
            {:r, :g, :b} = @GrabData('BodyColor')
            {:r, :g, :b} = @GrabData('HornColor') if @GrabData('SeparateHorn')
            @StartRT('Horn', texSize, r, g, b)

            surface.SetDrawColor(@GrabData('HornDetailColor'))
            surface.SetMaterial(@@HORN_DETAIL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

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

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

            @StartRT('Horn_illum', texSize)

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

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

            {:r, :g, :b} = @@BUMP_COLOR
            @StartRT('Horn_bump', texSize, r, g, b)
            alpha = 255
            alpha = @GrabData('HornDetailColor').a
            surface.SetDrawColor(255, 255, 255, alpha)
            surface.SetMaterial(_M.HORN_DETAIL_BUMP)
            surface.DrawTexturedRect(0, 0, texSize, texSize)
            @HornMaterial\SetTexture('$bumpmap', @EndRT())

            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

    CompileNewSocks: =>
        return unless @isValid

        data = {
            '$basetexture': 'models/debug/debugwhite'

            '$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'
        }

        textureColor1 = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_NewSocks_Color1"
            'shader': 'VertexLitGeneric'
            'data': data
        }

        textureColor2 = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_NewSocks_Color2"
            'shader': 'VertexLitGeneric'
            'data': data
        }

        textureBase = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_NewSocks_Base"
            'shader': 'VertexLitGeneric'
            'data': data
        }

        @NewSocksColor1Name = '!' .. textureColor1.name\lower()
        @NewSocksColor2Name = '!' .. textureColor2.name\lower()
        @NewSocksBaseName = '!' .. textureBase.name\lower()

        @NewSocksColor1 = CreateMaterial(textureColor1.name, textureColor1.shader, textureColor1.data)
        @NewSocksColor2 = CreateMaterial(textureColor2.name, textureColor2.shader, textureColor2.data)
        @NewSocksBase = CreateMaterial(textureBase.name, textureBase.shader, textureBase.data)

        @UpdatePhongData()

        url = @GrabData('NewSocksTextureURL')
        if url == '' or not url\find('^https?://')
            {:r, :g, :b} = @GrabData('NewSocksColor1')
            @NewSocksColor1\SetVector('$color', Vector(r / 255, g / 255, b / 255))
            @NewSocksColor1\SetVector('$color2', Vector(r / 255, g / 255, b / 255))

            {:r, :g, :b} = @GrabData('NewSocksColor2')
            @NewSocksColor2\SetVector('$color', Vector(r / 255, g / 255, b / 255))
            @NewSocksColor2\SetVector('$color2', Vector(r / 255, g / 255, b / 255))

            {:r, :g, :b} = @GrabData('NewSocksColor3')
            @NewSocksBase\SetVector('$color', Vector(r / 255, g / 255, b / 255))
            @NewSocksBase\SetVector('$color2', Vector(r / 255, g / 255, b / 255))

            PPM2.DebugPrint('Compiled new socks texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL url, texSize, texSize, (texture, panel, material) ->
                for tex in *{@NewSocksColor1, @NewSocksColor2, @NewSocksBase}
                    tex\SetVector('$color', Vector(1, 1, 1))
                    tex\SetVector('$color2', Vector(1, 1, 1))
                    tex\SetTexture('$basetexture', texture)

        return @NewSocksColor1, @NewSocksColor2, @NewSocksBase

    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))
            @StartRT('Socks', texSize, r, g, b)

            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)

            @SocksMaterial\SetTexture('$basetexture', @EndRT())
            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 = ->
            {:r, :g, :b} = @GrabData('BodyColor')
            {:r, :g, :b} = @GrabData('WingsColor') if @GrabData('SeparateWings')
            rt = @StartRT('Wings_rt', texSize, r, g, b)

            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)
            @EndRT()
            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
            {:r, :g, :b} = @GrabData('ManeColor1')
            @StartRT('Mane_1', texSize, r, g, b)

            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)

            @HairColor1Material\SetTexture('$basetexture', @EndRT())

            -- Second mane pass
            {:r, :g, :b} = @GrabData('ManeColor2')
            @StartRT('Mane_2', texSize, r, g, b)

            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)

            @HairColor2Material\SetTexture('$basetexture', @EndRT())
            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
            {:r, :g, :b} = @GrabData('TailColor1')

            -- First tail pass
            @StartRT('Tail_1', texSize, r, g, b)

            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)

            @TailColor1Material\SetTexture('$basetexture', @EndRT())

            -- Second tail pass
            {:r, :g, :b} = @GrabData('TailColor2')
            @StartRT('Tail_2', texSize, r, g, b)

            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)

            @TailColor2Material\SetTexture('$basetexture', @EndRT())
            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

    @REFLECT_RENDER_SIZE = 64
    @GetReflectionsScale: =>
        val = REAL_TIME_EYE_REFLECTIONS_SIZE\GetInt()
        return @REFLECT_RENDER_SIZE if val % 2 ~= 0
        return val

    ResetEyeReflections: =>
        @EyeMaterialL\SetTexture('$iris', @EyeTextureL)
        @EyeMaterialR\SetTexture('$iris', @EyeTextureR)

    UpdateEyeReflections: (ent = @ent) =>
        @AttachID = @AttachID or @ent\LookupAttachment('eyes')
        local Pos
        local Ang
        {:Pos, :Ang} = @ent\GetAttachment(@AttachID) if ent == @ent
        {:Pos, :Ang} = ent\GetAttachment(ent\LookupAttachment('eyes')) if ent ~= @ent

        return @ResetEyeReflections() if Pos\Distance(EyePos()) > REAL_TIME_EYE_REFLECTIONS_DIST\GetInt()

        scale = @@GetReflectionsScale()
        @lastScale = @lastScale or scale
        if @lastScale ~= scale
            @lastScale = scale
            @reflectRT = nil
            @reflectRTMat = nil

        texName = "PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_EyesReflect_#{scale}"
        reflectrt = @reflectRT or GetRenderTargetEx(
            texName,
            scale,
            scale,
            RT_SIZE_DEFAULT,
            MATERIAL_RT_DEPTH_NONE,
            1 + 32768 + 2048 + 8388608 + 512 + 256,
            CREATERENDERTARGETFLAGS_UNFILTERABLE_OK,
            IMAGE_FORMAT_RGB888
        )

        reflectrt\Download()
        @reflectRT = reflectrt
        @reflectRTMat = @reflectRTMat or CreateMaterial(texName .. '_Mat', 'UnlitGeneric', {
            '$basetexture': 'models/debug/debugwhite'
            '$ignorez': 1
            '$vertexcolor': 1
            '$translucent': 1
            '$alpha': 1
            '$vertexalpha': 1
            '$nolod': 1
        })

        @reflectRTMat\SetTexture('$basetexture', reflectrt)

        render.PushRenderTarget(reflectrt)
        render.Clear(0, 0, 0, 255, true, true)

        viewData = {}
        viewData.drawhud = false
        viewData.drawmonitors = false
        viewData.drawviewmodel = false
        viewData.origin = Pos
        viewData.angles = Ang
        viewData.x = 0
        viewData.y = 0
        viewData.fov = 150
        viewData.w = scale
        viewData.h = scale
        viewData.aspectratio = 1
        viewData.znear = 1
        viewData.zfar = REAL_TIME_EYE_REFLECTIONS_RDIST\GetInt()

        render.RenderView(viewData)
        render.PopRenderTarget()

        oldW, oldH = ScrW(), ScrH()

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_EYES_HIRES or @@QUAD_SIZE_EYES
        render.SetViewPort(0, 0, texSize, texSize)

        surface.DisableClipping(true)
        rtleft = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_LeftReflect_#{scale}", texSize, texSize, false)
        rtleft\Download()
        rtright = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_RightReflect_#{scale}", texSize, texSize, false)
        rtright\Download()

        W, H = 1, 1

        separated = @GrabData('SeparateEyes')
        prefixData = ''
        prefixData = 'Left' if separated

        cam.Start2D()
        render.PushRenderTarget(rtleft)
        render.Clear(0, 0, 0, 255, true, true)

        surface.SetDrawColor(255, 255, 255, 255)
        surface.SetMaterial(@EyeMaterialDrawL)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

        surface.SetDrawColor(255, 255, 255, 255 * @GrabData('EyeGlossyStrength' .. prefixData))
        surface.SetMaterial(@reflectRTMat)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

        cam.End2D()
        render.PopRenderTarget()
        @EyeMaterialL\SetTexture('$iris', rtleft)

        prefixData = 'Right' if separated

        cam.Start2D()
        render.PushRenderTarget(rtright)
        render.Clear(0, 0, 0, 255, true, true)

        surface.SetDrawColor(255, 255, 255, 255)
        surface.SetMaterial(@EyeMaterialDrawR)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

        surface.SetDrawColor(255, 255, 255, 255 * @GrabData('EyeGlossyStrength' .. prefixData))
        surface.SetMaterial(@reflectRTMat)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

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

        render.SetViewPort(0, 0, oldW, oldH)
        @EyeMaterialR\SetTexture('$iris', rtright)
    CompileEye: (left = false) =>
        return unless @isValid
        prefix = left and 'l' or 'r'
        prefixUpper = left and 'L' or 'R'
        prefixUpperR = left and 'R' or 'L'

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

        EyeRefract =        @GrabData("EyeRefract#{prefixData}")
        EyeCornerA =        @GrabData("EyeCornerA#{prefixData}")
        EyeType =           @GrabData("EyeType#{prefixData}")
        EyeBackground =     @GrabData("EyeBackground#{prefixData}")
        EyeHole =           @GrabData("EyeHole#{prefixData}")
        HoleWidth =         @GrabData("HoleWidth#{prefixData}")
        IrisSize =          @GrabData("IrisSize#{prefixData}") * (EyeRefract and .38 or .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}")
        EyeLineDirection =  @GrabData("EyeLineDirection#{prefixData}")
        PonySize =          @GrabData('PonySize')
        PonySize = 1 if IsValid(@ent) and @ent\IsRagdoll()

        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()}_#{EyeRefract and 'EyeRefract' or 'Eyes'}_#{prefix}"
            'shader': EyeRefract and 'EyeRefract' or 'Eyes'
            'data': {
                '$iris': 'models/ppm/base/face/p_base'
                '$irisframe': '0'

                '$ambientoccltexture': 'models/ppm/eyes/eye_extra'
                '$envmap': 'models/ppm/eyes/eye_reflection'
                '$corneatexture': 'models/ppm/eyes/eye_cornea_oval'
                '$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
        @UpdatePhongData()

        IrisPos = texSize / 2 - texSize * IrisSize * PonySize / 2
        IrisQuadSize = texSize * IrisSize * PonySize

        HoleQuadSize = texSize * IrisSize * HoleSize * PonySize
        HolePos = texSize / 2
        holeX = HoleQuadSize * HoleWidth / 2
        holeY = texSize * (IrisSize * HoleSize * HoleHeight * PonySize) / 2
        calcHoleX = HolePos - holeX + holeX * HoleShiftX + shiftX
        calcHoleY = HolePos - holeY + holeY * HoleShiftY + shiftY

        if EyeRefract
            @StartRT("EyeCornea_#{prefix}", texSize)

            if EyeCornerA
                surface.SetMaterial(_M.EYE_CORNERA_OVAL)
                surface.SetDrawColor(255, 255, 255)
                DrawTexturedRectRotated(IrisPos + shiftX - texSize / 16, IrisPos + shiftY - texSize / 16, IrisQuadSize * IrisWidth * 1.5, IrisQuadSize * IrisHeight * 1.5, EyeRotation)

            createdMaterial\SetTexture('$corneatexture', @EndRT())

        if EyeURL == '' or not EyeURL\find('^https?://')
            {:r, :g, :b, :a} = EyeBackground
            rt = @StartRT("#{EyeRefract and 'EyeRefract' or 'Eyes'}_#{prefix}", texSize, r, g, b)
            @["EyeTexture#{prefixUpper}"] = rt

            drawMat = CreateMaterial("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_#{EyeRefract and 'EyeRefract' or 'Eyes'}_RenderMat_#{prefix}", 'UnlitGeneric', {
                '$basetexture': 'models/debug/debugwhite'
                '$ignorez': 1
                '$vertexcolor': 1
                '$vertexalpha': 1
                '$nolod': 1
            })

            @["EyeMaterialDraw#{prefixUpper}"] = drawMat
            drawMat\SetTexture('$basetexture', rt)

            surface.SetDrawColor(EyeIris1)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            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
                lprefix = prefixUpper
                lprefix = prefixUpperR if not EyeLineDirection
                surface.SetDrawColor(EyeIrisLine1)
                surface.SetMaterial(@@["EYE_LINE_#{lprefix}_1"])
                DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

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

            surface.SetDrawColor(EyeHole)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            DrawTexturedRectRotated(calcHoleX, calcHoleY, 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)

            @["EyeMaterial#{prefixUpper}"]\SetTexture('$iris', @EndRT())

            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?://')
            rt = @StartRT('CMark', texSize, 0, 0, 0, 0)

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

            @EndRT()
            @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) ->
                rt = @StartRT('CMark', texSize, 0, 0, 0, 0)

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

                @EndRT()
                @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

PPM2.REAL_TIME_EYE_REFLECTIONS = CreateConVar('ppm2_cl_reflections', '0', {FCVAR_ACRHIVE}, 'Calculate eye reflections in real time. Needs beefy computer.')
REAL_TIME_EYE_REFLECTIONS = PPM2.REAL_TIME_EYE_REFLECTIONS

PPM2.REAL_TIME_EYE_REFLECTIONS_SIZE = CreateConVar('ppm2_cl_reflections_size', '512', {FCVAR_ACRHIVE}, 'Reflections size. Must be multiple to 2! (16, 32, 64, 128, 256)')
REAL_TIME_EYE_REFLECTIONS_SIZE = PPM2.REAL_TIME_EYE_REFLECTIONS_SIZE

PPM2.REAL_TIME_EYE_REFLECTIONS_DIST = CreateConVar('ppm2_cl_reflections_drawdist', '192', {FCVAR_ACRHIVE}, 'Reflections maximal draw distance')
REAL_TIME_EYE_REFLECTIONS_DIST = PPM2.REAL_TIME_EYE_REFLECTIONS_DIST

PPM2.REAL_TIME_EYE_REFLECTIONS_RDIST = CreateConVar('ppm2_cl_reflections_renderdist', '1000', {FCVAR_ACRHIVE}, 'Reflection scene draw distance (ZFar)')
REAL_TIME_EYE_REFLECTIONS_RDIST = PPM2.REAL_TIME_EYE_REFLECTIONS_RDIST

reflectTasks = {}
lastReflectionFrame = 0

hook.Add 'DrawOverlay', 'PPM2.ReflectionsUpdate', (a, b) ->
    return if PPM2.__RENDERING_REFLECTIONS
    return if lastReflectionFrame == FrameNumber()
    lastReflectionFrame = FrameNumber()
    PPM2.__RENDERING_REFLECTIONS = true
    for task in *reflectTasks
        pcall task.ctrl.UpdateEyeReflections, task.ctrl, task.ent
    PPM2.__RENDERING_REFLECTIONS = false
    reflectTasks = {}

hook.Add 'PreDrawEffects', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawEffects', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PreDrawHalos', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawHalos', 'PPM2.ReflectionsUpdate', (-> return true if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PreDrawTranslucentRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1
hook.Add 'PostDrawTranslucentRenderables', 'PPM2.ReflectionsUpdate', (-> return false if PPM2.__RENDERING_REFLECTIONS), -1

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 = _M.BODY_MATERIAL
    @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"] = true
        @EYE_UPDATE_TRIGGER["EyeRefract#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeCornerA#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["EyeLineDirection#{publicName}"] = true
        @EYE_UPDATE_TRIGGER["LEyeLightwarp"] = true
        @EYE_UPDATE_TRIGGER["REyeLightwarp"] = true
        @EYE_UPDATE_TRIGGER["LEyeLightwarpURL"] = true
        @EYE_UPDATE_TRIGGER["REyeLightwarpURL"] = true
        @EYE_UPDATE_TRIGGER["BEyesLightwarp"] = true
        @EYE_UPDATE_TRIGGER["BEyesLightwarpURL"] = 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))
            @lastMaterialUpdate = 0
            @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', 'LipsColor', 'NoseColor', 'LipsColorInherit', 'NoseColorInherit'
                @DelayCompile('CompileBody')
            when 'CMark', 'CMarkType', 'CMarkURL', 'CMarkColor', 'CMarkSize'
                @DelayCompile('CompileCMark')
            when 'SocksColor', 'SocksTextureURL', 'SocksTexture', 'SocksDetailColor1', 'SocksDetailColor2', 'SocksDetailColor3', 'SocksDetailColor4', 'SocksDetailColor5', 'SocksDetailColor6'
                @DelayCompile('CompileSocks')
            when 'NewSocksColor1', 'NewSocksColor2', 'NewSocksColor3', 'NewSocksTextureURL'
                @DelayCompile('CompileNewSocks')
            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}, timeouts: 0})
        --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
                table.remove(@HTML_MATERIAL_QUEUE, 1)
                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][data.height][data.url] = {
                    texture: texture
                    material: newMat
                }

                @ALREADY_DOWNLOADING[data.width][data.height][data.url] = false

                for callback in *data.callbacks
                    callback(texture, panel, newMat)
                timer.Simple 0, -> panel\Remove() if IsValid(panel)
            return
        data.frame = 0
        panel = vgui.Create('DHTML')
        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
        data.timerid = "PPM2.TextureMaterialTimeout.#{math.random(1, 100000)}"
        timer.Create data.timerid, 8, 1, ->
            return unless IsValid(panel)
            panel\Remove()
            if data.timeouts >= 4
                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)

                table.remove(@HTML_MATERIAL_QUEUE, 1)
            else
                data.timeouts += 1
                table.remove(@HTML_MATERIAL_QUEUE, 1)
                table.insert(@HTML_MATERIAL_QUEUE, data)

    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: => @BodyMaterial
    GetBodyName: => @BodyMaterialName
    GetSocks: => @SocksMaterial
    GetSocksName: => @SocksMaterialName
    GetNewSocks: => @NewSocksColor1, @NewSocksColor2, @NewSocksBase
    GetNewSocksName: => @NewSocksColor1Name, @NewSocksColor2Name, @NewSocksBaseName
    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()
        @CompileNewSocks()
        @CompileEye(false)
        @CompileEye(true)
        @compiled = true

    StartRT: (name, texSize, r = 0, g = 0, b = 0, a = 255) =>
        error('Attempt to start new render target without finishing the old one!\nUPCOMING =======' .. debug.traceback() .. '\nCURRENT =======' .. @currentRTTrace) if @currentRT
        @currentRTTrace = debug.traceback()
        @oldW, @oldH = ScrW(), ScrH()
        render.SetViewPort(0, 0, texSize, texSize)
        rt = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{name}", texSize, texSize, false)
        rt\Download()
        render.PushRenderTarget(rt)
        render.Clear(r, g, b, a, true, true)
        surface.DisableClipping(true)
        cam.Start2D()
        surface.SetDrawColor(r, g, b, a)
        surface.DrawRect(0, 0, texSize, texSize)
        @currentRT = rt
        return rt

    EndRT: =>
        cam.End2D()
        render.SetViewPort(0, 0, @oldW, @oldH)
        render.PopRenderTarget()
        surface.DisableClipping(false)
        rt = @currentRT
        @currentRT = nil
        return rt

    CheckReflections: (ent = @ent) =>
        if REAL_TIME_EYE_REFLECTIONS\GetBool()
            @isInRealTimeReflections = true
            table.insert(reflectTasks, {ctrl: @, ent: ent})
        elseif @isInRealTimeReflections
            @isInRealTimeReflections = false
            @ResetEyeReflections()

    PreDraw: (ent = @ent, drawingNewTask = false) =>
        return unless @compiled
        return unless @isValid
        @CheckReflections(ent)

        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() or drawingNewTask
            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, drawingNewTask = false) =>
        return unless @compiled
        return unless @isValid
        return unless PPM2.ALTERNATIVE_RENDER\GetBool() or drawingNewTask
        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())

    UpdateNewSocks: (ent = @ent, socksEnt) =>
        return unless @compiled
        return unless @isValid
        socksEnt\SetSubMaterial(0, @NewSocksColor2Name)
        socksEnt\SetSubMaterial(1, @NewSocksColor1Name)
        socksEnt\SetSubMaterial(2, @NewSocksBaseName)

    @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 = 256
    @QUAD_SIZE_HORN_HIRES = 512

    @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', lightwarpsOnly = false) =>
        return if not matTarget
        PhongExponent, PhongBoost, PhongTint, PhongFresnel, Lightwarp, LightwarpURL = @GetPhongData(prefix)

        if not lightwarpsOnly
            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, @BodyMaterial) if @BodyMaterial
        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')

        @ApplyPhongData(@SocksMaterial, 'Socks') if @SocksMaterial
        @ApplyPhongData(@NewSocksColor1, 'Socks') if @NewSocksColor1
        @ApplyPhongData(@NewSocksColor2, 'Socks') if @NewSocksColor2
        @ApplyPhongData(@NewSocksBase, 'Socks') if @NewSocksBase

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

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

        if @GrabData('SeparateEyes')
            @ApplyPhongData(@EyeMaterialL, 'LEye', true)
            @ApplyPhongData(@EyeMaterialR, 'REye', true)
        else
            @ApplyPhongData(@EyeMaterialL, 'BEyes', true)
            @ApplyPhongData(@EyeMaterialR, 'BEyes', true)

    CompileBody: =>
        return unless @isValid
        urlTextures = {}
        left = 0
        bodysize = @@GetBodySize()

        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Body"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/body'
                '$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'
            }
        }

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

        continueCompilation = ->
            return unless @isValid
            {:r, :g, :b} = @GrabData('BodyColor')
            @StartRT("Body_rt_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", bodysize, r, g, b)

            surface.SetMaterial(@@BODY_MATERIAL)
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            if not @GrabData('LipsColorInherit')
                surface.SetDrawColor(@GrabData('LipsColor'))
            else
                {:r, :g, :b} = @GrabData('BodyColor')
                r, g, b = math.max(r - 30, 0), math.max(g - 30, 0), math.max(b - 30, 0)
                surface.SetDrawColor(r, g, b)

            surface.SetMaterial(_M.LIPS)
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            if not @GrabData('NoseColorInherit')
                surface.SetDrawColor(@GrabData('NoseColor'))
            else
                {:r, :g, :b} = @GrabData('BodyColor')
                r, g, b = math.max(r - 30, 0), math.max(g - 30, 0), math.max(b - 30, 0)
                surface.SetDrawColor(r, g, b)

            surface.SetMaterial(_M.NOSE)
            surface.DrawTexturedRect(0, 0, bodysize, bodysize)

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

            for i = 1, PPM2.MAX_BODY_DETAILS
                if mat = _M.BODY_DETAILS[@GrabData("BodyDetail#{i}")]
                    surface.SetDrawColor(@GrabData("BodyDetailColor#{i}"))
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            surface.SetDrawColor(255, 255, 255)

            for i, mat in pairs urlTextures
                surface.SetDrawColor(@GrabData("BodyDetailURLColor#{i}"))
                surface.SetMaterial(mat)
                surface.DrawTexturedRect(0, 0, bodysize, bodysize)

            @DrawTattoo(i) for i = 1, PPM2.MAX_TATTOOS when @GrabData("TattooOverDetail#{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)

            @BodyMaterial\SetTexture('$basetexture', @EndRT())

            @StartRT("Body_rtIllum_#{USE_HIGHRES_BODY\GetBool() and 'hd' or USE_HIGHRES_TEXTURES\GetBool() and 'hq' or 'normal'}", 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)

            @BodyMaterial\SetTexture('$selfillummask', @EndRT())
            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()

        continueCompilation() if left == 0
        return @BodyMaterial

    @BUMP_COLOR = Color(127, 127, 255)
    CompileHorn: =>
        return unless @isValid
        textureData = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_Horn"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/horn'
                '$bumpmap': 'models/ppm/base/horn_normal'
                '$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 = ->
            {:r, :g, :b} = @GrabData('BodyColor')
            {:r, :g, :b} = @GrabData('HornColor') if @GrabData('SeparateHorn')
            @StartRT('Horn', texSize, r, g, b)

            surface.SetDrawColor(@GrabData('HornDetailColor'))
            surface.SetMaterial(@@HORN_DETAIL_COLOR)
            surface.DrawTexturedRect(0, 0, texSize, texSize)

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

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

            @StartRT('Horn_illum', texSize)

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

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

            {:r, :g, :b} = @@BUMP_COLOR
            @StartRT('Horn_bump', texSize, r, g, b)
            alpha = 255
            alpha = @GrabData('HornDetailColor').a
            surface.SetDrawColor(255, 255, 255, alpha)
            surface.SetMaterial(_M.HORN_DETAIL_BUMP)
            surface.DrawTexturedRect(0, 0, texSize, texSize)
            @HornMaterial\SetTexture('$bumpmap', @EndRT())

            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

    CompileNewSocks: =>
        return unless @isValid

        data = {
            '$basetexture': 'models/debug/debugwhite'

            '$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'
        }

        textureColor1 = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_NewSocks_Color1"
            'shader': 'VertexLitGeneric'
            'data': data
        }

        textureColor2 = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_NewSocks_Color2"
            'shader': 'VertexLitGeneric'
            'data': data
        }

        textureBase = {
            'name': "PPM2_#{@@SessionID}_#{@GetID()}_NewSocks_Base"
            'shader': 'VertexLitGeneric'
            'data': data
        }

        @NewSocksColor1Name = '!' .. textureColor1.name\lower()
        @NewSocksColor2Name = '!' .. textureColor2.name\lower()
        @NewSocksBaseName = '!' .. textureBase.name\lower()

        @NewSocksColor1 = CreateMaterial(textureColor1.name, textureColor1.shader, textureColor1.data)
        @NewSocksColor2 = CreateMaterial(textureColor2.name, textureColor2.shader, textureColor2.data)
        @NewSocksBase = CreateMaterial(textureBase.name, textureBase.shader, textureBase.data)

        @UpdatePhongData()

        url = @GrabData('NewSocksTextureURL')
        if url == '' or not url\find('^https?://')
            {:r, :g, :b} = @GrabData('NewSocksColor1')
            @NewSocksColor1\SetVector('$color', Vector(r / 255, g / 255, b / 255))
            @NewSocksColor1\SetVector('$color2', Vector(r / 255, g / 255, b / 255))

            {:r, :g, :b} = @GrabData('NewSocksColor2')
            @NewSocksColor2\SetVector('$color', Vector(r / 255, g / 255, b / 255))
            @NewSocksColor2\SetVector('$color2', Vector(r / 255, g / 255, b / 255))

            {:r, :g, :b} = @GrabData('NewSocksColor3')
            @NewSocksBase\SetVector('$color', Vector(r / 255, g / 255, b / 255))
            @NewSocksBase\SetVector('$color2', Vector(r / 255, g / 255, b / 255))

            PPM2.DebugPrint('Compiled new socks texture for ', @ent, ' as part of ', @)
        else
            @@LoadURL url, texSize, texSize, (texture, panel, material) ->
                for tex in *{@NewSocksColor1, @NewSocksColor2, @NewSocksBase}
                    tex\SetVector('$color', Vector(1, 1, 1))
                    tex\SetVector('$color2', Vector(1, 1, 1))
                    tex\SetTexture('$basetexture', texture)

        return @NewSocksColor1, @NewSocksColor2, @NewSocksBase

    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))
            @StartRT('Socks', texSize, r, g, b)

            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)

            @SocksMaterial\SetTexture('$basetexture', @EndRT())
            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 = ->
            {:r, :g, :b} = @GrabData('BodyColor')
            {:r, :g, :b} = @GrabData('WingsColor') if @GrabData('SeparateWings')
            rt = @StartRT('Wings_rt', texSize, r, g, b)

            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)
            @EndRT()
            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
            {:r, :g, :b} = @GrabData('ManeColor1')
            @StartRT('Mane_1', texSize, r, g, b)

            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)

            @HairColor1Material\SetTexture('$basetexture', @EndRT())

            -- Second mane pass
            {:r, :g, :b} = @GrabData('ManeColor2')
            @StartRT('Mane_2', texSize, r, g, b)

            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)

            @HairColor2Material\SetTexture('$basetexture', @EndRT())
            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
            {:r, :g, :b} = @GrabData('TailColor1')

            -- First tail pass
            @StartRT('Tail_1', texSize, r, g, b)

            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)

            @TailColor1Material\SetTexture('$basetexture', @EndRT())

            -- Second tail pass
            {:r, :g, :b} = @GrabData('TailColor2')
            @StartRT('Tail_2', texSize, r, g, b)

            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)

            @TailColor2Material\SetTexture('$basetexture', @EndRT())
            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

    @REFLECT_RENDER_SIZE = 64
    @GetReflectionsScale: =>
        val = REAL_TIME_EYE_REFLECTIONS_SIZE\GetInt()
        return @REFLECT_RENDER_SIZE if val % 2 ~= 0
        return val

    ResetEyeReflections: =>
        @EyeMaterialL\SetTexture('$iris', @EyeTextureL)
        @EyeMaterialR\SetTexture('$iris', @EyeTextureR)

    UpdateEyeReflections: (ent = @ent) =>
        @AttachID = @AttachID or @ent\LookupAttachment('eyes')
        local Pos
        local Ang
        {:Pos, :Ang} = @ent\GetAttachment(@AttachID) if ent == @ent
        {:Pos, :Ang} = ent\GetAttachment(ent\LookupAttachment('eyes')) if ent ~= @ent

        return @ResetEyeReflections() if Pos\Distance(EyePos()) > REAL_TIME_EYE_REFLECTIONS_DIST\GetInt()

        scale = @@GetReflectionsScale()
        @lastScale = @lastScale or scale
        if @lastScale ~= scale
            @lastScale = scale
            @reflectRT = nil
            @reflectRTMat = nil

        texName = "PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_EyesReflect_#{scale}"
        reflectrt = @reflectRT or GetRenderTargetEx(
            texName,
            scale,
            scale,
            RT_SIZE_DEFAULT,
            MATERIAL_RT_DEPTH_NONE,
            1 + 32768 + 2048 + 8388608 + 512 + 256,
            CREATERENDERTARGETFLAGS_UNFILTERABLE_OK,
            IMAGE_FORMAT_RGB888
        )

        reflectrt\Download()
        @reflectRT = reflectrt
        @reflectRTMat = @reflectRTMat or CreateMaterial(texName .. '_Mat', 'UnlitGeneric', {
            '$basetexture': 'models/debug/debugwhite'
            '$ignorez': 1
            '$vertexcolor': 1
            '$translucent': 1
            '$alpha': 1
            '$vertexalpha': 1
            '$nolod': 1
        })

        @reflectRTMat\SetTexture('$basetexture', reflectrt)

        render.PushRenderTarget(reflectrt)
        render.Clear(0, 0, 0, 255, true, true)

        viewData = {}
        viewData.drawhud = false
        viewData.drawmonitors = false
        viewData.drawviewmodel = false
        viewData.origin = Pos
        viewData.angles = Ang
        viewData.x = 0
        viewData.y = 0
        viewData.fov = 150
        viewData.w = scale
        viewData.h = scale
        viewData.aspectratio = 1
        viewData.znear = 1
        viewData.zfar = REAL_TIME_EYE_REFLECTIONS_RDIST\GetInt()

        render.RenderView(viewData)
        render.PopRenderTarget()

        oldW, oldH = ScrW(), ScrH()

        texSize = USE_HIGHRES_TEXTURES\GetBool() and @@QUAD_SIZE_EYES_HIRES or @@QUAD_SIZE_EYES
        render.SetViewPort(0, 0, texSize, texSize)

        surface.DisableClipping(true)
        rtleft = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_LeftReflect_#{scale}", texSize, texSize, false)
        rtleft\Download()
        rtright = GetRenderTarget("PPM2_#{@@SessionID}_#{@GetID()}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_RightReflect_#{scale}", texSize, texSize, false)
        rtright\Download()

        W, H = 1, 1

        separated = @GrabData('SeparateEyes')
        prefixData = ''
        prefixData = 'Left' if separated

        cam.Start2D()
        render.PushRenderTarget(rtleft)
        render.Clear(0, 0, 0, 255, true, true)

        surface.SetDrawColor(255, 255, 255, 255)
        surface.SetMaterial(@EyeMaterialDrawL)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

        surface.SetDrawColor(255, 255, 255, 255 * @GrabData('EyeGlossyStrength' .. prefixData))
        surface.SetMaterial(@reflectRTMat)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

        cam.End2D()
        render.PopRenderTarget()
        @EyeMaterialL\SetTexture('$iris', rtleft)

        prefixData = 'Right' if separated

        cam.Start2D()
        render.PushRenderTarget(rtright)
        render.Clear(0, 0, 0, 255, true, true)

        surface.SetDrawColor(255, 255, 255, 255)
        surface.SetMaterial(@EyeMaterialDrawR)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

        surface.SetDrawColor(255, 255, 255, 255 * @GrabData('EyeGlossyStrength' .. prefixData))
        surface.SetMaterial(@reflectRTMat)
        surface.DrawTexturedRect(0, 0, texSize * W, texSize * H)

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

        render.SetViewPort(0, 0, oldW, oldH)
        @EyeMaterialR\SetTexture('$iris', rtright)
    CompileEye: (left = false) =>
        return unless @isValid
        prefix = left and 'l' or 'r'
        prefixUpper = left and 'L' or 'R'
        prefixUpperR = left and 'R' or 'L'

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

        EyeRefract =        @GrabData("EyeRefract#{prefixData}")
        EyeCornerA =        @GrabData("EyeCornerA#{prefixData}")
        EyeType =           @GrabData("EyeType#{prefixData}")
        EyeBackground =     @GrabData("EyeBackground#{prefixData}")
        EyeHole =           @GrabData("EyeHole#{prefixData}")
        HoleWidth =         @GrabData("HoleWidth#{prefixData}")
        IrisSize =          @GrabData("IrisSize#{prefixData}") * (EyeRefract and .38 or .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}")
        EyeLineDirection =  @GrabData("EyeLineDirection#{prefixData}")
        PonySize =          @GrabData('PonySize')
        PonySize = 1 if IsValid(@ent) and @ent\IsRagdoll()

        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()}_#{EyeRefract and 'EyeRefract' or 'Eyes'}_#{prefix}"
            'shader': EyeRefract and 'EyeRefract' or 'Eyes'
            'data': {
                '$iris': 'models/ppm/base/face/p_base'
                '$irisframe': '0'

                '$ambientoccltexture': 'models/ppm/eyes/eye_extra'
                '$envmap': 'models/ppm/eyes/eye_reflection'
                '$corneatexture': 'models/ppm/eyes/eye_cornea_oval'
                '$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
        @UpdatePhongData()

        IrisPos = texSize / 2 - texSize * IrisSize * PonySize / 2
        IrisQuadSize = texSize * IrisSize * PonySize

        HoleQuadSize = texSize * IrisSize * HoleSize * PonySize
        HolePos = texSize / 2
        holeX = HoleQuadSize * HoleWidth / 2
        holeY = texSize * (IrisSize * HoleSize * HoleHeight * PonySize) / 2
        calcHoleX = HolePos - holeX + holeX * HoleShiftX + shiftX
        calcHoleY = HolePos - holeY + holeY * HoleShiftY + shiftY

        if EyeRefract
            @StartRT("EyeCornea_#{prefix}", texSize)

            if EyeCornerA
                surface.SetMaterial(_M.EYE_CORNERA_OVAL)
                surface.SetDrawColor(255, 255, 255)
                DrawTexturedRectRotated(IrisPos + shiftX - texSize / 16, IrisPos + shiftY - texSize / 16, IrisQuadSize * IrisWidth * 1.5, IrisQuadSize * IrisHeight * 1.5, EyeRotation)

            createdMaterial\SetTexture('$corneatexture', @EndRT())

        if EyeURL == '' or not EyeURL\find('^https?://')
            {:r, :g, :b, :a} = EyeBackground
            rt = @StartRT("#{EyeRefract and 'EyeRefract' or 'Eyes'}_#{prefix}", texSize, r, g, b)
            @["EyeTexture#{prefixUpper}"] = rt

            drawMat = CreateMaterial("PPM2_#{@@SessionID}_#{USE_HIGHRES_TEXTURES\GetBool() and 'HD' or 'NORMAL'}_#{@GetID()}_#{EyeRefract and 'EyeRefract' or 'Eyes'}_RenderMat_#{prefix}", 'UnlitGeneric', {
                '$basetexture': 'models/debug/debugwhite'
                '$ignorez': 1
                '$vertexcolor': 1
                '$vertexalpha': 1
                '$nolod': 1
            })

            @["EyeMaterialDraw#{prefixUpper}"] = drawMat
            drawMat\SetTexture('$basetexture', rt)

            surface.SetDrawColor(EyeIris1)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            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
                lprefix = prefixUpper
                lprefix = prefixUpperR if not EyeLineDirection
                surface.SetDrawColor(EyeIrisLine1)
                surface.SetMaterial(@@["EYE_LINE_#{lprefix}_1"])
                DrawTexturedRectRotated(IrisPos + shiftX, IrisPos + shiftY, IrisQuadSize * IrisWidth, IrisQuadSize * IrisHeight, EyeRotation)

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

            surface.SetDrawColor(EyeHole)
            surface.SetMaterial(@@EYE_OVALS[EyeType + 1] or @EYE_OVAL)
            DrawTexturedRectRotated(calcHoleX, calcHoleY, 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)

            @["EyeMaterial#{prefixUpper}"]\SetTexture('$iris', @EndRT())

            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?://')
            rt = @StartRT('CMark', texSize, 0, 0, 0, 0)

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

            @EndRT()
            @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) ->
                rt = @StartRT('CMark', texSize, 0, 0, 0, 0)

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

                @EndRT()
                @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