Make code prettier in db

Make code prettier in db

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

NAME, T = ...

DELIMITER = '.'
DELIMITER_ESCAPED = '\\' .. DELIMITER
DELIMITER_NAME_MATCH = '[^' .. DELIMITER_ESCAPED .. ']+$'
DELIMITER_TOKEN_MATCH = '[^' .. DELIMITER_ESCAPED .. ']+'

local check_global, parse_key, extract_name, prepare

class Database
    new: (@global_name) =>
        @loaded = false

    load: =>
        _G[@global_name] = {} if type(_G[@global_name]) != 'table'
        @global = _G[@global_name]
        @loaded = true
        T.event_manager\fire 'SHAREXP_DB_LOADED', @

    -- Probably not needed
    save: =>
        _G[@global_name] = @global

    get: (key, default) =>
        t = parse_key @, key
        name = extract_name key
        prepare @, name, default, t
        t[name]._VALUE

    set: (key, value) =>
        t = parse_key @, key
        name = extract_name key
        prepare @, name, value, t
        t[name]._VALUE = value

    reset: (key) =>
        t = parse_key @, key
        name = extract_name key
        return if type(t[name]) != 'table'
        t[name]._VALUE = t[name]._DEFAULT

T.database = Database NAME .. 'DB'
T.database_char = Database NAME .. 'CharDB'

check_global = (db) ->
    if type(db.global) != 'table'
        error 'Tried to perform a database operation before database loaded'

parse_key = (db, key) ->
    key = key\lower!
    keys = [token for token in key\gmatch DELIMITER_TOKEN_MATCH]
    check_global!
    t = db.global
    for i = 1, #keys - 1
        t[keys[i]] = {} if type(t[keys[i]]) != 'table'
        t = t[keys[i]]
    t

extract_name = (key) ->
    key\match DELIMITER_NAME_MATCH

prepare = (db, key, default, tbl) ->
    check_global!
    tbl = tbl or db.global
    if type(tbl[key]) != 'table'
        tbl[key] = { _VALUE: default, _DEFAULT: default }
    else
        with tbl[key]
            ._VALUE = default if type(._VALUE) != type default
            ._DEFAULT = default if ._DEFAULT != default

mt =
    __call: (tbl, key, default) ->
        tbl\get key, default

setmetatable T.database, mt
setmetatable T.database_char, mt

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

NAME, T = ...

DELIMITER = '.'
DELIMITER_ESCAPED = '\\' .. DELIMITER
DELIMITER_NAME_MATCH = '[^' .. DELIMITER_ESCAPED .. ']+$'
DELIMITER_TOKEN_MATCH = '[^' .. DELIMITER_ESCAPED .. ']+'

local check_global, parse_key, extract_name, prepare

class Database
    new: (@global_name) =>
        @loaded = false

    load: =>
        _G[@global_name] = {} if type(_G[@global_name]) != 'table'
        @global = _G[@global_name]
        @loaded = true
        T.event_manager\fire 'SHAREXP_DB_LOADED', @

    -- Probably not needed
    save: =>
        _G[@global_name] = @global

    get: (key, default) =>
        t = parse_key @, key
        name = extract_name key
        prepare @, name, default, t
        t[name]._VALUE

    set: (key, value) =>
        t = parse_key @, key
        name = extract_name key
        prepare @, name, value, t
        t[name]._VALUE = value

    reset: (key) =>
        t = parse_key @, key
        name = extract_name key
        return if type(t[name]) != 'table'
        t[name]._VALUE = t[name]._DEFAULT

T.database = Database NAME .. 'DB'
T.database_char = Database NAME .. 'CharDB'

check_global = (db) ->
    if type(db.global) != 'table'
        error 'Tried to perform a database operation before database loaded'

parse_key = (db, key) ->
    key = key\lower!
    keys = [token for token in key\gmatch DELIMITER_TOKEN_MATCH]
    check_global!
    t = db.global
    for i = 1, #keys - 1
        t[keys[i]] = {} if type(t[keys[i]]) != 'table'
        t = t[keys[i]]
    t

extract_name = (key) ->
    key\match DELIMITER_NAME_MATCH

prepare = (db, key, default, tbl) ->
    check_global!
    tbl = tbl or db.global
    if type(tbl[key]) != 'table'
        tbl[key] = { _VALUE: default, _DEFAULT: default }
    else
        tbl[key]._VALUE = default if type(tbl[key]._VALUE) != type default
        tbl[key]._DEFAULT = default if tbl[key]._DEFAULT != default

mt =
    __call: (tbl, key, default) ->
        tbl\get key, default

setmetatable T.database, mt
setmetatable T.database_char, mt


github: use compare URL instead of URL to individual commit

util = require 'util'

hex_to_char = (x) ->
  string.char(tonumber(x, 16))

unescape = (url) ->
  url\gsub("%%(%x%x)", hex_to_char)

handlers = {
  push: (repo, destination, json) ->
    branch = util.bold(json.ref\gsub 'refs/heads/', '')
    for i, c in pairs(json.commits)
      if i > 3
        break
      message = c.message\gsub '\n.*', ''
      if i == 1
        message = message .. ' ' .. json.compare
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{util.nonickalertword c.author.username}: #{message}"
    if #json.commits > 3
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{#json.commits-3} more commits not displayed."
  watch: (repo, destination, json) ->
    action = json.action
    if action == 'started'
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: +1 ★ (Total: #{json.repository.stargazers_count})"
    else
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} watching"
  ping: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: ping"
  commit_comment: (repo, destination, json) ->
    --commenter = json.comment.user.login
    body = json.comment.body
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: commented #{body}"
  pull_request: (repo, destination, json) ->
    action = json.action
    number = util.bold json.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} PR ##{number}: #{json.pull_request.title} #{json.pull_request.html_url}"
  issues: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} issue ##{nr}: #{json.issue.title} #{json.issue.html_url}"
  issue_comment: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: comment on issue ##{nr}: #{json.comment.body} #{json.issue.html_url}"
  fork: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: forked to #{json.forkee.html_url}"
  create: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: created #{json.ref_type} #{util.bold json.ref}"
  delete: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: deleted #{json.ref_type} #{util.bold json.ref}"
  gollum: (repo, destination, json) ->
    return
  release: (repo, destination, json) ->
    return
}

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = unescape(destination)
  json = util.json.decode(req.body)

  repo = json.repository.full_name

  event = req.headers['X-GitHub-Event']
  handler = handlers[event]
  if handler
    handler(repo, destination, json)
  else
    ivar2\Log('error', 'Unknown github event: %s', event)

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}

util = require 'util'

hex_to_char = (x) ->
  string.char(tonumber(x, 16))

unescape = (url) ->
  url\gsub("%%(%x%x)", hex_to_char)

handlers = {
  push: (repo, destination, json) ->
    branch = util.bold(json.ref\gsub 'refs/heads/', '')
    for i, c in pairs(json.commits)
      if i > 3
        break
      message = c.message\gsub '\n.*', ''
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{util.nonickalertword c.author.username}: #{message} #{c.url}"
    if #json.commits > 3
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{branch}, #{#json.commits-3} more commits not displayed."
  watch: (repo, destination, json) ->
    action = json.action
    if action == 'started'
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: +1 ★ (Total: #{json.repository.stargazers_count})"
    else
      ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} watching"
  ping: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: ping"
  commit_comment: (repo, destination, json) ->
    --commenter = json.comment.user.login
    body = json.comment.body
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: commented #{body}"
  pull_request: (repo, destination, json) ->
    action = json.action
    number = util.bold json.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} PR ##{number}: #{json.pull_request.title} #{json.pull_request.html_url}"
  issues: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: #{action} issue ##{nr}: #{json.issue.title} #{json.issue.html_url}"
  issue_comment: (repo, destination, json) ->
    action = json.action
    nr = util.bold json.issue.number
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: comment on issue ##{nr}: #{json.comment.body} #{json.issue.html_url}"
  fork: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: forked to #{json.forkee.html_url}"
  create: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: created #{json.ref_type} #{util.bold json.ref}"
  delete: (repo, destination, json) ->
    ivar2\Msg 'privmsg', destination, nil, "[#{repo}]: #{util.nonickalertword json.sender.login}: deleted #{json.ref_type} #{util.bold json.ref}"
  gollum: (repo, destination, json) ->
    return
  release: (repo, destination, json) ->
    return
}

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = unescape(destination)
  json = util.json.decode(req.body)

  repo = json.repository.full_name

  event = req.headers['X-GitHub-Event']
  handler = handlers[event]
  if handler
    handler(repo, destination, json)
  else
    ivar2\Log('error', 'Unknown github event: %s', event)

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}


workaround for a broken rockspec

workaround for a broken rockspec

import Widget from require "lapis.html"

class Base extends Widget

  shuffled: (t) =>
    order = [{rnd: math.random(), idx: i} for i in ipairs t]
    table.sort(order, (a,b) -> a.rnd < b.rnd)
    [t[x.idx] for x in *order]

  render_endorse_button: (m) =>
    if @current_user
      button class: "module-endorse #{@current_user\endorses(m) and "endorsed" or ""}", ["data-module-id"]: m.id, ->
        span class: "regular", "endorse"
        span class: "endorsed", "endorsed"
        span class: "hover", "deendorse"

  render_users_txtlist: (users, prefix, max) =>
    len = #users
    max = max or len
    if len > 0
      last = users[len]
      users[len] = nil
      text prefix
      n = 0
      for u in *users
        a href: @url_for("main.user", id: u.id), u\get_fullname()
        text ", "
        n += 1
        if n >= max - 1
          break
      a href: @url_for("main.user", id: last.id), last\get_fullname()
      if len - n - 1 > 0
        text " and #{len - n - 1} other#{len - n - 1 > 1 and "s" or ""}"
      text "."

  render_endorsers: (endorsers, max_endorsers) =>
    p class: "module-endorsers", ->
      @render_users_txtlist(@shuffled(endorsers), "Endorsed by: ", max_endorsers)

  render_module_link: (m) =>
    url = @module\get_url()
    if url
      if url\sub(1, 10) == "github.com"
        url = "https://#{url}"
      p -> a href: url, url

  render_modules_txtlist: (deps, prefix) =>
    len = #deps
    if len > 0
      last = deps[len]
      deps[len] = nil
      text prefix
      for u in *deps
        a href: @url_for("main.module", id: u.id), u\get_name()
        text ", "
      a href: @url_for("main.module", id: last.id), last\get_name()
      text "."

  render_dependencies: (deps) =>
    p class: "module-dependencies", ->
      @render_modules_txtlist(deps, "Depends on: ")

  render_reverse_dependencies: (deps) =>
    p class: "module-reverse-dependencies", ->
      @render_modules_txtlist(deps, "Depended on by: ")

  render_all_dependencies: (m) =>
    deps = m\dependencies()
    rev_deps = m\reverse_dependencies()
    if deps[1]
      @render_dependencies(deps)
    if rev_deps[1]
      @render_reverse_dependencies(rev_deps)

  render_labels: (labels) =>
    ul class: "labels-list", ->
      for l in *labels
        li ->
          a href: @url_for("main.label", id: l.id), l\get_name()

  render_endorsers_and_labels: (m, max_endorsers) =>
      endorsers = m\endorsers()
      labels = m\labels(sort: "get_name")
      if endorsers[1]
        @render_endorsers(endorsers, max_endorsers)
      if labels[1]
        div class: "labels-container", ->
          text "Labels: "
          @render_labels(labels)

  render_modules_list: (modules) =>
    ul class: "modules-list", ->
      for m in *modules
        li ->
          @render_endorse_button(m)
          a class: "module-name", href: @url_for("main.module", id: m.id), m\get_name()
          p class: "module-description", m\get_description_or_placeholder()
          @render_endorsers_and_labels(m, 3)

  render_errors: () =>
    if @errors
      div id: "errors", ->
        for error in *@errors
          p ->
            em "ERROR"
            text " " .. error

Base

import Widget from require "lapis.html"

class Base extends Widget

  shuffled: (t) =>
    order = [{rnd: math.random(), idx: i} for i in ipairs t]
    table.sort(order, (a,b) -> a.rnd < b.rnd)
    [t[x.idx] for x in *order]

  render_endorse_button: (m) =>
    if @current_user
      button class: "module-endorse #{@current_user\endorses(m) and "endorsed" or ""}", ["data-module-id"]: m.id, ->
        span class: "regular", "endorse"
        span class: "endorsed", "endorsed"
        span class: "hover", "deendorse"

  render_users_txtlist: (users, prefix, max) =>
    len = #users
    max = max or len
    if len > 0
      last = users[len]
      users[len] = nil
      text prefix
      n = 0
      for u in *users
        a href: @url_for("main.user", id: u.id), u\get_fullname()
        text ", "
        n += 1
        if n >= max - 1
          break
      a href: @url_for("main.user", id: last.id), last\get_fullname()
      if len - n - 1 > 0
        text " and #{len - n - 1} other#{len - n - 1 > 1 and "s" or ""}"
      text "."

  render_endorsers: (endorsers, max_endorsers) =>
    p class: "module-endorsers", ->
      @render_users_txtlist(@shuffled(endorsers), "Endorsed by: ", max_endorsers)

  render_module_link: (m) =>
    url = @module\get_url()
    if url
      p -> a href: url, url

  render_modules_txtlist: (deps, prefix) =>
    len = #deps
    if len > 0
      last = deps[len]
      deps[len] = nil
      text prefix
      for u in *deps
        a href: @url_for("main.module", id: u.id), u\get_name()
        text ", "
      a href: @url_for("main.module", id: last.id), last\get_name()
      text "."

  render_dependencies: (deps) =>
    p class: "module-dependencies", ->
      @render_modules_txtlist(deps, "Depends on: ")

  render_reverse_dependencies: (deps) =>
    p class: "module-reverse-dependencies", ->
      @render_modules_txtlist(deps, "Depended on by: ")

  render_all_dependencies: (m) =>
    deps = m\dependencies()
    rev_deps = m\reverse_dependencies()
    if deps[1]
      @render_dependencies(deps)
    if rev_deps[1]
      @render_reverse_dependencies(rev_deps)

  render_labels: (labels) =>
    ul class: "labels-list", ->
      for l in *labels
        li ->
          a href: @url_for("main.label", id: l.id), l\get_name()

  render_endorsers_and_labels: (m, max_endorsers) =>
      endorsers = m\endorsers()
      labels = m\labels(sort: "get_name")
      if endorsers[1]
        @render_endorsers(endorsers, max_endorsers)
      if labels[1]
        div class: "labels-container", ->
          text "Labels: "
          @render_labels(labels)

  render_modules_list: (modules) =>
    ul class: "modules-list", ->
      for m in *modules
        li ->
          @render_endorse_button(m)
          a class: "module-name", href: @url_for("main.module", id: m.id), m\get_name()
          p class: "module-description", m\get_description_or_placeholder()
          @render_endorsers_and_labels(m, 3)

  render_errors: () =>
    if @errors
      div id: "errors", ->
        for error in *@errors
          p ->
            em "ERROR"
            text " " .. error

Base


add line breaks before button Update code

import Widget from require "lapis.html"

quizs = require('quiz.groups')
kr = require('quiz.kr')

model = require('model')
msk_time = require "msk_time"
config = require("lapis.config").get!

Helpers = require('views.helpers')

class AllTests extends Widget
  @include Helpers

  content: =>
    div class: "body", ->
      if @prep
        a href: @url_for("prep-submissions"), ->
          text @_("code submissions")
        text ' | '
        a href: @url_for("prep-quizs"), ->
          text @_("finished quizes")
        text ' | '
        a href: @url_for("prep-quizs-today"), ->
          text @_("today quizes")
        text ' | '
        a href: @url_for("quiz-state"), ->
          text @_("enable/disable quiz")
        url = @url_for("update-code")
        br!
        br!
        form {method: "POST", action: url,
            enctype: "multipart/form-data"}, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input type: "submit", value: @_("Update code")
      if config.checker_url
        p @_("Submit programming assignment")
        @submission_form!
      p @_("Start new quiz:")
      for _, group in ipairs(quizs)
        group_name = group[1]
        group_quizs = group[2]
        element 'table', -> tr ->
          td ->
            text group_name
          for _, quiz in ipairs(group_quizs)
            quiz_name = quiz[1]
            if model.Quiz.can_create(quiz_name) or @prep
              td ->
                @new_test_button quiz_name
      my_quizs = (t, state) ->
        qq = model.my_quizs(@, state)
        if #qq > 0
          p t
        element 'ul', ->
          for quiz in *qq
            element 'li', ->
              time = msk_time(quiz.created_at)
              raw quiz\anchor(@) .. ', ' .. time
              if quiz.state == model.FINISHED
                r = quiz.right_answers
                a = quiz.tasks
                text " | "
                b style: 'color: ' .. quiz\color!, ->
                  text "#{r} / #{a}"
      my_quizs @_("Your active quizes:"), model.ACTIVE
      my_quizs @_("Your finished quizes:"), model.FINISHED
      submissions = model.Submission\select(
        'where submission.user = ? order by id', @session.user)
      @print_submissions(submissions)

import Widget from require "lapis.html"

quizs = require('quiz.groups')
kr = require('quiz.kr')

model = require('model')
msk_time = require "msk_time"
config = require("lapis.config").get!

Helpers = require('views.helpers')

class AllTests extends Widget
  @include Helpers

  content: =>
    div class: "body", ->
      if @prep
        a href: @url_for("prep-submissions"), ->
          text @_("code submissions")
        text ' | '
        a href: @url_for("prep-quizs"), ->
          text @_("finished quizes")
        text ' | '
        a href: @url_for("prep-quizs-today"), ->
          text @_("today quizes")
        text ' | '
        a href: @url_for("quiz-state"), ->
          text @_("enable/disable quiz")
        url = @url_for("update-code")
        form {method: "POST", action: url,
            enctype: "multipart/form-data"}, ->
          input {type: "hidden", name: "csrf_token",
              value: @new_csrf}
          input type: "submit", value: @_("Update code")
      if config.checker_url
        p @_("Submit programming assignment")
        @submission_form!
      p @_("Start new quiz:")
      for _, group in ipairs(quizs)
        group_name = group[1]
        group_quizs = group[2]
        element 'table', -> tr ->
          td ->
            text group_name
          for _, quiz in ipairs(group_quizs)
            quiz_name = quiz[1]
            if model.Quiz.can_create(quiz_name) or @prep
              td ->
                @new_test_button quiz_name
      my_quizs = (t, state) ->
        qq = model.my_quizs(@, state)
        if #qq > 0
          p t
        element 'ul', ->
          for quiz in *qq
            element 'li', ->
              time = msk_time(quiz.created_at)
              raw quiz\anchor(@) .. ', ' .. time
              if quiz.state == model.FINISHED
                r = quiz.right_answers
                a = quiz.tasks
                text " | "
                b style: 'color: ' .. quiz\color!, ->
                  text "#{r} / #{a}"
      my_quizs @_("Your active quizes:"), model.ACTIVE
      my_quizs @_("Your finished quizes:"), model.FINISHED
      submissions = model.Submission\select(
        'where submission.user = ? order by id', @session.user)
      @print_submissions(submissions)


Expose PonySizeController in main table

Expose PonySizeController in main table

--
-- Copyright (C) 2017-2018 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.
--

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

USE_NEW_HULL = CreateConVar('ppm2_sv_newhull', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Use proper collision box for ponies. Slightly affects jump mechanics. When disabled, unexpected behaviour could happen.')
ALLOW_TO_MODIFY_SCALE = PPM2.ALLOW_TO_MODIFY_SCALE

class PonySizeController extends PPM2.ControllerChildren
	@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'}

	@NECK_BONE_1 = 'LrigNeck1'
	@NECK_BONE_2 = 'LrigNeck2'
	@NECK_BONE_3 = 'LrigNeck3'
	@NECK_BONE_4 = 'LrigScull'

	@LEGS_BONE_ROOT = 'LrigPelvis'

	@LEGS_FRONT_1 = 'Lrig_LEG_FL_FrontHoof'
	@LEGS_FRONT_2 = 'Lrig_LEG_FR_FrontHoof'

	@LEGS_FRONT_3 = 'Lrig_LEG_FL_Metacarpus'
	@LEGS_FRONT_4 = 'Lrig_LEG_FR_Metacarpus'

	@LEGS_FRONT_5 = 'Lrig_LEG_FL_Radius'
	@LEGS_FRONT_6 = 'Lrig_LEG_FR_Radius'

	@LEGS_BEHIND_1_1 = 'Lrig_LEG_BR_Tibia'
	@LEGS_BEHIND_2_1 = 'Lrig_LEG_BR_PhalanxPrima'
	@LEGS_BEHIND_3_1 = 'Lrig_LEG_BR_LargeCannon'

	@LEGS_BEHIND_1_2 = 'Lrig_LEG_BL_Tibia'
	@LEGS_BEHIND_2_2 = 'Lrig_LEG_BL_PhalanxPrima'
	@LEGS_BEHIND_3_2 = 'Lrig_LEG_BL_LargeCannon'

	@NEXT_OBJ_ID = 0

	Remap: =>
		mapping = {
			'NECK_BONE_1'
			'NECK_BONE_2'
			'NECK_BONE_3'
			'NECK_BONE_4'
			'LEGS_BONE_ROOT'
			'LEGS_FRONT_1'
			'LEGS_FRONT_2'
			'LEGS_FRONT_3'
			'LEGS_FRONT_4'
			'LEGS_FRONT_5'
			'LEGS_FRONT_6'
			'LEGS_BEHIND_1_1'
			'LEGS_BEHIND_2_1'
			'LEGS_BEHIND_3_1'
			'LEGS_BEHIND_1_2'
			'LEGS_BEHIND_2_2'
			'LEGS_BEHIND_3_2'
		}

		@validSkeleton = true

		for _, name in ipairs mapping
			@[name] = @ent\LookupBone(@@[name])
			@validSkeleton = false if not @[name]

	new: (controller) =>
		@isValid = true
		@ent = controller.ent
		@entID = controller.entID
		@controller = controller
		@objID = @@NEXT_OBJ_ID
		@@NEXT_OBJ_ID += 1
		@lastPAC3BoneReset = 0
		@Remap()
		PPM2.DebugPrint('Created new size controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)
		@ent\SetModelScale(1) if not @ent\IsPlayer() and @ent\GetModelScale() ~= 1

	__tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
	IsValid: => @controller\IsValid()
	GetData: => @controller
	GetEntity: => @ent
	GetEntityID: => @entID
	GetDataID: => @entID
	IsNetworked: => @controller\IsNetworked()
	AllowResize: => not @controller\IsNetworked() or ALLOW_TO_MODIFY_SCALE\GetBool()

	@STEP_SIZE = 20
	@PONY_HULL = 17
	@HULL_MINS = Vector(-@PONY_HULL, -@PONY_HULL, 0)
	@HULL_MAXS = Vector(@PONY_HULL, @PONY_HULL, 72 * PPM2.PONY_HEIGHT_MODIFIER)
	@HULL_MAXS_DUCK = Vector(@PONY_HULL, @PONY_HULL, 36 * PPM2.PONY_HEIGHT_MODIFIER_DUCK_HULL)

	@DEFAULT_HULL_MINS = Vector(-16, -16, 0)
	@DEFAULT_HULL_MAXS = Vector(16, 16, 72)
	@DEFAULT_HULL_MAXS_DUCK = Vector(16, 16, 36)
	@DEF_SCALE = Vector(1, 1, 1)

	DataChanges: (state) =>
		return if not IsValid(@ent)
		return if not @ent\IsPony()
		@Remap()
		@ent\SetModelScale(1) if not @ent\IsPlayer() and @ent\GetModelScale() ~= 1

		if state\GetKey() == 'PonySize'
			@ModifyScale()

		if state\GetKey() == 'NeckSize'
			@ModifyNeck()
			@ModifyViewOffset()

		if state\GetKey() == 'LegsSize'
			@ModifyLegs()
			@ModifyHull()
			@ModifyViewOffset()

	ResetViewOffset: (ent = @ent) =>
		ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL) if ent.SetViewOffset
		ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL) if ent.SetViewOffsetDucked

	ResetHulls: (ent = @ent) =>
		ent\ResetHull() if ent.ResetHull
		ent\SetStepSize(@@STEP_SIZE) if ent.SetStepSize
		ent.__ppm2_modified_hull = false

	ResetJumpHeight: (ent = @ent) =>
		return if CLIENT
		return if not ent.SetJumpPower
		return if not ent.__ppm2_modified_jump
		ent\SetJumpPower(ent\GetJumpPower() / PPM2.PONY_JUMP_MODIFIER)
		ent.__ppm2_modified_jump = false

	ResetModelScale: (ent = @ent) =>
		if SERVER
			--ent\SetModelScale(1)
			return
		mat = Matrix()
		mat\Scale(@@DEF_SCALE)
		ent\EnableMatrix('RenderMultiply', mat)

	ResetScale: (ent = @ent) =>
		return if not IsValid(ent)

		if USE_NEW_HULL\GetBool() or ent.__ppm2_modified_hull
			@ResetHulls(ent)
			@ResetJumpHeight(ent)

		@ResetViewOffset(ent)
		@ResetModelScale(ent)
		if @validSkeleton
			@ResetNeck(ent)
			@ResetLegs(ent)

	ResetNeck: (ent = @ent) =>
		return if not CLIENT
		return if not IsValid(@ent)
		return if not @validSkeleton
		with ent
			\ManipulateBoneScale2Safe(@NECK_BONE_1, LVector(1, 1, 1))
			\ManipulateBoneScale2Safe(@NECK_BONE_2, LVector(1, 1, 1))
			\ManipulateBoneScale2Safe(@NECK_BONE_3, LVector(1, 1, 1))
			\ManipulateBoneScale2Safe(@NECK_BONE_4, LVector(1, 1, 1))
			\ManipulateBoneAngles2Safe(@NECK_BONE_1, Angle(0, 0, 0))
			\ManipulateBoneAngles2Safe(@NECK_BONE_2, Angle(0, 0, 0))
			\ManipulateBoneAngles2Safe(@NECK_BONE_3, Angle(0, 0, 0))
			\ManipulateBoneAngles2Safe(@NECK_BONE_4, Angle(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_1, LVector(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_2, LVector(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_3, LVector(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_4, LVector(0, 0, 0))

	ResetLegs: (ent = @ent) =>
		return if not CLIENT
		return if not IsValid(ent)
		return if not @validSkeleton

		vec1 = LVector(1, 1, 1)
		vec2 = LVector(0, 0, 0)
		ang = Angle(0, 0, 0)

		with ent
			\ManipulateBoneScale2Safe(@LEGS_BONE_ROOT, vec1)
			\ManipulateBoneScale2Safe(@LEGS_FRONT_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_FRONT_2, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_1_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_2_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_3_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_1_2, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_2_2, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_3_2, vec1)

			\ManipulateBoneAngles2Safe(@LEGS_BONE_ROOT, ang)
			\ManipulateBoneAngles2Safe(@LEGS_FRONT_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_FRONT_2, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_1_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_2_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_3_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_1_2, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_2_2, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_3_2, ang)

			\ManipulateBonePosition2Safe(@LEGS_BONE_ROOT, vec2)
			\ManipulateBonePosition2Safe(@LEGS_FRONT_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_FRONT_2, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_2, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_2, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_2, vec2)

	Remove: => @ResetScale()
	Reset: =>
		@ResetScale()
		@ResetNeck()
		@ResetLegs()
		@ModifyScale()

	GetLegsSize: => @GetData()\GetLegsSize()
	GetLegsScale: => @GetData()\GetLegsSize()
	GetNeckSize: => @GetData()\GetNeckSize()
	GetNeckScale: => @GetData()\GetNeckSize()
	GetPonySize: => @GetData()\GetPonySize()
	GetPonyScale: => @GetData()\GetPonySize()

	PlayerDeath: =>
		@ResetScale()
		@ResetNeck()
		@ResetLegs()
		@Remap()

	PlayerRespawn: =>
		@Remap()
		@ResetScale()
		@ModifyScale()

	SlowUpdate: =>
		@Remap()
		@ModifyScale()

	ModifyHull: (ent = @ent) =>
		ent.__ppm2_modified_hull = true
		size = @GetPonySize()
		legssize = @GetLegsModifier()

		HULL_MINS = Vector(@@HULL_MINS)
		HULL_MAXS = Vector(@@HULL_MAXS)
		HULL_MAXS_DUCK = Vector(@@HULL_MAXS_DUCK)

		if @AllowResize()
			HULL_MINS *= size
			HULL_MAXS *= size
			HULL_MAXS_DUCK *= size

			HULL_MINS.z *= legssize
			HULL_MAXS.z *= legssize
			HULL_MAXS_DUCK.z *= legssize

		with ent
			if .SetHull
				cmins, cmaxs = \GetHull()
				\SetHull(HULL_MINS, HULL_MAXS) if cmins ~= HULL_MINS or cmaxs ~= HULL_MAXS

			if .SetHullDuck
				cmins, cmaxs = \GetHullDuck()
				\SetHullDuck(HULL_MINS, HULL_MAXS_DUCK) if cmins ~= HULL_MINS or cmaxs ~= HULL_MAXS_DUCK

			if .SetStepSize
				newsize = @@STEP_SIZE * size * @GetLegsModifier(1.2)
				\SetStepSize(newsize) if \GetStepSize() ~= newsize

	ModifyJumpHeight: (ent = @ent) =>
		return if CLIENT
		return if not @ent.SetJumpPower
		return if ent.__ppm2_modified_jump
		ent\SetJumpPower(ent\GetJumpPower() * PPM2.PONY_JUMP_MODIFIER)
		ent.__ppm2_modified_jump = true

	GetLegsModifier: (mult = 0.4) =>
		if @AllowResize()
			1 + (@GetLegsSize() - 1) * mult
		else
			1

	ModifyViewOffset: (ent = @ent) =>
		size = @GetPonySize()
		necksize = 1 + (@GetNeckSize() - 1) * .3
		legssize = @GetLegsModifier()

		PLAYER_VIEW_OFFSET = Vector(PPM2.PLAYER_VIEW_OFFSET)
		PLAYER_VIEW_OFFSET_DUCK = Vector(PPM2.PLAYER_VIEW_OFFSET_DUCK)

		if @AllowResize()
			PLAYER_VIEW_OFFSET *= size * necksize
			PLAYER_VIEW_OFFSET_DUCK *= size * necksize

			PLAYER_VIEW_OFFSET.z *= legssize
			PLAYER_VIEW_OFFSET_DUCK.z *= legssize

		ent\SetViewOffset(PLAYER_VIEW_OFFSET) if ent.SetViewOffset
		ent\SetViewOffsetDucked(PLAYER_VIEW_OFFSET_DUCK) if ent.SetViewOffsetDucked

	ModifyModelScale: (ent = @ent) =>
		return if not @AllowResize()
		-- https://github.com/Facepunch/garrysmod-issues/issues/2193
		if SERVER
			if not ent\IsPlayer()
				newscale = (@GetPonySize() * 100)\floor() / 100
				currscale = (ent\GetModelScale() * 100)\floor() / 100
				if currscale ~= newscale
					if type(ent) == 'NPC' or type(NPC) == 'NextBot'
						ent\SetPreventTransmit(ply, true) for _, ply in ipairs player.GetAll()
						ent\SetModelScale(newscale)
						ent\SetPreventTransmit(ply, false) for _, ply in ipairs player.GetAll()
					else
						ent\SetModelScale(newscale)
			return
		--return if not ent\IsClientsideEntity()
		return if ent.RenderOverride -- PAC3 and other stuff that can change this value
		mat = Matrix()
		mat\Scale(@@DEF_SCALE * @GetPonySize())
		ent\EnableMatrix('RenderMultiply', mat)

	ModifyScale: (ent = @ent) =>
		return if not IsValid(ent)
		return if not ent\IsPony()
		return if ent.Alive and not ent\Alive()

		if USE_NEW_HULL\GetBool()
			@ModifyHull(ent)
			@ModifyJumpHeight(ent)

		@ModifyViewOffset(ent)
		@ModifyModelScale(ent)

		if CLIENT and @lastPAC3BoneReset < RealTimeL()
			@ModifyNeck(ent)
			@ModifyLegs(ent)

	ModifyNeck: (ent = @ent) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		size = (@GetNeckSize() - 1) * 3
		vec = LVector(size, -size, 0)

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
		emptyLVector = LVector(0, 0, 0)

		with ent
			\ManipulateBonePosition2Safe(@NECK_BONE_1, vec + (boneAnimTable[@NECK_BONE_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@NECK_BONE_2, vec + (boneAnimTable[@NECK_BONE_2] or emptyLVector))
			\ManipulateBonePosition2Safe(@NECK_BONE_3, vec + (boneAnimTable[@NECK_BONE_3] or emptyLVector))
			\ManipulateBonePosition2Safe(@NECK_BONE_4, vec + (boneAnimTable[@NECK_BONE_4] or emptyLVector))

	ModifyLegs: (ent = @ent) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		realSizeModify = @GetLegsSize() - 1
		size = realSizeModify * 3

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
		emptyLVector = LVector(0, 0, 0)

		with ent
			\ManipulateBonePosition2Safe(@LEGS_BONE_ROOT, LVector(0, 0, size * 5) + \GetManipulateBonePosition2Safe(@LEGS_BONE_ROOT))

			\ManipulateBonePosition2Safe(@LEGS_FRONT_1, LVector(size * 1.5, 0, 0) + (boneAnimTable[@LEGS_FRONT_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_FRONT_2, LVector(size * 1.5, 0, 0) + (boneAnimTable[@LEGS_FRONT_2] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_FRONT_3, LVector(size, 0, 0) + (boneAnimTable[@LEGS_FRONT_3] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_FRONT_4, LVector(size, 0, 0) + (boneAnimTable[@LEGS_FRONT_4] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_FRONT_5, LVector(size, size, 0) + (boneAnimTable[@LEGS_FRONT_5] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_FRONT_6, LVector(size, size, 0) + (boneAnimTable[@LEGS_FRONT_6] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_1, LVector(size, -size * 0.5, 0) + (boneAnimTable[@LEGS_BEHIND_1_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_2, LVector(size, -size * 0.5, 0) + (boneAnimTable[@LEGS_BEHIND_1_2] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_1, LVector(size, 0, 0) + (boneAnimTable[@LEGS_BEHIND_2_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_2, LVector(size, 0, 0) + (boneAnimTable[@LEGS_BEHIND_2_2] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_1, LVector(size * 2, 0, 0) + (boneAnimTable[@LEGS_BEHIND_3_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_2, LVector(size * 2, 0, 0) + (boneAnimTable[@LEGS_BEHIND_3_2] or emptyLVector))

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

PPM2.PonySizeController = PonySizeController

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

	@NECK_BONE_1 = 'LrigNeck1'
	@NECK_BONE_2 = 'LrigNeck2'
	@NECK_BONE_3 = 'LrigNeck3'
	@NECK_BONE_4 = 'LrigScull'

	@LEGS_FRONT_1 = 'Lrig_LEG_FL_FrontHoof'
	@LEGS_FRONT_2 = 'Lrig_LEG_FR_FrontHoof'

	@LEGS_FRONT_3 = 'Lrig_LEG_FL_Metacarpus'
	@LEGS_FRONT_4 = 'Lrig_LEG_FR_Metacarpus'

	@LEGS_FRONT_5 = 'Lrig_LEG_FL_Radius'
	@LEGS_FRONT_6 = 'Lrig_LEG_FR_Radius'

	@LEGS_BEHIND_1_1 = 'Lrig_LEG_BL_Tibia'
	@LEGS_BEHIND_1_2 = 'Lrig_LEG_BR_Tibia'

	@LEGS_BEHIND_2_1 = 'Lrig_LEG_BL_PhalanxPrima'
	@LEGS_BEHIND_2_2 = 'Lrig_LEG_BR_PhalanxPrima'

	@LEGS_BEHIND_3_1 = 'Lrig_LEG_BL_LargeCannon'
	@LEGS_BEHIND_3_2 = 'Lrig_LEG_BR_LargeCannon'

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

PPM2.NewPonySizeContoller = NewPonySizeContoller

hook.Add 'PPM2.SetupBones', 'PPM2.Size', (ent, data) ->
	if sizes = data\GetSizeController()
		sizes.ent = ent
		sizes\ModifyNeck()
		sizes\ModifyLegs()
		sizes.lastPAC3BoneReset = RealTimeL() + 1

ppm2_sv_allow_resize = ->
	for _, ply in ipairs player.GetAll()
		if data = ply\GetPonyData()
			if scale = data\GetSizeController()
				scale\Reset()

cvars.AddChangeCallback 'ppm2_sv_allow_resize', ppm2_sv_allow_resize, 'PPM2.Scale'

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

--
-- Copyright (C) 2017-2018 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.
--

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

USE_NEW_HULL = CreateConVar('ppm2_sv_newhull', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Use proper collision box for ponies. Slightly affects jump mechanics. When disabled, unexpected behaviour could happen.')
ALLOW_TO_MODIFY_SCALE = PPM2.ALLOW_TO_MODIFY_SCALE

class PonySizeController extends PPM2.ControllerChildren
	@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'}

	@NECK_BONE_1 = 'LrigNeck1'
	@NECK_BONE_2 = 'LrigNeck2'
	@NECK_BONE_3 = 'LrigNeck3'
	@NECK_BONE_4 = 'LrigScull'

	@LEGS_BONE_ROOT = 'LrigPelvis'

	@LEGS_FRONT_1 = 'Lrig_LEG_FL_FrontHoof'
	@LEGS_FRONT_2 = 'Lrig_LEG_FR_FrontHoof'

	@LEGS_FRONT_3 = 'Lrig_LEG_FL_Metacarpus'
	@LEGS_FRONT_4 = 'Lrig_LEG_FR_Metacarpus'

	@LEGS_FRONT_5 = 'Lrig_LEG_FL_Radius'
	@LEGS_FRONT_6 = 'Lrig_LEG_FR_Radius'

	@LEGS_BEHIND_1_1 = 'Lrig_LEG_BR_Tibia'
	@LEGS_BEHIND_2_1 = 'Lrig_LEG_BR_PhalanxPrima'
	@LEGS_BEHIND_3_1 = 'Lrig_LEG_BR_LargeCannon'

	@LEGS_BEHIND_1_2 = 'Lrig_LEG_BL_Tibia'
	@LEGS_BEHIND_2_2 = 'Lrig_LEG_BL_PhalanxPrima'
	@LEGS_BEHIND_3_2 = 'Lrig_LEG_BL_LargeCannon'

	@NEXT_OBJ_ID = 0

	Remap: =>
		mapping = {
			'NECK_BONE_1'
			'NECK_BONE_2'
			'NECK_BONE_3'
			'NECK_BONE_4'
			'LEGS_BONE_ROOT'
			'LEGS_FRONT_1'
			'LEGS_FRONT_2'
			'LEGS_FRONT_3'
			'LEGS_FRONT_4'
			'LEGS_FRONT_5'
			'LEGS_FRONT_6'
			'LEGS_BEHIND_1_1'
			'LEGS_BEHIND_2_1'
			'LEGS_BEHIND_3_1'
			'LEGS_BEHIND_1_2'
			'LEGS_BEHIND_2_2'
			'LEGS_BEHIND_3_2'
		}

		@validSkeleton = true

		for _, name in ipairs mapping
			@[name] = @ent\LookupBone(@@[name])
			@validSkeleton = false if not @[name]

	new: (controller) =>
		@isValid = true
		@ent = controller.ent
		@entID = controller.entID
		@controller = controller
		@objID = @@NEXT_OBJ_ID
		@@NEXT_OBJ_ID += 1
		@lastPAC3BoneReset = 0
		@Remap()
		PPM2.DebugPrint('Created new size controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)
		@ent\SetModelScale(1) if not @ent\IsPlayer() and @ent\GetModelScale() ~= 1

	__tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
	IsValid: => @controller\IsValid()
	GetData: => @controller
	GetEntity: => @ent
	GetEntityID: => @entID
	GetDataID: => @entID
	IsNetworked: => @controller\IsNetworked()
	AllowResize: => not @controller\IsNetworked() or ALLOW_TO_MODIFY_SCALE\GetBool()

	@STEP_SIZE = 20
	@PONY_HULL = 17
	@HULL_MINS = Vector(-@PONY_HULL, -@PONY_HULL, 0)
	@HULL_MAXS = Vector(@PONY_HULL, @PONY_HULL, 72 * PPM2.PONY_HEIGHT_MODIFIER)
	@HULL_MAXS_DUCK = Vector(@PONY_HULL, @PONY_HULL, 36 * PPM2.PONY_HEIGHT_MODIFIER_DUCK_HULL)

	@DEFAULT_HULL_MINS = Vector(-16, -16, 0)
	@DEFAULT_HULL_MAXS = Vector(16, 16, 72)
	@DEFAULT_HULL_MAXS_DUCK = Vector(16, 16, 36)
	@DEF_SCALE = Vector(1, 1, 1)

	DataChanges: (state) =>
		return if not IsValid(@ent)
		return if not @ent\IsPony()
		@Remap()
		@ent\SetModelScale(1) if not @ent\IsPlayer() and @ent\GetModelScale() ~= 1

		if state\GetKey() == 'PonySize'
			@ModifyScale()

		if state\GetKey() == 'NeckSize'
			@ModifyNeck()
			@ModifyViewOffset()

		if state\GetKey() == 'LegsSize'
			@ModifyLegs()
			@ModifyHull()
			@ModifyViewOffset()

	ResetViewOffset: (ent = @ent) =>
		ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL) if ent.SetViewOffset
		ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL) if ent.SetViewOffsetDucked

	ResetHulls: (ent = @ent) =>
		ent\ResetHull() if ent.ResetHull
		ent\SetStepSize(@@STEP_SIZE) if ent.SetStepSize
		ent.__ppm2_modified_hull = false

	ResetJumpHeight: (ent = @ent) =>
		return if CLIENT
		return if not ent.SetJumpPower
		return if not ent.__ppm2_modified_jump
		ent\SetJumpPower(ent\GetJumpPower() / PPM2.PONY_JUMP_MODIFIER)
		ent.__ppm2_modified_jump = false

	ResetModelScale: (ent = @ent) =>
		if SERVER
			--ent\SetModelScale(1)
			return
		mat = Matrix()
		mat\Scale(@@DEF_SCALE)
		ent\EnableMatrix('RenderMultiply', mat)

	ResetScale: (ent = @ent) =>
		return if not IsValid(ent)

		if USE_NEW_HULL\GetBool() or ent.__ppm2_modified_hull
			@ResetHulls(ent)
			@ResetJumpHeight(ent)

		@ResetViewOffset(ent)
		@ResetModelScale(ent)
		if @validSkeleton
			@ResetNeck(ent)
			@ResetLegs(ent)

	ResetNeck: (ent = @ent) =>
		return if not CLIENT
		return if not IsValid(@ent)
		return if not @validSkeleton
		with ent
			\ManipulateBoneScale2Safe(@NECK_BONE_1, LVector(1, 1, 1))
			\ManipulateBoneScale2Safe(@NECK_BONE_2, LVector(1, 1, 1))
			\ManipulateBoneScale2Safe(@NECK_BONE_3, LVector(1, 1, 1))
			\ManipulateBoneScale2Safe(@NECK_BONE_4, LVector(1, 1, 1))
			\ManipulateBoneAngles2Safe(@NECK_BONE_1, Angle(0, 0, 0))
			\ManipulateBoneAngles2Safe(@NECK_BONE_2, Angle(0, 0, 0))
			\ManipulateBoneAngles2Safe(@NECK_BONE_3, Angle(0, 0, 0))
			\ManipulateBoneAngles2Safe(@NECK_BONE_4, Angle(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_1, LVector(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_2, LVector(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_3, LVector(0, 0, 0))
			\ManipulateBonePosition2Safe(@NECK_BONE_4, LVector(0, 0, 0))

	ResetLegs: (ent = @ent) =>
		return if not CLIENT
		return if not IsValid(ent)
		return if not @validSkeleton

		vec1 = LVector(1, 1, 1)
		vec2 = LVector(0, 0, 0)
		ang = Angle(0, 0, 0)

		with ent
			\ManipulateBoneScale2Safe(@LEGS_BONE_ROOT, vec1)
			\ManipulateBoneScale2Safe(@LEGS_FRONT_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_FRONT_2, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_1_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_2_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_3_1, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_1_2, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_2_2, vec1)
			\ManipulateBoneScale2Safe(@LEGS_BEHIND_3_2, vec1)

			\ManipulateBoneAngles2Safe(@LEGS_BONE_ROOT, ang)
			\ManipulateBoneAngles2Safe(@LEGS_FRONT_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_FRONT_2, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_1_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_2_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_3_1, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_1_2, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_2_2, ang)
			\ManipulateBoneAngles2Safe(@LEGS_BEHIND_3_2, ang)

			\ManipulateBonePosition2Safe(@LEGS_BONE_ROOT, vec2)
			\ManipulateBonePosition2Safe(@LEGS_FRONT_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_FRONT_2, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_1, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_2, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_2, vec2)
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_2, vec2)

	Remove: => @ResetScale()
	Reset: =>
		@ResetScale()
		@ResetNeck()
		@ResetLegs()
		@ModifyScale()

	GetLegsSize: => @GetData()\GetLegsSize()
	GetLegsScale: => @GetData()\GetLegsSize()
	GetNeckSize: => @GetData()\GetNeckSize()
	GetNeckScale: => @GetData()\GetNeckSize()
	GetPonySize: => @GetData()\GetPonySize()
	GetPonyScale: => @GetData()\GetPonySize()

	PlayerDeath: =>
		@ResetScale()
		@ResetNeck()
		@ResetLegs()
		@Remap()

	PlayerRespawn: =>
		@Remap()
		@ResetScale()
		@ModifyScale()

	SlowUpdate: =>
		@Remap()
		@ModifyScale()

	ModifyHull: (ent = @ent) =>
		ent.__ppm2_modified_hull = true
		size = @GetPonySize()
		legssize = @GetLegsModifier()

		HULL_MINS = Vector(@@HULL_MINS)
		HULL_MAXS = Vector(@@HULL_MAXS)
		HULL_MAXS_DUCK = Vector(@@HULL_MAXS_DUCK)

		if @AllowResize()
			HULL_MINS *= size
			HULL_MAXS *= size
			HULL_MAXS_DUCK *= size

			HULL_MINS.z *= legssize
			HULL_MAXS.z *= legssize
			HULL_MAXS_DUCK.z *= legssize

		with ent
			if .SetHull
				cmins, cmaxs = \GetHull()
				\SetHull(HULL_MINS, HULL_MAXS) if cmins ~= HULL_MINS or cmaxs ~= HULL_MAXS

			if .SetHullDuck
				cmins, cmaxs = \GetHullDuck()
				\SetHullDuck(HULL_MINS, HULL_MAXS_DUCK) if cmins ~= HULL_MINS or cmaxs ~= HULL_MAXS_DUCK

			if .SetStepSize
				newsize = @@STEP_SIZE * size * @GetLegsModifier(1.2)
				\SetStepSize(newsize) if \GetStepSize() ~= newsize

	ModifyJumpHeight: (ent = @ent) =>
		return if CLIENT
		return if not @ent.SetJumpPower
		return if ent.__ppm2_modified_jump
		ent\SetJumpPower(ent\GetJumpPower() * PPM2.PONY_JUMP_MODIFIER)
		ent.__ppm2_modified_jump = true

	GetLegsModifier: (mult = 0.4) =>
		if @AllowResize()
			1 + (@GetLegsSize() - 1) * mult
		else
			1

	ModifyViewOffset: (ent = @ent) =>
		size = @GetPonySize()
		necksize = 1 + (@GetNeckSize() - 1) * .3
		legssize = @GetLegsModifier()

		PLAYER_VIEW_OFFSET = Vector(PPM2.PLAYER_VIEW_OFFSET)
		PLAYER_VIEW_OFFSET_DUCK = Vector(PPM2.PLAYER_VIEW_OFFSET_DUCK)

		if @AllowResize()
			PLAYER_VIEW_OFFSET *= size * necksize
			PLAYER_VIEW_OFFSET_DUCK *= size * necksize

			PLAYER_VIEW_OFFSET.z *= legssize
			PLAYER_VIEW_OFFSET_DUCK.z *= legssize

		ent\SetViewOffset(PLAYER_VIEW_OFFSET) if ent.SetViewOffset
		ent\SetViewOffsetDucked(PLAYER_VIEW_OFFSET_DUCK) if ent.SetViewOffsetDucked

	ModifyModelScale: (ent = @ent) =>
		return if not @AllowResize()
		-- https://github.com/Facepunch/garrysmod-issues/issues/2193
		if SERVER
			if not ent\IsPlayer()
				newscale = (@GetPonySize() * 100)\floor() / 100
				currscale = (ent\GetModelScale() * 100)\floor() / 100
				if currscale ~= newscale
					if type(ent) == 'NPC' or type(NPC) == 'NextBot'
						ent\SetPreventTransmit(ply, true) for _, ply in ipairs player.GetAll()
						ent\SetModelScale(newscale)
						ent\SetPreventTransmit(ply, false) for _, ply in ipairs player.GetAll()
					else
						ent\SetModelScale(newscale)
			return
		--return if not ent\IsClientsideEntity()
		return if ent.RenderOverride -- PAC3 and other stuff that can change this value
		mat = Matrix()
		mat\Scale(@@DEF_SCALE * @GetPonySize())
		ent\EnableMatrix('RenderMultiply', mat)

	ModifyScale: (ent = @ent) =>
		return if not IsValid(ent)
		return if not ent\IsPony()
		return if ent.Alive and not ent\Alive()

		if USE_NEW_HULL\GetBool()
			@ModifyHull(ent)
			@ModifyJumpHeight(ent)

		@ModifyViewOffset(ent)
		@ModifyModelScale(ent)

		if CLIENT and @lastPAC3BoneReset < RealTimeL()
			@ModifyNeck(ent)
			@ModifyLegs(ent)

	ModifyNeck: (ent = @ent) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		size = (@GetNeckSize() - 1) * 3
		vec = LVector(size, -size, 0)

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
		emptyLVector = LVector(0, 0, 0)

		with ent
			\ManipulateBonePosition2Safe(@NECK_BONE_1, vec + (boneAnimTable[@NECK_BONE_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@NECK_BONE_2, vec + (boneAnimTable[@NECK_BONE_2] or emptyLVector))
			\ManipulateBonePosition2Safe(@NECK_BONE_3, vec + (boneAnimTable[@NECK_BONE_3] or emptyLVector))
			\ManipulateBonePosition2Safe(@NECK_BONE_4, vec + (boneAnimTable[@NECK_BONE_4] or emptyLVector))

	ModifyLegs: (ent = @ent) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		realSizeModify = @GetLegsSize() - 1
		size = realSizeModify * 3

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
		emptyLVector = LVector(0, 0, 0)

		with ent
			\ManipulateBonePosition2Safe(@LEGS_BONE_ROOT, LVector(0, 0, size * 5) + \GetManipulateBonePosition2Safe(@LEGS_BONE_ROOT))

			\ManipulateBonePosition2Safe(@LEGS_FRONT_1, LVector(size * 1.5, 0, 0) + (boneAnimTable[@LEGS_FRONT_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_FRONT_2, LVector(size * 1.5, 0, 0) + (boneAnimTable[@LEGS_FRONT_2] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_FRONT_3, LVector(size, 0, 0) + (boneAnimTable[@LEGS_FRONT_3] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_FRONT_4, LVector(size, 0, 0) + (boneAnimTable[@LEGS_FRONT_4] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_FRONT_5, LVector(size, size, 0) + (boneAnimTable[@LEGS_FRONT_5] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_FRONT_6, LVector(size, size, 0) + (boneAnimTable[@LEGS_FRONT_6] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_1, LVector(size, -size * 0.5, 0) + (boneAnimTable[@LEGS_BEHIND_1_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_1_2, LVector(size, -size * 0.5, 0) + (boneAnimTable[@LEGS_BEHIND_1_2] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_1, LVector(size, 0, 0) + (boneAnimTable[@LEGS_BEHIND_2_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_2_2, LVector(size, 0, 0) + (boneAnimTable[@LEGS_BEHIND_2_2] or emptyLVector))

			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_1, LVector(size * 2, 0, 0) + (boneAnimTable[@LEGS_BEHIND_3_1] or emptyLVector))
			\ManipulateBonePosition2Safe(@LEGS_BEHIND_3_2, LVector(size * 2, 0, 0) + (boneAnimTable[@LEGS_BEHIND_3_2] or emptyLVector))

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

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

	@NECK_BONE_1 = 'LrigNeck1'
	@NECK_BONE_2 = 'LrigNeck2'
	@NECK_BONE_3 = 'LrigNeck3'
	@NECK_BONE_4 = 'LrigScull'

	@LEGS_FRONT_1 = 'Lrig_LEG_FL_FrontHoof'
	@LEGS_FRONT_2 = 'Lrig_LEG_FR_FrontHoof'

	@LEGS_FRONT_3 = 'Lrig_LEG_FL_Metacarpus'
	@LEGS_FRONT_4 = 'Lrig_LEG_FR_Metacarpus'

	@LEGS_FRONT_5 = 'Lrig_LEG_FL_Radius'
	@LEGS_FRONT_6 = 'Lrig_LEG_FR_Radius'

	@LEGS_BEHIND_1_1 = 'Lrig_LEG_BL_Tibia'
	@LEGS_BEHIND_1_2 = 'Lrig_LEG_BR_Tibia'

	@LEGS_BEHIND_2_1 = 'Lrig_LEG_BL_PhalanxPrima'
	@LEGS_BEHIND_2_2 = 'Lrig_LEG_BR_PhalanxPrima'

	@LEGS_BEHIND_3_1 = 'Lrig_LEG_BL_LargeCannon'
	@LEGS_BEHIND_3_2 = 'Lrig_LEG_BR_LargeCannon'

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

hook.Add 'PPM2.SetupBones', 'PPM2.Size', (ent, data) ->
	if sizes = data\GetSizeController()
		sizes.ent = ent
		sizes\ModifyNeck()
		sizes\ModifyLegs()
		sizes.lastPAC3BoneReset = RealTimeL() + 1

ppm2_sv_allow_resize = ->
	for _, ply in ipairs player.GetAll()
		if data = ply\GetPonyData()
			if scale = data\GetSizeController()
				scale\Reset()

cvars.AddChangeCallback 'ppm2_sv_allow_resize', ppm2_sv_allow_resize, 'PPM2.Scale'

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