trigger: use namespace triggers instead of triggers

Former-commit-id: 08cdfedf9017e44dac87600ac1a8489ee168defc [formerly 43bec4ffc2aefde8f17978423f3a926688a37fc1]
Former-commit-id: 6cf90a6bb6235385c62d09ad09d32d3f00ebcff7

sql = require'lsqlite3'

openDb = ->
  db = sql.open 'cache/trigger.sql'

  db\exec [[
    CREATE TABLE IF NOT EXISTS trigger (
      name UNIQUE ON CONFLICT REPLACE,
      pattern UNIQUE ON CONFLICT REPLACE,
      funcstr
    );
    ]]
  return db

triggerHelp  = (source, destination, argument) =>
   @Msg 'privmsg', destination, source, 'Usage: !trigger add <name>|<lua pattern>|<lua code>. Ex: !trigger add help|^!help (%w+)|say "%s, you need help"'

-- Construct a safe environ for trigger with two functions; say and reply
--- Preferrably you should be able to cross-call modules here to make aliasing possible
sandbox = (func) ->
  (source, destination, ...) =>
    -- Store args so we can use them in env
    arg = {...}
    env =
      say: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      reply: (str) ->
        @Msg 'privmsg', destination, source, source.nick..': '..str, unpack(arg),
      print: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      simplehttp:require'simplehttp'
      json:require'json'
      :string
      :type
      :tostring
      :tonumber
      :ipars
      :pairs
      :table
      :next

    --setfenv func, setmetatable(env, {__index: _G })
    setfenv func, env
    success, err = pcall func, arg
    if err
      @Msg 'privmsg', destination, source, err
    else
      success

-- Construct a safe function to run and return a handler
triggerHandler = (source, destination, funcstr) =>
  func, err = loadstring funcstr
  unless func
    @Msg 'privmsg', destination, source, 'Trigger error: %s', err
  else
    sandbox(func)

-- Register the command
regCommand = (source, destination, name, pattern, funcstr) =>
  db = openDb!
  ins = db\prepare "INSERT INTO trigger (name, pattern, funcstr) VALUES(?, ?, ?)"
  code = ins\bind_values name, pattern, funcstr
  code = ins\step!
  code = ins\finalize!
  db\close!

  @RegisterCommand 'trigger', pattern, triggerHandler(@, source, destination, funcstr)

delCommand = (source, destination, name) =>
  db = openDb!
  -- Find pattern which is used for deletion of handler
  stmt = db\prepare "SELECT pattern FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  code = stmt\step!
  if code == sqlite3.DONE
    -- Invalid name
    db\close!
    return
  pattern = stmt\get_values()[1]

  -- Delete trigger from db
  ins = db\prepare "DELETE FROM trigger WHERE name = ?"
  code = ins\bind_values pattern
  code = ins\step!
  code = ins\finalize!
  db\close!

  @UnregisterCommand 'triggers', pattern

-- Register commands on startup
db = openDb!
for row in db\nrows 'SELECT pattern, funcstr FROM trigger'
  ivar2\RegisterCommand 'triggers', row.pattern, triggerHandler(ivar2, nil, nil, row.funcstr)
db\close!

PRIVMSG:
  '%ptrigger$': triggerHelp
  '%ptrigger add (.+)|(.+)|(.+)$': regCommand
  '%ptrigger del (.+)$': delCommand

sql = require'lsqlite3'

openDb = ->
  db = sql.open 'cache/trigger.sql'

  db\exec [[
    CREATE TABLE IF NOT EXISTS trigger (
      name UNIQUE ON CONFLICT REPLACE,
      pattern UNIQUE ON CONFLICT REPLACE,
      funcstr
    );
    ]]
  return db

triggerHelp  = (source, destination, argument) =>
   @Msg 'privmsg', destination, source, 'Usage: !trigger add <name>|<lua pattern>|<lua code>. Ex: !trigger add help|^!help (%w+)|say "%s, you need help"'

-- Construct a safe environ for trigger with two functions; say and reply
--- Preferrably you should be able to cross-call modules here to make aliasing possible
sandbox = (func) ->
  (source, destination, ...) =>
    -- Store args so we can use them in env
    arg = {...}
    env =
      say: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      reply: (str) ->
        @Msg 'privmsg', destination, source, source.nick..': '..str, unpack(arg),
      print: (str) ->
        @Msg 'privmsg', destination, source, str, unpack(arg),
      simplehttp:require'simplehttp'
      json:require'json'
      :string
      :type
      :tostring
      :tonumber
      :ipars
      :pairs
      :table
      :next

    --setfenv func, setmetatable(env, {__index: _G })
    setfenv func, env
    success, err = pcall func, arg
    if err
      @Msg 'privmsg', destination, source, err
    else
      success

-- Construct a safe function to run and return a handler
triggerHandler = (source, destination, funcstr) =>
  func, err = loadstring funcstr
  unless func
    @Msg 'privmsg', destination, source, 'Trigger error: %s', err
  else
    sandbox(func)

-- Register the command
regCommand = (source, destination, name, pattern, funcstr) =>
  db = openDb!
  ins = db\prepare "INSERT INTO trigger (name, pattern, funcstr) VALUES(?, ?, ?)"
  code = ins\bind_values name, pattern, funcstr
  code = ins\step!
  code = ins\finalize!
  db\close!

  @RegisterCommand 'trigger', pattern, triggerHandler(@, source, destination, funcstr)

delCommand = (source, destination, name) =>
  db = openDb!
  -- Find pattern which is used for deletion of handler
  stmt = db\prepare "SELECT pattern FROM trigger WHERE name = ?"
  code = stmt\bind_values name
  code = stmt\step!
  if code == sqlite3.DONE
    -- Invalid name
    db\close!
    return
  pattern = stmt\get_values()[1]

  -- Delete trigger from db
  ins = db\prepare "DELETE FROM trigger WHERE name = ?"
  code = ins\bind_values pattern
  code = ins\step!
  code = ins\finalize!
  db\close!

  @UnregisterCommand 'trigger', pattern

-- Register commands on startup
db = openDb!
for row in db\nrows 'SELECT pattern, funcstr FROM trigger'
  ivar2\RegisterCommand 'trigger', row.pattern, triggerHandler(ivar2, nil, nil, row.funcstr)
db\close!

PRIVMSG:
  '%ptrigger$': triggerHelp
  '%ptrigger add (.+)|(.+)|(.+)$': regCommand
  '%ptrigger del (.+)$': delCommand


Nicer internal input module

Nicer internal input module


Refactored function declaration

Refactored function declaration

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

for ply in *player.GetAll()
    ply.__PPM2_PonyData\Remove() if ply.__PPM2_PonyData

wUInt = (def = 0, size = 8) ->
    return (arg = def) -> net.WriteUInt(arg, size)

rUInt = (size = 8, min = 0, max = 255) ->
    return -> math.Clamp(net.ReadUInt(size), min, max)

rFloat = (min = 0, max = 255) ->
    return -> math.Clamp(net.ReadFloat(), min, max)

wFloat = net.WriteFloat
rSEnt = net.ReadStrongEntity
wSEnt = net.WriteStrongEntity
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

class NetworkedPonyData extends PPM2.NetworkedObject
    @NW_ClientsideCreation = true
    @RenderTasks = {}

    @Setup()
    @NetworkVar('Entity',           rSEnt, wSEnt, StrongEntity(-1), ((newValue) => IsValid(@GetOwner()) and StrongEntity(@GetOwner()\EntIndex()) or newValue))
    @NetworkVar('UpperManeModel',   rSEnt, wSEnt, StrongEntity(-1), nil, false)
    @NetworkVar('LowerManeModel',   rSEnt, wSEnt, StrongEntity(-1), nil, false)
    @NetworkVar('TailModel',        rSEnt, wSEnt, StrongEntity(-1), nil, false)
    @NetworkVar('SocksModel',       rSEnt, wSEnt, StrongEntity(-1), nil, false)

    @NetworkVar('Race',             rUInt(4, 0, 3), wUInt(PPM2.RACE_EARTH, 4), PPM2.RACE_EARTH)
    @NetworkVar('Gender',           rUInt(4, 0, 1), wUInt(PPM2.GENDER_FEMALE, 4), PPM2.GENDER_FEMALE)
    @NetworkVar('Weight',           rFloat(PPM2.MIN_WEIGHT, PPM2.MAX_WEIGHT), wFloat, 1)
    @NetworkVar('PonySize',         rFloat(PPM2.MIN_SCALE, PPM2.MAX_SCALE),   wFloat, 1)
    @NetworkVar('NeckSize',         rFloat(PPM2.MIN_NECK, PPM2.MAX_NECK),     wFloat, 1)
    @NetworkVar('LegsSize',         rFloat(PPM2.MIN_LEGS, PPM2.MAX_LEGS),     wFloat, 1)

    -- Reserved - they can be accessed/used/changed, but they do not do anything
    @NetworkVar('Age',              rUInt(4, 0, 2), wUInt(PPM2.AGE_ADULT, 4), PPM2.AGE_ADULT)

    @NetworkVar('EyelashType',      rUInt(8, PPM2.MIN_EYELASHES, PPM2.MAX_EYELASHES),           wUInt(0, 8), 0)
    @NetworkVar('TailType',         rUInt(8, PPM2.MIN_TAILS, PPM2.MAX_TAILS),                   wUInt(0, 8), 0)
    @NetworkVar('ManeType',         rUInt(8, PPM2.MIN_UPPER_MANES, PPM2.MAX_UPPER_MANES),       wUInt(0, 8), 0)
    @NetworkVar('ManeTypeLower',    rUInt(8, PPM2.MIN_LOWER_MANES, PPM2.MAX_LOWER_MANES),       wUInt(0, 8), 0)

    @NetworkVar('TailTypeNew',      rUInt(8, PPM2.MIN_TAILS_NEW, PPM2.MAX_TAILS_NEW),               wUInt(0, 8), 0)
    @NetworkVar('ManeTypeNew',      rUInt(8, PPM2.MIN_UPPER_MANES_NEW, PPM2.MAX_UPPER_MANES_NEW),   wUInt(0, 8), 0)
    @NetworkVar('ManeTypeLowerNew', rUInt(8, PPM2.MIN_LOWER_MANES_NEW, PPM2.MAX_LOWER_MANES_NEW),   wUInt(0, 8), 0)

    @NetworkVar('BodyColor',        rColor, wColor, 	    Color(255, 255, 255))

    @NetworkVar('SeparateEyes',     rBool, wBool,              false)
    for publicName in *{'', 'Left', 'Right'}
        @NetworkVar("EyeType#{publicName}",          rUInt(8, PPM2.MIN_EYE_TYPE, PPM2.MAX_EYE_TYPE), wUInt(0, 8), 0)
        @NetworkVar("EyeBackground#{publicName}",    rColor, wColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeHole#{publicName}",          rColor, wColor, 	    Color(0,   0,   0  ))
        @NetworkVar("EyeIrisTop#{publicName}",       rColor, wColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisBottom#{publicName}",    rColor, wColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisLine1#{publicName}",     rColor, wColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeIrisLine2#{publicName}",     rColor, wColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeReflection#{publicName}",    rColor, wColor,    Color(255, 255, 255, 127))
        @NetworkVar("EyeEffect#{publicName}",        rColor, wColor,         Color(255, 255, 255))
        @NetworkVar("EyeLines#{publicName}",         rBool, wBool,                           true)
        @NetworkVar("DerpEyes#{publicName}",         rBool, wBool,                          false)
        @NetworkVar("DerpEyesStrength#{publicName}", rFloat(PPM2.MIN_DERP_STRENGTH, PPM2.MAX_DERP_STRENGTH), wFloat, 1)
        @NetworkVar("HoleWidth#{publicName}",        rFloat(PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE), wFloat,  1)
        @NetworkVar("HoleHeight#{publicName}",       rFloat(PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE), wFloat,  1)
        @NetworkVar("HoleSize#{publicName}",         rFloat(PPM2.MIN_HOLE, PPM2.MAX_HOLE),             wFloat, .8)
        @NetworkVar("HoleShiftX#{publicName}",       rFloat(PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT), wFloat, 0)
        @NetworkVar("HoleShiftY#{publicName}",       rFloat(PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT), wFloat, 0)
        @NetworkVar("IrisSize#{publicName}",         rFloat(PPM2.MIN_IRIS, PPM2.MAX_IRIS),             wFloat, .8)
        @NetworkVar("IrisWidth#{publicName}",        rFloat(PPM2.MIN_IRIS, PPM2.MAX_IRIS),             wFloat, 1)
        @NetworkVar("IrisHeight#{publicName}",       rFloat(PPM2.MIN_IRIS, PPM2.MAX_IRIS),             wFloat, 1)
        @NetworkVar("EyeRotation#{publicName}",      rUInt(12, PPM2.MIN_EYE_ROTATION, PPM2.MAX_EYE_ROTATION), wUInt(0, 12), 0)
        @NetworkVar("EyeURL#{publicName}",           rString,                                                            wString, '')

    @NetworkVar('SeparateMane',     rBool, wBool,              false)
    for i = 1, 6
        @NetworkVar("ManeColor#{i}",            rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("ManeDetailColor#{i}",      rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("ManeURLColor#{i}",         rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("ManeURL#{i}",              rString, wString,   '')

        @NetworkVar("TailColor#{i}",            rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("TailDetailColor#{i}",      rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("TailURLColor#{i}",         rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("TailURL#{i}",              rString, wString,   '')

        @NetworkVar("LowerManeColor#{i}",       rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeURL#{i}",         rString, wString,   '')
        @NetworkVar("LowerManeURLColor#{i}",    rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeDetailColor#{i}", rColor, wColor,     Color(255, 255, 255))

        @NetworkVar("UpperManeColor#{i}",       rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURL#{i}",         rString, wString,   '')
        @NetworkVar("UpperManeDetailColor#{i}", rColor, wColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURLColor#{i}",    rColor, wColor,     Color(255, 255, 255))

    @NetworkVar('CMark',            rBool, wBool,                    true)
    @NetworkVar('CMarkSize',        rFloat(0, 1), wFloat,            1)
    @NetworkVar('CMarkColor',       rColor, wColor,                  Color(255, 255, 255))
    @NetworkVar('CMarkURL',         rString, wString,                '')
    @NetworkVar('CMarkType',        rUInt(8, PPM2.MIN_CMARK, PPM2.MAX_CMARK),       wUInt(4, 8), 4)
    @NetworkVar('TailSize',         rFloat(PPM2.MIN_TAIL_SIZE, PPM2.MAX_TAIL_SIZE), wFloat, 1)

    @NetworkVar('Bodysuit',         rUInt(8, PPM2.MIN_SUIT, PPM2.MAX_SUIT),   wUInt(0, 8), 0)
    @NetworkVar('Socks',            rBool, wBool,              false)
    @NetworkVar('NoFlex',           rBool, wBool,              false)

    @NetworkVar('SocksAsModel',     rBool, wBool,              false)
    @NetworkVar('SocksTexture',     rUInt(8, PPM2.MIN_SOCKS, PPM2.MAX_SOCKS), wUInt(0, 8), 0)
    @NetworkVar('SocksColor',       rColor, wColor,            Color(255, 255, 255))
    @NetworkVar('SocksTextureURL',  rString, wString,          '')

    for i = 1, 6
        @NetworkVar('SocksDetailColor' .. i, rColor, wColor, Color(255, 255, 255))

    @NetworkVar('BatPonyEars',      rBool, wBool,              false)
    @NetworkVar('Fangs',            rBool, wBool,              false)
    @NetworkVar('ClawTeeth',        rBool, wBool,              false)

    @NetworkVar('TeethColor',       rColor, wColor,    Color(255, 255, 255))
    @NetworkVar('MouthColor',       rColor, wColor,    Color(219, 65, 155))
    @NetworkVar('TongueColor',      rColor, wColor,    Color(235, 131, 59))

    for {:flex, :active} in *PPM2.PonyFlexController.FLEX_LIST
        @NetworkVar("DisableFlex#{flex}", rBool, wBool, false) if active

    for i = 1, PPM2.MAX_BODY_DETAILS
        @NetworkVar("BodyDetail#{i}",       rUInt(8, PPM2.MIN_DETAIL, PPM2.MAX_DETAIL), wUInt(0, 8), 0)
        @NetworkVar("BodyDetailColor#{i}",  rColor,  wColor, Color(255, 255, 255))
        @NetworkVar("BodyDetailURL#{i}",    rString, wString, '')
        @NetworkVar("BodyDetailURLColor#{i}",  rColor,  wColor, Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("HornURL#{i}",       rString, wString, '')
        @NetworkVar("WingsURL#{i}",      rString, wString, '')
        @NetworkVar("HornURLColor#{i}",  rColor,  wColor, Color(255, 255, 255))
        @NetworkVar("WingsURLColor#{i}", rColor,  wColor, Color(255, 255, 255))

    @NetworkVar('Fly',                  rBool,   wBool,                 false)
    @NetworkVar('DisableTask',          rBool,   wBool,                 false)
    @NetworkVar('UseFlexLerp',          rBool,   wBool,                  true)
    @NetworkVar('FlexLerpMultiplier',   net.ReadFloat,  wFloat,                    1)
    @NetworkVar('NewMuzzle',            rBool,   wBool,                  true)
    @NetworkVar('SeparateWings',        rBool,   wBool,                 false)
    @NetworkVar('SeparateHorn',         rBool,   wBool,                 false)
    @NetworkVar('WingsColor',           rColor,  wColor,  Color(255, 255, 255))
    @NetworkVar('HornColor',            rColor,  wColor,  Color(255, 255, 255))

    @NetworkVar('WingsType',            rUInt(8, PPM2.MIN_WINGS, PPM2.MAX_WINGS), wUInt(0, 8), 0)
    @NetworkVar('MaleBuff',             rFloat(PPM2.MIN_MALE_BUFF, PPM2.MAX_MALE_BUFF), wFloat, PPM2.DEFAULT_MALE_BUFF)

    @NetworkVar('BatWingColor',         rColor, wColor,    Color(255, 255, 255))
    @NetworkVar('BatWingSkinColor',     rColor, wColor,    Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("BatWingURL#{i}",           rString, wString, '')
        @NetworkVar("BatWingSkinURL#{i}",       rString, wString, '')
        @NetworkVar("BatWingURLColor#{i}",      rColor,  wColor, Color(255, 255, 255))
        @NetworkVar("BatWingSkinURLColor#{i}",  rColor,  wColor, Color(255, 255, 255))

    Clone: (target = @ent) =>
        copy = @@(nil, target)
        @ApplyDataToObject(copy)
        return copy

    new: (netID, ent) =>
        @recomputeTextures = true
        @isValid = true
        if ent
            @modelCached = ent\GetModel()
            @SetEntity(ent)
            @SetupEntity(ent)
        super(netID)
    IsValid: => @isValid
    GetModel: => @modelCached
    EntIndex: => @entID
    SetupEntity: (ent) =>
        if ent.__PPM2_PonyData
            return if ent.__PPM2_PonyData\GetOwner() and IsValid(ent.__PPM2_PonyData\GetOwner()) and StrongEntity(ent.__PPM2_PonyData\GetOwner()) ~= StrongEntity(@GetOwner())
            ent.__PPM2_PonyData\Remove() if ent.__PPM2_PonyData.Remove and ent.__PPM2_PonyData ~= @
        ent.__PPM2_PonyData = @
        @ent = ent
        return unless IsValid(ent)
        @modelCached = ent\GetModel()
        @ent = ent
        @flightController = PPM2.PonyflyController(@)
        @entID = ent\EntIndex()
        @ModelChanges(@modelCached, @modelCached)
        @Reset()
        if CLIENT
            timer.Simple 0, ->
                @GetRenderController()\CompileTextures() if @GetRenderController()
        PPM2.DebugPrint('Ponydata ', @, ' was updated to use for ', @ent)
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    ModelChanges: (old = @ent\GetModel(), new = old) =>
        @modelCached = new
        @SetFly(false) if SERVER
        timer.Simple 0.5, ->
            return unless IsValid(@ent)
            @Reset()
    GenericDataChange: (state) =>
        if state\GetKey() == 'Entity' and IsValid(@GetEntity())
            @SetupEntity(@GetEntity())

        if state\GetKey() == 'Fly' and @flightController
            @flightController\Switch(state\GetValue())

        if state\GetKey() == 'DisableTask'
            @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

        @GetSizeController()\DataChanges(state) if @ent and @GetBodygroupController()
        @GetBodygroupController()\DataChanges(state) if @ent and @GetBodygroupController()

        if CLIENT and @ent
            @GetWeightController()\DataChanges(state) if @GetWeightController()
            @GetRenderController()\DataChanges(state) if @GetRenderController()

    ResetScale: =>
        if scale = @GetSizeController()
            scale\ResetScale()

    ModifyScale: =>
        if scale = @GetSizeController()
            scale\ModifyScale()

    Reset: =>
        if scale = @GetSizeController()
            scale\Reset()
        if CLIENT
            @GetWeightController()\Reset() if @GetWeightController().Reset
            @GetRenderController()\Reset() if @GetRenderController().Reset
            @GetBodygroupController()\Reset() if @GetBodygroupController().Reset
    PlayerRespawn: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledRespawn
            @alreadyCalledRespawn = true
            @alreadyCalledDeath = true
        else
            @alreadyCalledRespawn = false
            @alreadyCalledDeath = false
        @ApplyBodygroups(CLIENT)
        @SetFly(false) if SERVER

        if scale = @GetSizeController()
            scale\PlayerRespawn()

        if CLIENT
            @deathRagdollMerged = false
            @GetWeightController()\UpdateWeight() if @GetWeightController()
            @GetRenderController()\PlayerRespawn() if @GetRenderController()
            @GetBodygroupController()\MergeModels(@ent) if IsValid(@ent) and @GetBodygroupController().MergeModels

    PlayerDeath: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledDeath
            @alreadyCalledDeath = true
        else
            @alreadyCalledDeath = false
        @SetFly(false) if SERVER

        if scale = @GetSizeController()
            scale\PlayerDeath()

        if CLIENT
            @DoRagdollMerge()
            @GetRenderController()\PlayerDeath() if @GetRenderController()

    DoRagdollMerge: =>
        return if @deathRagdollMerged
        bgController = @GetBodygroupController()
        rag = @ent\GetRagdollEntity()
        if not bgController.MergeModels
            @deathRagdollMerged = true
        elseif IsValid(rag)
            @deathRagdollMerged = true
            bgController\MergeModels(rag)

    ApplyBodygroups: (updateModels = CLIENT) => @GetBodygroupController()\ApplyBodygroups(updateModels) if @ent
    SetLocalChange: (state) => @GenericDataChange(state)
    NetworkDataChanges: (state) => @GenericDataChange(state)
    SlowUpdate: =>
        @GetBodygroupController()\SlowUpdate() if @GetBodygroupController()
        if scale = @GetSizeController()
            scale\SlowUpdate()

    GetFlightController: => @flightController
    GetRenderController: =>
        return if SERVER
        return @renderController if not @isValid
        if not @renderController or @modelCached ~= @modelRender
            @modelRender = @modelCached
            cls = PPM2.GetRenderController(@modelCached)
            if @renderController and cls == @renderController.__class
                @renderController.ent = @ent
                PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
                return @renderController
            @renderController\Remove() if @renderController
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    GetWeightController: =>
        return if SERVER
        return @weightController if not @isValid
        if not @weightController or @modelCached ~= @modelWeight
            @modelCached = @modelCached or @ent\GetModel()
            @modelWeight = @modelCached
            cls = PPM2.GetPonyWeightController(@modelCached)
            if @weightController and cls == @weightController.__class
                @weightController.ent = @ent
                PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
                return @weightController
            @weightController\Remove() if @weightController
            @weightController = cls(@)
        @weightController.ent = @ent
        return @weightController
    GetSizeController: =>
        return @scaleController if not @isValid
        if not @scaleController or @modelCached ~= @modelScale
            @modelCached = @modelCached or @ent\GetModel()
            @modelScale = @modelCached
            cls = PPM2.GetSizeController(@modelCached)
            if @scaleController and cls == @scaleController.__class
                @scaleController.ent = @ent
                PPM2.DebugPrint('Skipping size controller recreation for ', @ent, ' as part of ', @)
                return @scaleController
            @scaleController\Remove() if @scaleController
            @scaleController = cls(@)
        @scaleController.ent = @ent
        return @scaleController
    GetScaleController: => @GetSizeController()
    GetBodygroupController: =>
        return @bodygroups if not @isValid
        if not @bodygroups or @modelBodygroups ~= @modelCached
            @modelCached = @modelCached or @ent\GetModel()
            @modelBodygroups = @modelCached
            cls = PPM2.GetBodygroupController(@modelCached)
            if @bodygroups and cls == @bodygroups.__class
                @bodygroups.ent = @ent
                PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
                return @bodygroups
            @bodygroups\Remove() if @bodygroups
            @bodygroups = cls(@)
        @bodygroups.ent = @ent
        return @bodygroups
    Remove: (byClient = false) =>
        @@NW_Objects[@netID] = nil if @NETWORKED
        @isValid = false
        @ent = @GetEntity() if not IsValid(@ent)
        @ent.__PPM2_PonyData = nil if IsValid(@ent) and @ent.__PPM2_PonyData == @
        if CLIENT
            @GetWeightController()\Remove() if @GetWeightController()
            @GetRenderController()\Remove() if @GetRenderController()
            if IsValid(@ent) and @ent.__ppm2_task_hit
                @ent.__ppm2_task_hit = false
                @ent\SetNoDraw(false)
        @GetBodygroupController()\Remove() if @GetBodygroupController()
        @GetSizeController()\Remove() if @GetSizeController()
        @flightController\Switch(false) if @flightController
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    __tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

PPM2.NetworkedPonyData = NetworkedPonyData

if CLIENT
    net.Receive 'PPM2.NotifyDisconnect', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

    net.Receive 'PPM2.PonyDataRemove', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

	hook.Add 'StrongEntityLinkUpdates', 'PPM2.NetworkedObjectCheck', =>
		if @__PPM2_PonyData
            @__PPM2_PonyData\SetEntity(@GetEntity())
            @__PPM2_PonyData\SetupEntity(@)
else
    hook.Add 'PlayerJoinTeam', 'PPM2.TeamWaypoint', (ply, new) ->
        ply.__ppm2_modified_jump = false
    hook.Add 'OnPlayerChangedTeam', 'PPM2.TeamWaypoint', (ply, old, new) ->
        ply.__ppm2_modified_jump = false

entMeta = FindMetaTable('Entity')
entMeta.GetPonyData = =>
    if @__PPM2_PonyData and StrongEntity(@__PPM2_PonyData\GetEntity()) ~= StrongEntity(@)
        @__PPM2_PonyData\SetEntity(@)
        @__PPM2_PonyData\SetupEntity(@) if CLIENT
    return @__PPM2_PonyData

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

for ply in *player.GetAll()
    ply.__PPM2_PonyData\Remove() if ply.__PPM2_PonyData

class NetworkedPonyData extends PPM2.NetworkedObject
    @NW_ClientsideCreation = true
    @RenderTasks = {}

    @Setup()
    @NetworkVar('Entity',           net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), ((newValue) => IsValid(@GetOwner()) and StrongEntity(@GetOwner()\EntIndex()) or newValue))
    @NetworkVar('UpperManeModel',   net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('LowerManeModel',   net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('TailModel',        net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('SocksModel',       net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)

    @NetworkVar('Race',             (-> math.Clamp(net.ReadUInt(4), 0, 3)), ((arg = PPM2.RACE_EARTH) -> net.WriteUInt(arg, 4)), PPM2.RACE_EARTH)
    @NetworkVar('Gender',           (-> math.Clamp(net.ReadUInt(4), 0, 1)), ((arg = PPM2.GENDER_FEMALE) -> net.WriteUInt(arg, 4)), PPM2.GENDER_FEMALE)
    @NetworkVar('Weight',           (-> math.Clamp(net.ReadFloat(), PPM2.MIN_WEIGHT, PPM2.MAX_WEIGHT)), net.WriteFloat, 1)
    @NetworkVar('PonySize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_SCALE, PPM2.MAX_SCALE)),   net.WriteFloat, 1)
    @NetworkVar('NeckSize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_NECK, PPM2.MAX_NECK)),     net.WriteFloat, 1)
    @NetworkVar('LegsSize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_LEGS, PPM2.MAX_LEGS)),     net.WriteFloat, 1)

    -- Reserved - they can be accessed/used/changed, but they do not do anything
    @NetworkVar('Age',              (-> math.Clamp(net.ReadUInt(4), 0, 2)), ((arg = PPM2.AGE_ADULT) -> net.WriteUInt(arg, 4)), PPM2.AGE_ADULT)

    @NetworkVar('EyelashType',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_EYELASHES, PPM2.MAX_EYELASHES)),           ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('TailType',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_TAILS, PPM2.MAX_TAILS)),                   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeType',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_UPPER_MANES, PPM2.MAX_UPPER_MANES)),       ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeLower',    (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_LOWER_MANES, PPM2.MAX_LOWER_MANES)),       ((arg = 0) -> net.WriteUInt(arg, 8)), 0)

    @NetworkVar('TailTypeNew',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_TAILS_NEW, PPM2.MAX_TAILS_NEW)),               ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeNew',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_UPPER_MANES_NEW, PPM2.MAX_UPPER_MANES_NEW)),   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeLowerNew', (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_LOWER_MANES_NEW, PPM2.MAX_LOWER_MANES_NEW)),   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)

    @NetworkVar('BodyColor',        net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))

    @NetworkVar('SeparateEyes',     net.ReadBool, net.WriteBool,              false)
    for publicName in *{'', 'Left', 'Right'}
        @NetworkVar("EyeType#{publicName}",          (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_EYE_TYPE, PPM2.MAX_EYE_TYPE)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
        @NetworkVar("EyeBackground#{publicName}",    net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeHole#{publicName}",          net.ReadColor, net.WriteColor, 	    Color(0,   0,   0  ))
        @NetworkVar("EyeIrisTop#{publicName}",       net.ReadColor, net.WriteColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisBottom#{publicName}",    net.ReadColor, net.WriteColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisLine1#{publicName}",     net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeIrisLine2#{publicName}",     net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeReflection#{publicName}",    net.ReadColor, net.WriteColor,    Color(255, 255, 255, 127))
        @NetworkVar("EyeEffect#{publicName}",        net.ReadColor, net.WriteColor,         Color(255, 255, 255))
        @NetworkVar("EyeLines#{publicName}",         net.ReadBool, net.WriteBool,                           true)
        @NetworkVar("DerpEyes#{publicName}",         net.ReadBool, net.WriteBool,                          false)
        @NetworkVar("DerpEyesStrength#{publicName}", (-> math.Clamp(net.ReadFloat(), PPM2.MIN_DERP_STRENGTH, PPM2.MAX_DERP_STRENGTH)), net.WriteFloat, 1)
        @NetworkVar("HoleWidth#{publicName}",        (-> math.Clamp(net.ReadFloat(), PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE)), net.WriteFloat,  1)
        @NetworkVar("HoleHeight#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE)), net.WriteFloat,  1)
        @NetworkVar("HoleSize#{publicName}",         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE, PPM2.MAX_HOLE)),             net.WriteFloat, .8)
        @NetworkVar("HoleShiftX#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT)), net.WriteFloat, 0)
        @NetworkVar("HoleShiftY#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT)), net.WriteFloat, 0)
        @NetworkVar("IrisSize#{publicName}",         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, .8)
        @NetworkVar("IrisWidth#{publicName}",        (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, 1)
        @NetworkVar("IrisHeight#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, 1)
        @NetworkVar("EyeRotation#{publicName}",      (-> math.Clamp(net.ReadInt(12), PPM2.MIN_EYE_ROTATION, PPM2.MAX_EYE_ROTATION)), ((arg = 0) -> net.WriteInt(arg, 12)), 1)
        @NetworkVar("EyeURL#{publicName}",           net.ReadString,                                                            net.WriteString, '')

    @NetworkVar('SeparateMane',     net.ReadBool, net.WriteBool,              false)
    for i = 1, 6
        @NetworkVar("ManeColor#{i}",            net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeDetailColor#{i}",      net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeURLColor#{i}",         net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeURL#{i}",              net.ReadString, net.WriteString,   '')

        @NetworkVar("TailColor#{i}",            net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailDetailColor#{i}",      net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailURLColor#{i}",         net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailURL#{i}",              net.ReadString, net.WriteString,   '')

        @NetworkVar("LowerManeColor#{i}",       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeURL#{i}",         net.ReadString, net.WriteString,   '')
        @NetworkVar("LowerManeURLColor#{i}",    net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeDetailColor#{i}", net.ReadColor, net.WriteColor,     Color(255, 255, 255))

        @NetworkVar("UpperManeColor#{i}",       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURL#{i}",         net.ReadString, net.WriteString,   '')
        @NetworkVar("UpperManeDetailColor#{i}", net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURLColor#{i}",    net.ReadColor, net.WriteColor,     Color(255, 255, 255))

    @NetworkVar('CMark',            net.ReadBool, net.WriteBool,              true)
    @NetworkVar('CMarkSize',        (-> math.Clamp(net.ReadFloat(), 0, 1)), net.WriteFloat, 1)
    @NetworkVar('CMarkColor',       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
    @NetworkVar('CMarkURL',         net.ReadString, net.WriteString,            '')
    @NetworkVar('CMarkType',        (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_CMARK, PPM2.MAX_CMARK)),           ((arg = 4) -> net.WriteUInt(arg, 8)), 4)
    @NetworkVar('TailSize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_TAIL_SIZE, PPM2.MAX_TAIL_SIZE)),   net.WriteFloat, 1)

    @NetworkVar('Bodysuit',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_SUIT, PPM2.MAX_SUIT)),             ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('Socks',            net.ReadBool, net.WriteBool,              false)
    @NetworkVar('NoFlex',           net.ReadBool, net.WriteBool,              false)

    @NetworkVar('SocksAsModel',     net.ReadBool, net.WriteBool,              false)
    @NetworkVar('SocksTexture',     (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_SOCKS, PPM2.MAX_SOCKS)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('SocksColor',       net.ReadColor, net.WriteColor,            Color(255, 255, 255))
    @NetworkVar('SocksTextureURL',  net.ReadString, net.WriteString,          '')

    for i = 1, 6
        @NetworkVar('SocksDetailColor' .. i, net.ReadColor, net.WriteColor, Color(255, 255, 255))

    @NetworkVar('BatPonyEars',      net.ReadBool, net.WriteBool,              false)
    @NetworkVar('Fangs',            net.ReadBool, net.WriteBool,              false)
    @NetworkVar('ClawTeeth',        net.ReadBool, net.WriteBool,              false)

    @NetworkVar('TeethColor',       net.ReadColor, net.WriteColor,    Color(255, 255, 255))
    @NetworkVar('MouthColor',       net.ReadColor, net.WriteColor,    Color(219, 65, 155))
    @NetworkVar('TongueColor',      net.ReadColor, net.WriteColor,    Color(235, 131, 59))

    for {:flex, :active} in *PPM2.PonyFlexController.FLEX_LIST
        @NetworkVar("DisableFlex#{flex}", net.ReadBool, net.WriteBool, false) if active

    for i = 1, PPM2.MAX_BODY_DETAILS
        @NetworkVar("BodyDetail#{i}",       (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_DETAIL, PPM2.MAX_DETAIL)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
        @NetworkVar("BodyDetailColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("BodyDetailURL#{i}",    net.ReadString, net.WriteString, '')
        @NetworkVar("BodyDetailURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("HornURL#{i}",       net.ReadString, net.WriteString, '')
        @NetworkVar("WingsURL#{i}",      net.ReadString, net.WriteString, '')
        @NetworkVar("HornURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("WingsURLColor#{i}", net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    @NetworkVar('Fly',                  net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('DisableTask',          net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('UseFlexLerp',          net.ReadBool,   net.WriteBool,                  true)
    @NetworkVar('FlexLerpMultiplier',   net.ReadFloat,  net.WriteFloat,                    1)
    @NetworkVar('NewMuzzle',            net.ReadBool,   net.WriteBool,                  true)
    @NetworkVar('SeparateWings',        net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('SeparateHorn',         net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('WingsColor',           net.ReadColor,  net.WriteColor,  Color(255, 255, 255))
    @NetworkVar('HornColor',            net.ReadColor,  net.WriteColor,  Color(255, 255, 255))

    @NetworkVar('WingsType',            (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_WINGS, PPM2.MAX_WINGS)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('MaleBuff',             (-> math.Clamp(net.ReadFloat(), PPM2.MIN_MALE_BUFF, PPM2.MAX_MALE_BUFF)), net.WriteFloat, PPM2.DEFAULT_MALE_BUFF)

    @NetworkVar('BatWingColor',         net.ReadColor, net.WriteColor,    Color(255, 255, 255))
    @NetworkVar('BatWingSkinColor',     net.ReadColor, net.WriteColor,    Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("BatWingURL#{i}",           net.ReadString, net.WriteString, '')
        @NetworkVar("BatWingSkinURL#{i}",       net.ReadString, net.WriteString, '')
        @NetworkVar("BatWingURLColor#{i}",      net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("BatWingSkinURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    Clone: (target = @ent) =>
        copy = @@(nil, target)
        @ApplyDataToObject(copy)
        return copy

    new: (netID, ent) =>
        @recomputeTextures = true
        @isValid = true
        if ent
            @modelCached = ent\GetModel()
            @SetEntity(ent)
            @SetupEntity(ent)
        super(netID)
    IsValid: => @isValid
    GetModel: => @modelCached
    EntIndex: => @entID
    SetupEntity: (ent) =>
        if ent.__PPM2_PonyData
            return if ent.__PPM2_PonyData\GetOwner() and IsValid(ent.__PPM2_PonyData\GetOwner()) and StrongEntity(ent.__PPM2_PonyData\GetOwner()) ~= StrongEntity(@GetOwner())
            ent.__PPM2_PonyData\Remove() if ent.__PPM2_PonyData.Remove and ent.__PPM2_PonyData ~= @
        ent.__PPM2_PonyData = @
        @ent = ent
        return unless IsValid(ent)
        @modelCached = ent\GetModel()
        @ent = ent
        @flightController = PPM2.PonyflyController(@)
        @entID = ent\EntIndex()
        @ModelChanges(@modelCached, @modelCached)
        @Reset()
        if CLIENT
            timer.Simple 0, ->
                @GetRenderController()\CompileTextures() if @GetRenderController()
        PPM2.DebugPrint('Ponydata ', @, ' was updated to use for ', @ent)
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    ModelChanges: (old = @ent\GetModel(), new = old) =>
        @modelCached = new
        @SetFly(false) if SERVER
        timer.Simple 0.5, ->
            return unless IsValid(@ent)
            @Reset()
    GenericDataChange: (state) =>
        if state\GetKey() == 'Entity' and IsValid(@GetEntity())
            @SetupEntity(@GetEntity())

        if state\GetKey() == 'Fly' and @flightController
            @flightController\Switch(state\GetValue())

        if state\GetKey() == 'DisableTask'
            @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

        @GetSizeController()\DataChanges(state) if @ent and @GetBodygroupController()
        @GetBodygroupController()\DataChanges(state) if @ent and @GetBodygroupController()

        if CLIENT and @ent
            @GetWeightController()\DataChanges(state) if @GetWeightController()
            @GetRenderController()\DataChanges(state) if @GetRenderController()

    ResetScale: =>
        if scale = @GetSizeController()
            scale\ResetScale()

    ModifyScale: =>
        if scale = @GetSizeController()
            scale\ModifyScale()

    Reset: =>
        if scale = @GetSizeController()
            scale\Reset()
        if CLIENT
            @GetWeightController()\Reset() if @GetWeightController().Reset
            @GetRenderController()\Reset() if @GetRenderController().Reset
            @GetBodygroupController()\Reset() if @GetBodygroupController().Reset
    PlayerRespawn: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledRespawn
            @alreadyCalledRespawn = true
            @alreadyCalledDeath = true
        else
            @alreadyCalledRespawn = false
            @alreadyCalledDeath = false
        @ApplyBodygroups(CLIENT)
        @SetFly(false) if SERVER

        if scale = @GetSizeController()
            scale\PlayerRespawn()

        if CLIENT
            @deathRagdollMerged = false
            @GetWeightController()\UpdateWeight() if @GetWeightController()
            @GetRenderController()\PlayerRespawn() if @GetRenderController()
            @GetBodygroupController()\MergeModels(@ent) if IsValid(@ent) and @GetBodygroupController().MergeModels

    PlayerDeath: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledDeath
            @alreadyCalledDeath = true
        else
            @alreadyCalledDeath = false
        @SetFly(false) if SERVER

        if scale = @GetSizeController()
            scale\PlayerDeath()

        if CLIENT
            @DoRagdollMerge()
            @GetRenderController()\PlayerDeath() if @GetRenderController()

    DoRagdollMerge: =>
        return if @deathRagdollMerged
        bgController = @GetBodygroupController()
        rag = @ent\GetRagdollEntity()
        if not bgController.MergeModels
            @deathRagdollMerged = true
        elseif IsValid(rag)
            @deathRagdollMerged = true
            bgController\MergeModels(rag)

    ApplyBodygroups: (updateModels = CLIENT) => @GetBodygroupController()\ApplyBodygroups(updateModels) if @ent
    SetLocalChange: (state) => @GenericDataChange(state)
    NetworkDataChanges: (state) => @GenericDataChange(state)
    SlowUpdate: =>
        @GetBodygroupController()\SlowUpdate() if @GetBodygroupController()
        if scale = @GetSizeController()
            scale\SlowUpdate()

    GetFlightController: => @flightController
    GetRenderController: =>
        return if SERVER
        return @renderController if not @isValid
        if not @renderController or @modelCached ~= @modelRender
            @modelRender = @modelCached
            cls = PPM2.GetRenderController(@modelCached)
            if @renderController and cls == @renderController.__class
                @renderController.ent = @ent
                PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
                return @renderController
            @renderController\Remove() if @renderController
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    GetWeightController: =>
        return if SERVER
        return @weightController if not @isValid
        if not @weightController or @modelCached ~= @modelWeight
            @modelCached = @modelCached or @ent\GetModel()
            @modelWeight = @modelCached
            cls = PPM2.GetPonyWeightController(@modelCached)
            if @weightController and cls == @weightController.__class
                @weightController.ent = @ent
                PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
                return @weightController
            @weightController\Remove() if @weightController
            @weightController = cls(@)
        @weightController.ent = @ent
        return @weightController
    GetSizeController: =>
        return @scaleController if not @isValid
        if not @scaleController or @modelCached ~= @modelScale
            @modelCached = @modelCached or @ent\GetModel()
            @modelScale = @modelCached
            cls = PPM2.GetSizeController(@modelCached)
            if @scaleController and cls == @scaleController.__class
                @scaleController.ent = @ent
                PPM2.DebugPrint('Skipping size controller recreation for ', @ent, ' as part of ', @)
                return @scaleController
            @scaleController\Remove() if @scaleController
            @scaleController = cls(@)
        @scaleController.ent = @ent
        return @scaleController
    GetScaleController: => @GetSizeController()
    GetBodygroupController: =>
        return @bodygroups if not @isValid
        if not @bodygroups or @modelBodygroups ~= @modelCached
            @modelCached = @modelCached or @ent\GetModel()
            @modelBodygroups = @modelCached
            cls = PPM2.GetBodygroupController(@modelCached)
            if @bodygroups and cls == @bodygroups.__class
                @bodygroups.ent = @ent
                PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
                return @bodygroups
            @bodygroups\Remove() if @bodygroups
            @bodygroups = cls(@)
        @bodygroups.ent = @ent
        return @bodygroups
    Remove: (byClient = false) =>
        @@NW_Objects[@netID] = nil if @NETWORKED
        @isValid = false
        @ent = @GetEntity() if not IsValid(@ent)
        @ent.__PPM2_PonyData = nil if IsValid(@ent) and @ent.__PPM2_PonyData == @
        if CLIENT
            @GetWeightController()\Remove() if @GetWeightController()
            @GetRenderController()\Remove() if @GetRenderController()
            if IsValid(@ent) and @ent.__ppm2_task_hit
                @ent.__ppm2_task_hit = false
                @ent\SetNoDraw(false)
        @GetBodygroupController()\Remove() if @GetBodygroupController()
        @GetSizeController()\Remove() if @GetSizeController()
        @flightController\Switch(false) if @flightController
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    __tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

PPM2.NetworkedPonyData = NetworkedPonyData

if CLIENT
    net.Receive 'PPM2.NotifyDisconnect', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

    net.Receive 'PPM2.PonyDataRemove', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

	hook.Add 'StrongEntityLinkUpdates', 'PPM2.NetworkedObjectCheck', =>
		if @__PPM2_PonyData
            @__PPM2_PonyData\SetEntity(@GetEntity())
            @__PPM2_PonyData\SetupEntity(@)
else
    hook.Add 'PlayerJoinTeam', 'PPM2.TeamWaypoint', (ply, new) ->
        ply.__ppm2_modified_jump = false
    hook.Add 'OnPlayerChangedTeam', 'PPM2.TeamWaypoint', (ply, old, new) ->
        ply.__ppm2_modified_jump = false

entMeta = FindMetaTable('Entity')
entMeta.GetPonyData = =>
    if @__PPM2_PonyData and StrongEntity(@__PPM2_PonyData\GetEntity()) ~= StrongEntity(@)
        @__PPM2_PonyData\SetEntity(@)
        @__PPM2_PonyData\SetupEntity(@) if CLIENT
    return @__PPM2_PonyData


prep-quizs: bgcolor only two last columns (score)

import Widget from require "lapis.html"

model = require('model')
msk_time = require "msk_time"

class PrepQuizs extends Widget
  content: =>
    local quizs
    if @today_only
      quizs = model.today_quizs(model.FINISHED)
    else
      quizs = model.all_quizs(model.FINISHED)
    element 'table', border: 1, ->
      element 'tr', ->
        element 'td', -> text @_("quiz")
        element 'td', -> text @_("user")
        element 'td', -> text @_("started")
        element 'td', -> text @_("finished")
        element 'td', -> text @_("right answers")
        element 'td', -> text @_("tasks")
      for quiz in *quizs
        color = 'red'
        if quiz.right_answers == quiz.tasks
          color = 'green'
        element 'tr', ->
          element 'td', ->
            a href: @url_for("prep-quiz", {id: quiz.id}), ->
              text quiz.name
          element 'td', -> text quiz.user
          element 'td', -> text msk_time(quiz.created_at)
          element 'td', -> text msk_time(quiz.updated_at)
          element 'td', bgcolor: color, ->
            text quiz.right_answers
          element 'td', bgcolor: color, ->
            text quiz.tasks

import Widget from require "lapis.html"

model = require('model')
msk_time = require "msk_time"

class PrepQuizs extends Widget
  content: =>
    local quizs
    if @today_only
      quizs = model.today_quizs(model.FINISHED)
    else
      quizs = model.all_quizs(model.FINISHED)
    element 'table', border: 1, ->
      element 'tr', ->
        element 'td', -> text @_("quiz")
        element 'td', -> text @_("user")
        element 'td', -> text @_("started")
        element 'td', -> text @_("finished")
        element 'td', -> text @_("right answers")
        element 'td', -> text @_("tasks")
      for quiz in *quizs
        color = 'red'
        if quiz.right_answers == quiz.tasks
          color = 'green'
        element 'tr', bgcolor: color, ->
          element 'td', ->
            a href: @url_for("prep-quiz", {id: quiz.id}), ->
              text quiz.name
          element 'td', -> text quiz.user
          element 'td', -> text msk_time(quiz.created_at)
          element 'td', -> text msk_time(quiz.updated_at)
          element 'td', -> text quiz.right_answers
          element 'td', -> text quiz.tasks


Priority

Priority

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

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flasglight_pass', '0', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')

class PonyRenderController
    @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(@)

    CompileTextures: => @GetTextureController()\CompileTextures()
    new: (data) =>
        @isValid = true
        @networkedData = data
        @ent = data.ent
        @modelCached = data\GetModel()
        @IGNORE_DRAW = false
        @CompileTextures() if @ent
        @CreateLegs() if @ent == LocalPlayer()
        @socksModel = data\GetSocksModel()
        @CreateFlexController() if @ent
    GetEntity: => @ent
    GetData: => @networkedData
    GetModel: => @networkedData\GetModel()

    GetLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        @CreateLegs() if not IsValid()
        return @legsModel
    CreateLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        for ent in *ents.GetAll()
            if ent.isPonyLegsModel
                ent\Remove()
        @legsModel = ClientsideModel(@modelCached)
        with @legsModel
            .isPonyLegsModel = true
            \SetNoDraw(true)
            .__PPM2_PonyData = @GetData()

        @GetData()\GetWeightController()\UpdateWeight(@legsModel)

        @lastLegUpdate = CurTime()
        @legClipPlanePos = Vector(0, 0, 0)
        @legBGSetup = CurTime()
        @legUpdateFrame = 0
        @legClipDot = 0
        @duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
        @legsClipPlane = @@LEG_CLIP_VECTOR
        return @legsModel

    @LEG_SHIFT_CONST = 24
    @LEG_SHIFT_CONST_VEHICLE = 14
    @LEG_Z_CONST = 0
    @LEG_Z_CONST_VEHICLE = 20
    @LEG_ANIM_SPEED_CONST = 1
    @LEG_CLIP_OFFSET_STAND = 28
    @LEG_CLIP_OFFSET_DUCK = 12
    @LEG_CLIP_OFFSET_VEHICLE = 11

    UpdateLegs: =>
        return if not @isValid
        return unless IsValid(@legsModel)
        return if @legUpdateFrame == FrameNumber()
        @legUpdateFrame = FrameNumber()
        ctime = CurTime()
        ply = @ent
        seq = ply\GetSequence()

        if seq ~= @legSeq
            @legSeq = seq
            @legsModel\ResetSequence(seq)

        if @legBGSetup < ctime
            @legBGSetup = ctime + 1
            for group in *ply\GetBodyGroups()
                @legsModel\SetBodygroup(group.id, ply\GetBodygroup(group.id))

        with @legsModel
            \FrameAdvance(ctime - @lastLegUpdate)
            \SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
            @lastLegUpdate = ctime
            \SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
            \SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
            \SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
            \SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
            \SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

        if ply\InVehicle()
            veh = ply\GetVehicle()
            vehAng = veh\GetAngles()
            eyepos = EyePos()
            vehAng\RotateAroundAxis(vehAng\Up(), 90)

            clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
            clipAng\RotateAroundAxis(clipAng\Right(), -90)

            @legsClipPlane = clipAng\Forward()
            @legsModel\SetRenderAngles(vehAng)

            legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
            legClipPlanePos\Rotate(vehAng)
            @legClipPlanePos = eyepos - legClipPlanePos

            drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
            drawPos\Rotate(vehAng)
            @legsModel\SetRenderOrigin(eyepos - drawPos)
        else
            @legsClipPlane = @@LEG_CLIP_VECTOR
            eangles = EyeAngles()
            yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
            newAng = Angle(0, yaw, 0)
            rad = math.rad(yaw)
            sin, cos = math.sin(rad), math.cos(rad)
            pos = ply\GetPos()
            {:x, :y, :z} = pos
            newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
            if ply\Crouching()
                @duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
            else
                @duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

            @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
            @legsModel\SetRenderAngles(newAng)
            @legsModel\SetRenderOrigin(newPos)
        @legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

    @LEG_CLIP_VECTOR = Vector(0, 0, -1)
    DrawLegs: (start3D = true) =>
        return if not @isValid
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        if ENABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @PreDraw()
            @legsModel\DrawModel()
            @PostDraw()
            render.PopFlashlightMode()
        @PreDraw()
        @legsModel\DrawModel()
        @PostDraw()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    IsValid: => IsValid(@ent) and @isValid
    Remove: =>
        @flexes\Remove() if @flexes
        @GetTextureController()\Remove()
        @isValid = false

    PlayerRespawn: =>
        return if not @isValid
        @flexes\PlayerRespawn() if @flexes

    PreDraw: (ent = @ent) =>
        return if not @isValid
        return false if @IGNORE_DRAW
        @GetTextureController()\PreDraw(ent)
        @flexes\Think() if @flexes
    PostDraw: (ent = @ent) =>
        return if not @isValid
        return if @IGNORE_DRAW
        textures = @GetTextureController()
        textures\PostDraw(ent)

        @IGNORE_DRAW = true
        if @GetData()\IsNetworked()
            @socksModel = @ent\GetNWEntity('PPM2.SocksModel') if not IsValid(@socksModel)

        @socksModel = @GetData()\GetSocksModel() if not IsValid(@socksModel)

        if IsValid(@socksModel)
            @socksModel\SetNoDraw(true)
            if ENABLE_FLASHLIGHT_PASS\GetBool()
                render.PushFlashlightMode(true)
                textures\PreDrawSocks()
                @socksModel\DrawModel()
                textures\PostDrawSocks()
                render.PopFlashlightMode()

            textures\PreDrawSocks()
            @socksModel\DrawModel()
            textures\PostDrawSocks()
        @IGNORE_DRAW = false

    @ARMS_MATERIAL_INDEX = 0
    PreDrawArms: (ent) =>
        return if not @isValid
        if ent and not @armsWeightSetup
            @armsWeightSetup = true
            weight = @GetData()\GetWeight()
            vec = Vector(weight, weight, weight)
            for i = 1, 13
                ent\ManipulateBoneScale(i, vec)
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
    PostDrawArms: (ent) =>
        return if not @isValid
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

    DataChanges: (state) =>
        return if not @isValid
        return if not @ent
        @GetTextureController()\DataChanges(state)
        @flexes\DataChanges(state) if @flexes
        switch state\GetKey()
            when 'Weight'
                @armsWeightSetup = false
                @GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
            when 'SocksModel'
                @socksModel = @GetData()\GetSocksModel()
                @socksModel\SetNoDraw(true) if IsValid(@socksModel)
            when 'NoFlex'
                if state\GetValue()
                    @flexes\ResetSequences() if @flexes
                    @flexes = nil
                else
                    @CreateFlexController()
    GetTextureController: =>
        return if not @isValid
        if not @renderController
            cls = PPM2.GetTextureController(@modelCached)
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    CreateFlexController: =>
        return if not @isValid
        return if not @ent\IsPlayer()
        return if @GetData()\GetNoFlex()
        if not @flexes
            cls = PPM2.GetFlexController(@modelCached)
            return if not cls
            @flexes = cls(@)
        @flexes.ent = @ent
        return @flexes
    GetFlexController: => @flexes

class NewPonyRenderController extends PonyRenderController
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    new: (data) =>
        @upperManeModel = data\GetUpperManeModel()
        @lowerManeModel = data\GetLowerManeModel()
        @tailModel = data\GetTailModel()
        @upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(true) if IsValid(@tailModel)
        super(data)

    DataChanges: (state) =>
        return if not @ent
        switch state\GetKey()
            when 'UpperManeModel'
                @upperManeModel = @GetData()\GetUpperManeModel()
                @upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel)
            when 'LowerManeMode'
                @lowerManeModel = @GetData()\GetLowerManeModel()
                @lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel)
            when 'TailModel'
                @tailModel = @GetData()\GetTailModel()
                @tailModel\SetNoDraw(true) if IsValid(@tailModel)
        super(state)

    DrawManeAndTailModels: =>
        textures = @GetTextureController()
        if IsValid(@upperManeModel)
            @upperManeModel\SetNoDraw(true)
            textures\PreDrawUpperMane()
            @upperManeModel\DrawModel()
            textures\PostDrawUpperMane()

        if IsValid(@lowerManeModel)
            @lowerManeModel\SetNoDraw(true)
            textures\PreDrawLowerMane()
            @lowerManeModel\DrawModel()
            textures\PostDrawLowerMane()

        if IsValid(@tailModel)
            @tailModel\SetNoDraw(true)
            textures\PreDrawTail()
            @tailModel\DrawModel()
            textures\PostDrawTail()

    PostDraw: (ent = @ent) =>
        return if @IGNORE_DRAW
        @IGNORE_DRAW = true

        if @GetData()\IsNetworked()
            @upperManeModel = @ent\GetNWEntity('PPM2.UpperManeModel') if not IsValid(@upperManeModel)
            @lowerManeModel = @ent\GetNWEntity('PPM2.LowerManeModel') if not IsValid(@lowerManeModel)
            @tailModel = @ent\GetNWEntity('PPM2.TailModel') if not IsValid(@tailModel)

        @upperManeModel = @GetData()\GetUpperManeModel() if not IsValid(@upperManeModel)
        @lowerManeModel = @GetData()\GetLowerManeModel() if not IsValid(@lowerManeModel)
        @tailModel = @GetData()\GetTailModel() if not IsValid(@tailModel)

        if ENABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @DrawManeAndTailModels()
            render.PopFlashlightMode()
        @DrawManeAndTailModels()

        @IGNORE_DRAW = false
        super(ent)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController

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

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flasglight_pass', '0', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')

class PonyRenderController
    @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(@)

    CompileTextures: => @GetTextureController()\CompileTextures()
    new: (data) =>
        @isValid = true
        @networkedData = data
        @ent = data.ent
        @modelCached = data\GetModel()
        @IGNORE_DRAW = false
        @CompileTextures() if @ent
        @CreateLegs() if @ent == LocalPlayer()
        @socksModel = data\GetSocksModel()
        @CreateFlexController() if @ent
    GetEntity: => @ent
    GetData: => @networkedData
    GetModel: => @networkedData\GetModel()

    GetLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        @CreateLegs() if not IsValid()
        return @legsModel
    CreateLegs: =>
        return NULL if not @isValid
        return NULL if @ent ~= LocalPlayer()
        for ent in *ents.GetAll()
            if ent.isPonyLegsModel
                ent\Remove()
        @legsModel = ClientsideModel(@modelCached)
        with @legsModel
            .isPonyLegsModel = true
            \SetNoDraw(true)
            .__PPM2_PonyData = @GetData()

        @GetData()\GetWeightController()\UpdateWeight(@legsModel)

        @lastLegUpdate = CurTime()
        @legClipPlanePos = Vector(0, 0, 0)
        @legBGSetup = CurTime()
        @legUpdateFrame = 0
        @legClipDot = 0
        @duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
        @legsClipPlane = @@LEG_CLIP_VECTOR
        return @legsModel

    @LEG_SHIFT_CONST = 24
    @LEG_SHIFT_CONST_VEHICLE = 14
    @LEG_Z_CONST = 0
    @LEG_Z_CONST_VEHICLE = 20
    @LEG_ANIM_SPEED_CONST = 1
    @LEG_CLIP_OFFSET_STAND = 28
    @LEG_CLIP_OFFSET_DUCK = 12
    @LEG_CLIP_OFFSET_VEHICLE = 11

    UpdateLegs: =>
        return if not @isValid
        return unless IsValid(@legsModel)
        return if @legUpdateFrame == FrameNumber()
        @legUpdateFrame = FrameNumber()
        ctime = CurTime()
        ply = @ent
        seq = ply\GetSequence()

        if seq ~= @legSeq
            @legSeq = seq
            @legsModel\ResetSequence(seq)

        if @legBGSetup < ctime
            @legBGSetup = ctime + 1
            for group in *ply\GetBodyGroups()
                @legsModel\SetBodygroup(group.id, ply\GetBodygroup(group.id))

        with @legsModel
            \FrameAdvance(ctime - @lastLegUpdate)
            \SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
            @lastLegUpdate = ctime
            \SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
            \SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
            \SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
            \SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
            \SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

        if ply\InVehicle()
            veh = ply\GetVehicle()
            vehAng = veh\GetAngles()
            eyepos = EyePos()
            vehAng\RotateAroundAxis(vehAng\Up(), 90)

            clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
            clipAng\RotateAroundAxis(clipAng\Right(), -90)

            @legsClipPlane = clipAng\Forward()
            @legsModel\SetRenderAngles(vehAng)

            legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
            legClipPlanePos\Rotate(vehAng)
            @legClipPlanePos = eyepos - legClipPlanePos

            drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
            drawPos\Rotate(vehAng)
            @legsModel\SetRenderOrigin(eyepos - drawPos)
        else
            @legsClipPlane = @@LEG_CLIP_VECTOR
            eangles = EyeAngles()
            yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
            newAng = Angle(0, yaw, 0)
            rad = math.rad(yaw)
            sin, cos = math.sin(rad), math.cos(rad)
            pos = ply\GetPos()
            {:x, :y, :z} = pos
            newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
            if ply\Crouching()
                @duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
            else
                @duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

            @legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
            @legsModel\SetRenderAngles(newAng)
            @legsModel\SetRenderOrigin(newPos)
        @legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

    @LEG_CLIP_VECTOR = Vector(0, 0, -1)
    DrawLegs: (start3D = true) =>
        return if not @isValid
        @CreateLegs() unless IsValid(@legsModel)
        return unless IsValid(@legsModel)
        return if @ent\ShouldDrawLocalPlayer()
        @UpdateLegs()

        oldClip = render.EnableClipping(true)
        render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
        cam.Start3D() if start3D

        if ENABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @PreDraw()
            @legsModel\DrawModel()
            @PostDraw()
            render.PopFlashlightMode()
        @PreDraw()
        @legsModel\DrawModel()
        @PostDraw()

        render.PopCustomClipPlane()
        cam.End3D() if start3D
        render.EnableClipping(oldClip)

    IsValid: => IsValid(@ent) and @isValid
    Remove: =>
        @isValid = false
        @flexes\Remove() if @flexes
        @GetRenderController()\Remove()

    PlayerRespawn: =>
        return if not @isValid
        @flexes\PlayerRespawn() if @flexes

    PreDraw: (ent = @ent) =>
        return if not @isValid
        return false if @IGNORE_DRAW
        @GetTextureController()\PreDraw(ent)
        @flexes\Think() if @flexes
    PostDraw: (ent = @ent) =>
        return if not @isValid
        return if @IGNORE_DRAW
        textures = @GetTextureController()
        textures\PostDraw(ent)

        @IGNORE_DRAW = true
        if @GetData()\IsNetworked()
            @socksModel = @ent\GetNWEntity('PPM2.SocksModel') if not IsValid(@socksModel)

        @socksModel = @GetData()\GetSocksModel() if not IsValid(@socksModel)

        if IsValid(@socksModel)
            @socksModel\SetNoDraw(true)
            if ENABLE_FLASHLIGHT_PASS\GetBool()
                render.PushFlashlightMode(true)
                textures\PreDrawSocks()
                @socksModel\DrawModel()
                textures\PostDrawSocks()
                render.PopFlashlightMode()

            textures\PreDrawSocks()
            @socksModel\DrawModel()
            textures\PostDrawSocks()
        @IGNORE_DRAW = false

    @ARMS_MATERIAL_INDEX = 0
    PreDrawArms: (ent) =>
        return if not @isValid
        if ent and not @armsWeightSetup
            @armsWeightSetup = true
            weight = @GetData()\GetWeight()
            vec = Vector(weight, weight, weight)
            for i = 1, 13
                ent\ManipulateBoneScale(i, vec)
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
    PostDrawArms: (ent) =>
        return if not @isValid
        render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

    DataChanges: (state) =>
        return if not @isValid
        return if not @ent
        @GetTextureController()\DataChanges(state)
        @flexes\DataChanges(state) if @flexes
        switch state\GetKey()
            when 'Weight'
                @armsWeightSetup = false
                @GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
            when 'SocksModel'
                @socksModel = @GetData()\GetSocksModel()
                @socksModel\SetNoDraw(true) if IsValid(@socksModel)
            when 'NoFlex'
                if state\GetValue()
                    @flexes\ResetSequences() if @flexes
                    @flexes = nil
                else
                    @CreateFlexController()
    GetTextureController: =>
        return if not @isValid
        if not @renderController
            cls = PPM2.GetTextureController(@modelCached)
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    CreateFlexController: =>
        return if not @isValid
        return if not @ent\IsPlayer()
        return if @GetData()\GetNoFlex()
        if not @flexes
            cls = PPM2.GetFlexController(@modelCached)
            return if not cls
            @flexes = cls(@)
        @flexes.ent = @ent
        return @flexes
    GetFlexController: => @flexes

class NewPonyRenderController extends PonyRenderController
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    new: (data) =>
        @upperManeModel = data\GetUpperManeModel()
        @lowerManeModel = data\GetLowerManeModel()
        @tailModel = data\GetTailModel()
        @upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel)
        @lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel)
        @tailModel\SetNoDraw(true) if IsValid(@tailModel)
        super(data)

    DataChanges: (state) =>
        return if not @ent
        switch state\GetKey()
            when 'UpperManeModel'
                @upperManeModel = @GetData()\GetUpperManeModel()
                @upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel)
            when 'LowerManeMode'
                @lowerManeModel = @GetData()\GetLowerManeModel()
                @lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel)
            when 'TailModel'
                @tailModel = @GetData()\GetTailModel()
                @tailModel\SetNoDraw(true) if IsValid(@tailModel)
        super(state)

    DrawManeAndTailModels: =>
        textures = @GetTextureController()
        if IsValid(@upperManeModel)
            @upperManeModel\SetNoDraw(true)
            textures\PreDrawUpperMane()
            @upperManeModel\DrawModel()
            textures\PostDrawUpperMane()

        if IsValid(@lowerManeModel)
            @lowerManeModel\SetNoDraw(true)
            textures\PreDrawLowerMane()
            @lowerManeModel\DrawModel()
            textures\PostDrawLowerMane()

        if IsValid(@tailModel)
            @tailModel\SetNoDraw(true)
            textures\PreDrawTail()
            @tailModel\DrawModel()
            textures\PostDrawTail()

    PostDraw: (ent = @ent) =>
        return if @IGNORE_DRAW
        @IGNORE_DRAW = true

        if @GetData()\IsNetworked()
            @upperManeModel = @ent\GetNWEntity('PPM2.UpperManeModel') if not IsValid(@upperManeModel)
            @lowerManeModel = @ent\GetNWEntity('PPM2.LowerManeModel') if not IsValid(@lowerManeModel)
            @tailModel = @ent\GetNWEntity('PPM2.TailModel') if not IsValid(@tailModel)

        @upperManeModel = @GetData()\GetUpperManeModel() if not IsValid(@upperManeModel)
        @lowerManeModel = @GetData()\GetLowerManeModel() if not IsValid(@lowerManeModel)
        @tailModel = @GetData()\GetTailModel() if not IsValid(@tailModel)

        if ENABLE_FLASHLIGHT_PASS\GetBool()
            render.PushFlashlightMode(true)
            @DrawManeAndTailModels()
            render.PopFlashlightMode()
        @DrawManeAndTailModels()

        @IGNORE_DRAW = false
        super(ent)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController


Remove import

Remove import

-- type checking for classes
_type = type
export type = (v) ->
  base = _type v
  if base == "table"
    cls = v.__class
    return cls.__name if cls
  base

sauce3.console = require "sauce3/core/console"
sauce3.java    = require "sauce3/core/java"

sauce3.File    = require "sauce3/utils/File"
sauce3.Font    = require "sauce3/utils/Font"
sauce3.Image   = require "sauce3/utils/Image"
sauce3.Quad    = require "sauce3/utils/Quad"

sauce3.file_system = require "sauce3/core/file_system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.graphics    = require "sauce3/core/graphics"
sauce3.console     = require "sauce3/core/console"
sauce3.system      = require "sauce3/core/system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.audio       = require "sauce3/core/audio"

import key_codes, button_codes from require "sauce3/wrappers"

sauce3._key_pressed = (key) ->
  (sauce3.key_pressed key_codes[key]) if sauce3.key_pressed

sauce3._key_released = (key) ->
  (sauce3.key_released key_codes[key]) if sauce3.key_released

sauce3._mouse_pressed = (x, y, button) ->
  (sauce3.mouse_pressed button_codes[button]) if sauce3.mouse_pressed

sauce3._mouse_released = (x, y, button) ->
  (sauce3.mouse_released button_codes[button]) if sauce3.mouse_released

sauce3._quit = ->
  if sauce3.quit then sauce3.quit!
  sauce3.audio.stop_all!

sauce3.run = ->
  dt = sauce3.timer.get_delta!

  sauce3.update(dt) if sauce3.update

  sauce3.graphics.clear!
  sauce3.graphics.origin!

  sauce3.draw! if sauce3.draw

  sauce3.graphics.present!

require "main"

-- type checking for classes
_type = type
export type = (v) ->
  base = _type v
  if base == "table"
    cls = v.__class
    return cls.__name if cls
  base

sauce3.console = require "sauce3/core/console"
sauce3.java    = require "sauce3/core/java"

sauce3.File    = require "sauce3/utils/File"
sauce3.Font    = require "sauce3/utils/Font"
sauce3.Image   = require "sauce3/utils/Image"
sauce3.Quad    = require "sauce3/utils/Quad"

sauce3.file_system = require "sauce3/core/file_system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.graphics    = require "sauce3/core/graphics"
sauce3.console     = require "sauce3/core/console"
sauce3.system      = require "sauce3/core/system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.audio       = require "sauce3/core/audio"
sauce3.ecs         = require "sauce3/core/ecs"

import key_codes, button_codes from require "sauce3/wrappers"

sauce3._key_pressed = (key) ->
  (sauce3.key_pressed key_codes[key]) if sauce3.key_pressed

sauce3._key_released = (key) ->
  (sauce3.key_released key_codes[key]) if sauce3.key_released

sauce3._mouse_pressed = (x, y, button) ->
  (sauce3.mouse_pressed button_codes[button]) if sauce3.mouse_pressed

sauce3._mouse_released = (x, y, button) ->
  (sauce3.mouse_released button_codes[button]) if sauce3.mouse_released

sauce3._quit = ->
  if sauce3.quit then sauce3.quit!
  sauce3.audio.stop_all!

sauce3.run = ->
  dt = sauce3.timer.get_delta!

  sauce3.update(dt) if sauce3.update

  sauce3.graphics.clear!
  sauce3.graphics.origin!

  sauce3.draw! if sauce3.draw

  sauce3.graphics.present!

require "main"


Updated PAC3 Patch

Updated PAC3 Patch

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

timer.Simple 0, ->
    timer.Simple 0, ->
        timer.Simple 0, ->
            return if not pac
            pac.__ppm2_ResetBones = pac.__ppm2_ResetBones or pac.ResetBones
            pac.ResetBones = (ent, ...) ->
                status = pac.__ppm2_ResetBones(ent, ...)
                if data = ent\GetPonyData()
                    hook.Call('PPM2_PACResetBones', nil, StrongEntity(ent), data)
                return status

hook.Add 'PAC3ResetBones', 'PPM2.ResetBones', (ent) ->
    data = ent\GetPonyData()
    hook.Call('PPM2_PACResetBones', nil, StrongEntity(ent), data) if data
    return if not pac.__ppm2_ResetBones
    pac.ResetBones = pac.__ppm2_ResetBones
    pac.__ppm2_ResetBones = nil

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

timer.Simple 0, ->
    timer.Simple 0, ->
        timer.Simple 0, ->
            return if not pac
            pac.__ppm2_ResetBones = pac.__ppm2_ResetBones or pac.ResetBones
            pac.ResetBones = (ent, ...) ->
                status = pac.__ppm2_ResetBones(ent, ...)
                if data = ent\GetPonyData()
                    hook.Call('PPM2_PACResetBones', nil, StrongEntity(ent), data)
                return status

hook.Add 'PAC3ResetBones', 'PPM2.ResetBones', (ent) ->
    if pac.__ppm2_ResetBones
        pac.ResetBones = pac.__ppm2_ResetBones
        pac.__ppm2_ResetBones = nil
    if data = ent\GetPonyData()
        hook.Call('PPM2_PACResetBones', nil, StrongEntity(ent), data)


Fixed drawing of images

Fixed drawing of images

import java from non
File = require "non.objects.File"
Texture = java.require "com.badlogic.gdx.graphics.Texture"
Constants = require "non.constants"

class Image
  new: (filename, format="rgba8", filetype) =>
    file = File filename, filetype
    @texture = java.new Texture, file.file, Constants.formats[format], false
    @texture\setFilter Constants.filters["linear"], Constants.filters["linear"]

  getDimensions: =>
    @getWidth!, @getHeight!

  getFilter: =>
    min_filter = @texture\getMinFilter!
    mag_filter = @texture\getMagFilter!
    Constants.filtercodes[min_filter], Constants.filtercodes[mag_filter]

  getFormat: =>
    texture_data = @texture\getTextureData!
    format = texture_data\getFormat!
    Constants.formatcodes[format]

  getHeight: =>
    @texture\getHeight!

  getWidth: =>
    @texture\getWidth!

  getWrap: =>
    Constants.wraps[@texture\getUWrap!], Constants.wraps[@texture\getVWrap!]

  setFilter: (min, mag) =>
    @texture\setFilter Constants.filters[min], Constants.filters[mag]

  setWrap: (horiz, vert) =>
    @texture\setWrap Constants.wraps[horiz], Constants.wraps[vert]

  free: =>
    @texture\dispose!

return Image

import java from non
File = require "non.objects.File"
Texture = java.require "com.badlogic.gdx.graphics.Texture"
Constants = require "non.constants"

class Image
  new: (filename, format="rgba8", filetype) =>
    file = File filename, filetype
    @texture = java.new Texture, file.file, c.formats[format], false
    @texture\setFilter Constants.filters["linear"], Constants.filters["linear"]

  getDimensions: =>
    @getWidth!, @getHeight!

  getFilter: =>
    min_filter = @texture\getMinFilter!
    mag_filter = @texture\getMagFilter!
    Constants.filtercodes[min_filter], Constants.filtercodes[mag_filter]

  getFormat: =>
    texture_data = @texture\getTextureData!
    format = texture_data\getFormat!
    Constants.formatcodes[format]

  getHeight: =>
    @texture\getHeight!

  getWidth: =>
    @texture\getWidth!

  getWrap: =>
    Constants.wraps[@texture\getUWrap!], Constants.wraps[@texture\getVWrap!]

  setFilter: (min, mag) =>
    @texture\setFilter Constants.filters[min], Constants.filters[mag]

  setWrap: (horiz, vert) =>
    @texture\setWrap Constants.wraps[horiz], Constants.wraps[vert]

  free: =>
    @texture\dispose!

return Image