default font, empty preload fix

default font, empty preload fix


Enable applications in development

Enable applications in development

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'smtp.zoho.eu'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'Nedelja informatike <ni@ni.mg.edu.rs>'
    smtp_from_newsletter 'Nedelja informatike <ni@ni.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'on'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'
    security_map_app_id_url 'http://127.0.0.1:19222/map_app_id'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled true

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

    smtp_server 'smtp.zoho.eu'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'Nedelja informatike <ni@ni.mg.edu.rs>'
    smtp_from_newsletter 'Nedelja informatike <ni@ni.mg.edu.rs>'

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'info'
    listen_address '127.0.0.1'
    secret secrets.app_secret
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'
    security_map_app_id_url 'http://10.131.29.36:19222/map_app_id'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled false

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'smtp.zoho.eu'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'Nedelja informatike <ni@ni.mg.edu.rs>'
    smtp_from_newsletter 'Nedelja informatike <ni@ni.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'on'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'
    security_map_app_id_url 'http://127.0.0.1:19222/map_app_id'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled false

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

    smtp_server 'smtp.zoho.eu'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'Nedelja informatike <ni@ni.mg.edu.rs>'
    smtp_from_newsletter 'Nedelja informatike <ni@ni.mg.edu.rs>'

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'info'
    listen_address '127.0.0.1'
    secret secrets.app_secret
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'
    security_map_app_id_url 'http://10.131.29.36:19222/map_app_id'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled false

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true


textutils: more utils!

textutils: more utils!

Former-commit-id: 9784e5b67fe6f7df38f2b353cae135a7de19b210 [formerly f8568039430d5cb19938a131723dfabacb101470]
Former-commit-id: bd0a559e0d0de5deb7e5a2ee4d417f39359f7445

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    say table.concat([n for n,k in pairs(ivar2.channels[destination].nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    new, n = string.gsub(arg, '\002', '')
    new, n = string.gsub(new, '\003[0-9]+(,[0-9]+)?', '')
    say(new)


use user-specified lookahead for wall detection (default: 30px)

require("lib/lgm/lgm")

export ^

class AI_Player extends Player
    new: (x, y, dirX, dirY, color, @lookahead=30) =>
        -- lookahead is to be specified in px
        super(x, y, dirX, dirY, color)

    update: (dt) =>
        -- TODO: remove magic numbers
        newX = @x + @dirX * @lookahead
        newY = @y + @dirY * @lookahead

        moveSegment = LGM.Segment(LGM.Vector(@x, @y), LGM.Vector(newX, newY))

        if intersectsWall(moveSegment)
            -- turn
            if @dirX ~= 0
                @dirY = if love.math.random() > 0.5 then 1 else -1
                @dirX = 0
            elseif @dirY ~= 0
                @dirX = if love.math.random() > 0.5 then 1 else -1
                @dirY = 0
            else
                error("Invalid dir state! (#{@dirX}, #{@dirY})")
            @hasTurned()

        super(dt)

require("lib/lgm/lgm")

export ^

class AI_Player extends Player
    new: (x, y, dirX, dirY, color) =>
        super(x, y, dirX, dirY, color)

    update: (dt) =>
        -- TODO: remove magic numbers
        newX = @x + @dirX * @speed * dt * 5
        newY = @y + @dirY * @speed * dt * 5

        moveSegment = LGM.Segment(LGM.Vector(@x, @y), LGM.Vector(newX, newY))

        if intersectsWall(moveSegment)
            -- turn
            if @dirX ~= 0
                @dirY = if love.math.random() > 0.5 then 1 else -1
                @dirX = 0
            elseif @dirY ~= 0
                @dirX = if love.math.random() > 0.5 then 1 else -1
                @dirY = 0
            else
                error("Invalid dir state! (#{@dirX}, #{@dirY})")
            @hasTurned()

        super(dt)


quiz-results: print start and stop times

import Widget from require "lapis.html"

Helpers = require('views.helpers')

quizs = require('quiz.all').available

class QuizResults extends Widget
  @include Helpers

  content: =>
    h2 @_("Quiz ") .. @quiz.name
    h2 @_("Student ") .. @quiz.user
    if @prep
      url = @url_for("prep-quizs-of", {user: @quiz.user})
      a href: url, ->
        text @_("Other quizes by ") .. @quiz.user
    h2 @quiz.right_answers .. ' / ' .. @quiz.tasks
    h2 @quiz.created_at .. ' - ' .. @quiz.updated_at
    if quizs[@quiz.name]
      element 'table', ->
        element 'tr', ->
          element 'td', ->
            text @_('Go through this test again:')
          element 'td', ->
            @new_test_button @quiz.name
    raw '
'
    for task in *(@quiz\all_tasks!)
      @task_table task
      raw '
'

import Widget from require "lapis.html"

Helpers = require('views.helpers')

quizs = require('quiz.all').available

class QuizResults extends Widget
  @include Helpers

  content: =>
    h2 @_("Quiz ") .. @quiz.name
    h2 @_("Student ") .. @quiz.user
    if @prep
      url = @url_for("prep-quizs-of", {user: @quiz.user})
      a href: url, ->
        text @_("Other quizes by ") .. @quiz.user
    h2 @quiz.right_answers .. ' / ' .. @quiz.tasks
    if quizs[@quiz.name]
      element 'table', ->
        element 'tr', ->
          element 'td', ->
            text @_('Go through this test again:')
          element 'td', ->
            @new_test_button @quiz.name
    raw '
'
    for task in *(@quiz\all_tasks!)
      @task_table task
      raw '
'


begun modelling how to structure cipher modes

return (path) ->
    return {
        keyschedule: (require path .. '/aes/keyschedule')(path)
        steps: (require path .. '/aes/steps')(path)
        encrypt: (require path .. '/aes/encrypt')(path)
        decrypt: (require path .. '/aes/decrypt')(path)
        encryptdata: (require path .. '/aes/encryptdata')(path)
        ecb: nil
        cbc: nil
        otherciphers: nil
    }

return (path) ->
    return {
        keyschedule: (require path .. '/aes/keyschedule')(path)
        steps: (require path .. '/aes/steps')(path)
        encrypt: (require path .. '/aes/encrypt')(path)
        decrypt: (require path .. '/aes/decrypt')(path)
    }


Start commenting and implementing Entity

require "LovePunk.LP"
require "LovePunk.Mask"
require "LovePunk.Tweener"

export ^

class Entity extends Tweener
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 0
		@__moveX, @__moveY = 0, 0
		-- The collision type, used for collision checking.
		@type = ""
		@name = ""

		@__hitbox = Mask!
		@__point = LP.point
		@__camera = LP.point2

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@__graphic = graphic if graphic != nil

		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@__mask = mask if mask != nil

		@__hitbox\assignTo @

	x: (value) =>
		if value != nil
			@__x = value
			return @__x

		if @followCamera
			@__x + LP.camera.x
		else
			@__x

	y: (value) =>
		if value != nil
			@__y = value
			return @__y
		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- The World object is deprecated for FlashPunk-like compatibility
	world: (value) =>
		@scene = value if value != nil
		@scene

	-- Override this, called when the Entity is added to a Scene.
	added: =>
	-- Override this, called when the Entity is removed from a Scene.
	removed: =>
	-- Updates the Entity.
	update: =>
	-- Renders the Entity. If you override this for special behaviour,
	-- remember to call super.render() to render the Entity's graphic.
	render: =>
		if @__graphic != nil and @__graphic.visable
			if @__graphic.relative
				@__point.x = @x!
				@__point.y = @y!
			else
				@__point.x, @__point.y = 0, 0
				@__camera.x = if @__scene == nil then LP.camera.x else @__scene.camera.x
				@__camera.y = if @__scene == nil then LP.camera.y else @__scene.camera.y
				@__graphic\render (if @__renderTarget != nil then @__renderTarget else LP.buffer), @__point, @__camera

	collide: (type, x, y) =>
	collideTypes: (types, x, y) =>
	collideWith: (e, x, y) =>
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
	collidePoint: (x, y, pX, pY) =>
	collideInto: (type, x, y, array) =>
	collideTypesInto: (types, x, y, array) =>
	addGraphic: (g) =>
	setHitbox: (width, height, originX, originY) =>
	setHitboxTo: (o) =>
	setOrigin: (x, y) =>
	centerOrigin: =>
	distanceFrom: (e, useHitboxes) =>
	distanceToPoint: (px, py, useHitbox) =>
	distanceToRect: (rx, ry, rwidth, rheight) =>
	toString: =>
	moveBy: (x, y, solidType, sweep) =>
	moveTo: (x, y, solidType, sweep) =>
	moveTowards: (x, y, amount, solidType, sweep) =>
	moveAtAngle: (angle, amount, solidType, sweep) =>
	moveCollideX: (e) =>
	moveCollideY: (e) =>
	clampHorizontal: (left, right, padding) =>
	clampVertical: (top, bottom, padding) =>
	centerGraphicInRect: =>

require "LovePunk.LP"
require "LovePunk.Mask"
require "LovePunk.Tweener"

export ^

class Entity extends Tweener
	new: (x = 0, y = 0, graphic = nil, mask = nil) =>
		super!
		-- If the Entity should render.
		@visible = true
		-- If the Entity should respond to collision checks.
		@collidable = true
		-- If the entity should follow the camera.
		@followCamera = false
		-- X position of the Entity in the Scene.
		@__x = x
		-- Y position of the Entity in the Scene.
		@__y = y
		-- X origin of the Entity's hitbox.
		@originX = 0
		-- Y origin of the Entity's hitbox.
		@originY = 0
		-- Width of the Entity's hitbox.
		@width = 0
		-- Height of the Entity's hitbox.
		@height = 0

		-- The bottommost position of the Entity's hitbox.
		@bottom = 0
		-- The center x position of the Entity's hitbox.
		@centerX = 0
		-- The center y position of the Entity's hitbox.
		@centerY = 0
		@__moveX, @__moveY = 0, 0
		-- The collision type, used for collision checking.
		@type = ""
		@name = ""

		@__hitbox = Mask!
		@__point = LP.point
		@__camera = LP.point2

		-- The rendering layer of this Entity. Higher layers are rendered first.
		@layer = LP.BASELAYER!

		-- Graphical component to render to the screen.
		@graphic = graphic if graphic != nil

		-- An optional Mask component, used for specialized collision. If this is not assigned, collision checks will use the Entity's hitbox by default.
		@mask = mask if mask != nil

		@__hitbox\assignTo @

	x: (value) =>
		if value != nil
			@__x = value
			return @__x

		if @followCamera
			@__x + LP.camera.x
		else
			@__x

	y: (value) =>
		if value != nil
			@__y = value
			return @__y
		if @followCamera
			@__y + LP.camera.y
		else
			@__y

	-- The World object is deprecated for FlashPunk-like compatibility
	world: (value) =>
		@scene = value if value != nil
		@scene

	added: =>
	removed: =>
	update: =>
	render: =>
	collide: (type, x, y) =>
	collideTypes: (types, x, y) =>
	collideWith: (e, x, y) =>
	collideRect: (x, y, rX, rY, rWidth, rHeight) =>
	collidePoint: (x, y, pX, pY) =>
	collideInto: (type, x, y, array) =>
	collideTypesInto: (types, x, y, array) =>
	addGraphic: (g) =>
	setHitbox: (width, height, originX, originY) =>
	setHitboxTo: (o) =>
	setOrigin: (x, y) =>
	centerOrigin: =>
	distanceFrom: (e, useHitboxes) =>
	distanceToPoint: (px, py, useHitbox) =>
	distanceToRect: (rx, ry, rwidth, rheight) =>
	toString: =>
	moveBy: (x, y, solidType, sweep) =>
	moveTo: (x, y, solidType, sweep) =>
	moveTowards: (x, y, amount, solidType, sweep) =>
	moveAtAngle: (angle, amount, solidType, sweep) =>
	moveCollideX: (e) =>
	moveCollideY: (e) =>
	clampHorizontal: (left, right, padding) =>
	clampVertical: (top, bottom, padding) =>
	centerGraphicInRect: =>


list: implement an apropos cmd to find patterns and modules

listModules = (source, destination, arg) =>
  out = {}
  for moduleName, moduleTable in next, @Events!['PRIVMSG']
    unless @IsModuleDisabled moduleName, destination
      table.insert out, moduleName
  if #out > 0
    say "Modules: %s", table.concat(out, ' ')

listPatterns = (source, destination, moduleName) =>
  moduleTable = @Events!['PRIVMSG'][moduleName]
  unless moduleTable
    return

  if @IsModuleDisabled moduleName, destination
    return

  out = {}
  for pattern, callback in next, moduleTable
    patt = @ChannelCommandPattern(pattern, moduleName, destination)
    -- Make them prettier
    patt = patt\gsub('%^%%p', '!')
    patt = patt\gsub('%$$', '')
    patt = patt\gsub('^%^', '')
    table.insert out, patt

  if #out > 0
    say "%s patterns:\n %s", moduleName, table.concat(out, ',\n ')

Apropos = (s, d, what) =>
  moduleTable = @Events!['PRIVMSG']
  unless moduleTable
    return

  out = {}
  for moduleName, moduleTable in next, moduleTable
    if @IsModuleDisabled moduleName, destination
      return

    for pattern, callback in next, moduleTable
      if type(pattern) == 'string'
        patt = @ChannelCommandPattern(pattern, moduleName, destination)
        if patt and type(patt) == 'string' and patt\match what
          out[#out+1] = "#{ivar2.util.bold moduleName} module has pattern #{ivar2.util.italic patt}"
  say table.concat(out, ',\n')

PRIVMSG:
  '^%plist$': listModules
  '^%plist (%w+)$': listPatterns
  '^%phelp$': listModules
  '^%phelp (%w+)$': listPatterns
  '^%papropos (.*)$': Apropos

listModules = (source, destination, arg) =>
  out = {}
  for moduleName, moduleTable in next, @Events!['PRIVMSG']
    unless @IsModuleDisabled moduleName, destination
      table.insert out, moduleName
  if #out > 0
    say "Modules: %s", table.concat(out, ' ')

listPatterns = (source, destination, moduleName) =>
  moduleTable = @Events!['PRIVMSG'][moduleName]
  unless moduleTable
    return

  if @IsModuleDisabled moduleName, destination
    return

  out = {}
  for pattern, callback in next, moduleTable
    patt = @ChannelCommandPattern(pattern, moduleName, destination)
    -- Make them prettier
    patt = patt\gsub('%^%%p', '!')
    patt = patt\gsub('%$$', '')
    patt = patt\gsub('^%^', '')
    table.insert out, patt

  if #out > 0
    say "%s patterns:\n %s", moduleName, table.concat(out, ',\n ')

PRIVMSG:
  '^%plist$': listModules
  '^%plist (%w+)$': listPatterns
  '^%phelp$': listModules
  '^%phelp (%w+)$': listPatterns


Refactor submit-application and check class

Removed some boilerplate to make for more pleasant error reporting
experience. Also the code now checks if the submitted class was in the
list.

content = require 'content'
csrf = require 'lapis.csrf'
import to_json from require 'lapis.util'

class SubmitApplication
    submit: (params, model) =>
        errors = { }

        tasks = { }
        tasklen = #model.tasks
        local tid
        for k, v in pairs params
            if tid = k\match "^tasks%[(%d+)%]$"
                tid = tonumber tid
                if tid >= 1 and tid <= tasklen
                    table.insert tasks, tid
                else
                    errors.bad_request = true
        if not errors.bad_request
            errors.bad_request = true
            for c in *model.form.classes
                if c == params.class
                    errors.bad_request = nil
                    break

        with errors
            .missing_name = true if not params.name
            .missing_email = true if not params.email
            .invalid_email = true if (type params.email) != 'string' or not params.email\match "[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w%w?%w?"
            .invalid_name = true if (type params.name) != 'string' or not params.name\find ' '
            .task_number_mismatch = true if #tasks < 2

        err_array = { }
        for k, _ in pairs errors
            table.insert err_array, k

        return nil, err_array if #err_array > 0
        true

content = require 'content'
import to_json from require 'lapis.util'

class SubmitApplication
    submit: (params, model) =>
        errors = { }

        print to_json params
        tasks = { }
        tasklen = #model.tasks
        for k, v in pairs params
            if tid = k\match "^tasks%[(%d+)%]$"
                tid = tonumber tid
                table.insert tasks, tid if tid >= 1 and tid <= tasklen else table.insert errors, 'bad_request'

        table.insert errors, 'missing_name' if not params.name
        table.insert errors, 'missing_email' if not params.email
        table.insert errors, 'invalid_email' if (type params.email) != 'string' or not params.email\match "[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w%w?%w?"
        table.insert errors, 'invalid_name' if (type params.name) != 'string' or not params.name\find ' '
        table.insert errors, 'task_number_mismatch' if #tasks < 2

        nil, errors if #errors > 0 else true


Fixed flickering bones

Fixed flickering bones

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

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

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

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = 1
    @BODYGROUP_HORN = 2
    @BODYGROUP_WINGS = 3
    @BODYGROUP_MANE_UPPER = 4
    @BODYGROUP_MANE_LOWER = 5
    @BODYGROUP_TAIL = 6
    @BODYGROUP_CMARK = 7
    @BODYGROUP_EYELASH = 8

    @NEXT_OBJ_ID = 0

    @COOLDOWN_TIME = 5
    @COOLDOWN_MAX_COUNT = 4

    for i = 1, 7
        @['BONE_MANE_' .. i] = 29 + i

    @BONE_TAIL_1 = 38
    @BONE_TAIL_2 = 39
    @BONE_TAIL_3 = 40

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @SocksModelUpdateCooldown = 0
        @SocksModelUpdateCount = 0
        @lastPAC3BoneReset = 0
        PPM2.DebugPrint('Created new bodygroups controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)

    UpdateCooldowns: =>
        if CLIENT
            @SocksModelUpdateCooldown = 0
            @SocksModelUpdateCount = 0
        rTime = RealTime()
        if @SocksModelUpdateCooldown < rTime
            @SocksModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @SocksModelUpdateCount = 0

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    CreateSocksModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @socksModel if IsValid(@socksModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isSocks and ent.manePlayer == @ent
                @socksModel = ent
                @GetData()\SetSocksModel(@socksModel)
                PPM2.DebugPrint('Resuing ', @socksModel, ' as socks model for ', @ent)
                return ent

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

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

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

        PPM2.DebugPrint('Creating new socks model for ', @ent, ' as ', @socksModel)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@socksModel)
                @GetData()\SetSocksModel(@socksModel)
                @ent\SetNWEntity('PPM2.SocksModel', @socksModel) if IsValid(@ent)
        else
            @GetData()\SetSocksModel(@socksModel)

        return @socksModel
    CreateSocksModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        return NULL if not IsValid(@socksModel)
        @socksModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @socksModel\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        @GetData()\SetSocksModel(@socksModel)
        return @socksModel

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        return unless IsValid(targetEnt)
        socks = @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
        if IsValid(socks)
            socks\SetParent(targetEnt)
            socks\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    GetSocks: => @socksModel or NULL

    ApplyRace: =>
        return unless @isValid
        return NULL if not IsValid(@ent)
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)

    ResetTail: =>
        return if not CLIENT
        @ent\ManipulateBoneScale(@@BONE_TAIL_1, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_2, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, Vector(1, 1, 1))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_1, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_2, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_3, Angle(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_1, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, Vector(0, 0, 0))

    ResetMane: =>
        return if not CLIENT
        vec1, ang, vec2 = Vector(1, 1, 1), Angle(0, 0, 0), Vector(0, 0, 0)
        for i = 1, 7
            @ent\ManipulateBoneScale(@@['BONE_MANE_' .. i], vec1)
            @ent\ManipulateBoneAngles(@@['BONE_MANE_' .. i], ang)
            @ent\ManipulateBonePosition(@@['BONE_MANE_' .. i], vec2)

    ResetBodygroups: =>
        return unless @isValid
        return unless IsValid(@ent)
        return unless @ent\GetBodyGroups()
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
        if @lastPAC3BoneReset < RealTime()
            @ResetTail()
            @ResetMane()
    Reset: => @ResetBodygroups()
    RemoveModels: =>
        @socksModel\Remove() if IsValid(@socksModel)

    UpdateTailSize: =>
        return if not CLIENT
        size = @GetData()\GetTailSize()
        size *= @GetData()\GetPonySize() if not @ent\IsRagdoll()
        vec = Vector(1, 1, 1)
        vecTail = vec * size
        vecTailPos = Vector((size - 1) * 8, 0, 0)

        @ent\ManipulateBoneScale(@@BONE_TAIL_1, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_2, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, vecTail)

        --@ent\ManipulateBonePosition(@@BONE_TAIL_1, vecTail)
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, vecTailPos)
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, vecTailPos)

    UpdateManeSize: =>
        return if not CLIENT
        return if @ent\IsRagdoll()
        size = @GetData()\GetPonySize()
        vecMane = Vector(1, 1, 1) * size

        @ent\ManipulateBoneScale(@@BONE_MANE_1, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_2, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_3, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_4, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_5, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_6, vecMane)

        @ent\ManipulateBonePosition(@@BONE_MANE_1, Vector(-(size - 1) * 4, (1 - size) * 6, 0))
        @ent\ManipulateBonePosition(@@BONE_MANE_2, Vector(-(size - 1) * 4, (size - 1) * 2, 1))
        @ent\ManipulateBonePosition(@@BONE_MANE_3, Vector((size - 1) * 2, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_MANE_4, Vector(1 - size, (1 - size) * 4, 1 - size))
        @ent\ManipulateBonePosition(@@BONE_MANE_5, Vector((size - 1) * 4, (1 - size) * 2, (size - 1) * 3))

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
        @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
        @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
        if @lastPAC3BoneReset < RealTime()
            @UpdateTailSize()
            @UpdateManeSize()
        @ApplyRace()
        @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return if not @ent\IsPony()
        @SlowUpdate(createModels)

    Remove: =>
        @RemoveModels()
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        switch state\GetKey()
            when 'ManeType'
                @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
            when 'ManeTypeLower'
                @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
            when 'TailType'
                @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
            when 'TailSize', 'PonySize'
                @UpdateTailSize()
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
            when 'Race'
                @ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

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

    ApplyRace: =>
        return unless @isValid
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 2)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 3)

--
-- 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	Tail01
-- 43	Tail02
-- 44	Tail03
--

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

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

    @BONE_TAIL_1 = 42
    @BONE_TAIL_2 = 43
    @BONE_TAIL_3 = 44

    @BONE_MANE_1 = 40
    @BONE_MANE_2 = 33
    @BONE_MANE_3 = 34
    @BONE_MANE_4 = 36
    @BONE_MANE_5 = 37
    @BONE_MANE_6 = 38
    @BONE_MANE_7 = 39

    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    new: (...) =>
        super(...)
        @UpperManeModelUpdateCooldown = 0
        @UpperManeModelUpdateCount = 0
        @LowerManeModelUpdateCooldown = 0
        @LowerManeModelUpdateCount = 0
        @TailModelUpdateCooldown = 0
        @TailModelUpdateCount = 0

    UpdateCooldowns: =>
        super()
        if CLIENT
            @UpperManeModelUpdateCooldown = 0
            @UpperManeModelUpdateCount = 0
            @LowerManeModelUpdateCooldown = 0
            @LowerManeModelUpdateCount = 0
            @TailModelUpdateCooldown = 0
            @TailModelUpdateCount = 0
        rTime = RealTime()
        if @UpperManeModelUpdateCooldown < rTime
            @UpperManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @UpperManeModelUpdateCount = 0
        if @LowerManeModelUpdateCooldown < rTime
            @LowerManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @LowerManeModelUpdateCount = 0
        if @TailModelUpdateCooldown < rTime
            @TailModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @TailModelUpdateCount = 0

    CreateUpperManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelUP if IsValid(@maneModelUP)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.upperMane and ent.manePlayer == @ent
                @maneModelUP = ent
                @GetData()\SetUpperManeModel(@maneModelUP)
                PPM2.DebugPrint('Resuing ', @maneModelUP, ' as upper mane model for ', @ent)
                return ent

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

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"

        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = true
            .upperMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new upper mane model for ', @ent, ' as ', @maneModelUP)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelLower if IsValid(@maneModelLower)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.lowerMane and ent.manePlayer == @ent
                @maneModelLower = ent
                @GetData()\SetLowerManeModel(@maneModelLower)
                PPM2.DebugPrint('Resuing ', @maneModelLower, ' as lower mane model for ', @ent)
                return ent

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

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"

        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = true
            .lowerMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetNoDraw(true) if CLIENT
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new lower mane model for ', @ent, ' as ', @maneModelLower)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @tailModel if IsValid(@tailModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isTail and ent.manePlayer == @ent
                @tailModel = ent
                @GetData()\SetTailModel(@tailModel)
                PPM2.DebugPrint('Resuing ', @tailModel, ' as tail model for ', @ent)
                return ent

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

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"

        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = true
            .isTail = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new tail model for ', @ent, ' as ', @tailModel)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@tailModel)
                @GetData()\SetTailModel(@tailModel)
                @ent\SetNWEntity('PPM2.TailModel', @tailModel) if IsValid(@ent)
        else
            @GetData()\SetTailModel(@tailModel)

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        @GetData()\SetUpperManeModel(@maneModelUP) if IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        @GetData()\SetLowerManeModel(@maneModelLower) if IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        @GetData()\SetTailModel(@tailModel) if IsValid(@tailModel)
        return @tailModel

    GetUpperMane: => @maneModelUP or NULL
    GetLowerMane: => @maneModelLower or NULL
    GetTail: => @tailModel or NULL

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        super(targetEnt)
        return unless IsValid(targetEnt)
        maneUpper = @CreateUpperManeModelIfNotExists()
        maneLower = @CreateLowerManeModelIfNotExists()
        tail = @CreateTailModelIfNotExists()
        if IsValid(maneUpper)
            maneUpper\SetParent(targetEnt)
            maneUpper\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(maneLower)
            maneLower\SetParent(targetEnt)
            maneLower\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(tail)
            tail\SetParent(targetEnt)
            tail\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    UpdateUpperMane: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        return NULL if not IsValid(@maneModelUP)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model) if model ~= @maneModelUP\GetModel()
        @maneModelUP\SetBodygroup(1, bodygroupID) if @maneModelUP\GetBodygroup(1) ~= bodygroupID
        @maneModelUP\SetParent(@ent\GetEntity()) if @maneModelUP\GetParent() ~= @ent and IsValid(@ent)
        @GetData()\SetUpperManeModel(@maneModelUP)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        return NULL if not IsValid(@maneModelLower)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model) if model ~= @maneModelLower\GetModel()
        @maneModelLower\SetBodygroup(1, bodygroupID) if @maneModelLower\GetBodygroup(1) ~= bodygroupID
        @maneModelLower\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetLowerManeModel(@maneModelLower)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        return NULL if not IsValid(@tailModel)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model) if model ~= @tailModel\GetModel()
        @tailModel\SetBodygroup(1, bodygroupID) if @tailModel\GetBodygroup(1) ~= bodygroupID
        @tailModel\SetModelScale(@GetData()\GetTailSize())
        @tailModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetTailModel(@tailModel)
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_MALE_2 = 35
    @FLEX_ID_MALE_BODY = 36
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

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

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0

        if @GetData()\GetNewMuzzle()
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        else
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)

        @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY,     maleModifier * @GetData()\GetMaleBuff())

        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS,         @GetData()\GetFangs() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GetData()\GetClawTeeth() and 1 or 0)

        if @lastPAC3BoneReset < RealTime()
            @UpdateTailSize()
            @UpdateManeSize()

        @ApplyRace()
        if createModels
            @UpdateUpperMane()
            @UpdateLowerMane()
            @UpdateTailModel()
            @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    RemoveModels: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return @RemoveModels() if not @ent\IsPony()
        @SlowUpdate(createModels)

    ApplyRace: =>
        return unless @isValid
        return if not IsValid(@ent)
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())

    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'NewMuzzle'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'BatPonyEars'
                @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
            when 'Fangs'
                @ent\SetFlexWeight(@@FLEX_ID_FANGS, @GetData()\GetFangs() and 1 or 0)
            when 'ClawTeeth'
                @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GetData()\GetClawTeeth() and 1 or 0)
            when 'ManeTypeNew'
                @UpdateUpperMane() if CLIENT
            when 'ManeTypeLowerNew'
                @UpdateLowerMane() if CLIENT
            when 'TailSize', 'TailTypeNew'
                return if SERVER
                @UpdateTailModel()
                @UpdateTailSize()
            when 'PonySize'
                return if SERVER
                @UpdateTailSize()
            when 'Race'
                @ApplyRace()
            when 'WingsType'
                @ApplyRace()
            when 'MaleBuff'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'SocksAsModel'
                return if SERVER
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)

if CLIENT
    hook.Add 'PPM2_PACResetBones', 'PPM2.Bodygroups', (ent, data) ->
        if bodygroup = data\GetBodygroupController()
            bodygroup.ent = ent
            bodygroup\UpdateTailSize()
            bodygroup\UpdateManeSize()
            bodygroup.lastPAC3BoneReset = RealTime() + 1

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

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

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

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

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

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = 1
    @BODYGROUP_HORN = 2
    @BODYGROUP_WINGS = 3
    @BODYGROUP_MANE_UPPER = 4
    @BODYGROUP_MANE_LOWER = 5
    @BODYGROUP_TAIL = 6
    @BODYGROUP_CMARK = 7
    @BODYGROUP_EYELASH = 8

    @NEXT_OBJ_ID = 0

    @COOLDOWN_TIME = 5
    @COOLDOWN_MAX_COUNT = 4

    for i = 1, 7
        @['BONE_MANE_' .. i] = 29 + i

    @BONE_TAIL_1 = 38
    @BONE_TAIL_2 = 39
    @BONE_TAIL_3 = 40

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @SocksModelUpdateCooldown = 0
        @SocksModelUpdateCount = 0
        PPM2.DebugPrint('Created new bodygroups controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)

    UpdateCooldowns: =>
        if CLIENT
            @SocksModelUpdateCooldown = 0
            @SocksModelUpdateCount = 0
        rTime = RealTime()
        if @SocksModelUpdateCooldown < rTime
            @SocksModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @SocksModelUpdateCount = 0

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    CreateSocksModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @socksModel if IsValid(@socksModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isSocks and ent.manePlayer == @ent
                @socksModel = ent
                @GetData()\SetSocksModel(@socksModel)
                PPM2.DebugPrint('Resuing ', @socksModel, ' as socks model for ', @ent)
                return ent

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

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

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

        PPM2.DebugPrint('Creating new socks model for ', @ent, ' as ', @socksModel)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@socksModel)
                @GetData()\SetSocksModel(@socksModel)
                @ent\SetNWEntity('PPM2.SocksModel', @socksModel) if IsValid(@ent)
        else
            @GetData()\SetSocksModel(@socksModel)

        return @socksModel
    CreateSocksModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        return NULL if not IsValid(@socksModel)
        @socksModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @socksModel\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        @GetData()\SetSocksModel(@socksModel)
        return @socksModel

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        return unless IsValid(targetEnt)
        socks = @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
        if IsValid(socks)
            socks\SetParent(targetEnt)
            socks\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    GetSocks: => @socksModel or NULL

    ApplyRace: =>
        return unless @isValid
        return NULL if not IsValid(@ent)
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)

    ResetTail: =>
        return if not CLIENT
        @ent\ManipulateBoneScale(@@BONE_TAIL_1, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_2, Vector(1, 1, 1))
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, Vector(1, 1, 1))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_1, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_2, Angle(0, 0, 0))
        @ent\ManipulateBoneAngles(@@BONE_TAIL_3, Angle(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_1, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, Vector(0, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, Vector(0, 0, 0))

    ResetMane: =>
        return if not CLIENT
        vec1, ang, vec2 = Vector(1, 1, 1), Angle(0, 0, 0), Vector(0, 0, 0)
        for i = 1, 7
            @ent\ManipulateBoneScale(@@['BONE_MANE_' .. i], vec1)
            @ent\ManipulateBoneAngles(@@['BONE_MANE_' .. i], ang)
            @ent\ManipulateBonePosition(@@['BONE_MANE_' .. i], vec2)

    ResetBodygroups: =>
        return unless @isValid
        return unless IsValid(@ent)
        return unless @ent\GetBodyGroups()
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
        @ResetTail()
        @ResetMane()
    Reset: => @ResetBodygroups()
    RemoveModels: =>
        @socksModel\Remove() if IsValid(@socksModel)

    UpdateTailSize: =>
        return if not CLIENT
        size = @GetData()\GetTailSize()
        size *= @GetData()\GetPonySize() if not @ent\IsRagdoll()
        vec = Vector(1, 1, 1)
        vecTail = vec * size
        vecTailPos = Vector((size - 1) * 8, 0, 0)

        @ent\ManipulateBoneScale(@@BONE_TAIL_1, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_2, vecTail)
        @ent\ManipulateBoneScale(@@BONE_TAIL_3, vecTail)

        --@ent\ManipulateBonePosition(@@BONE_TAIL_1, vecTail)
        @ent\ManipulateBonePosition(@@BONE_TAIL_2, vecTailPos)
        @ent\ManipulateBonePosition(@@BONE_TAIL_3, vecTailPos)

    UpdateManeSize: =>
        return if not CLIENT
        return if @ent\IsRagdoll()
        size = @GetData()\GetPonySize()
        vecMane = Vector(1, 1, 1) * size

        @ent\ManipulateBoneScale(@@BONE_MANE_1, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_2, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_3, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_4, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_5, vecMane)
        @ent\ManipulateBoneScale(@@BONE_MANE_6, vecMane)

        @ent\ManipulateBonePosition(@@BONE_MANE_1, Vector(-(size - 1) * 4, (1 - size) * 6, 0))
        @ent\ManipulateBonePosition(@@BONE_MANE_2, Vector(-(size - 1) * 4, (size - 1) * 2, 1))
        @ent\ManipulateBonePosition(@@BONE_MANE_3, Vector((size - 1) * 2, 0, 0))
        @ent\ManipulateBonePosition(@@BONE_MANE_4, Vector(1 - size, (1 - size) * 4, 1 - size))
        @ent\ManipulateBonePosition(@@BONE_MANE_5, Vector((size - 1) * 4, (1 - size) * 2, (size - 1) * 3))

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
        @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
        @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
        @UpdateTailSize()
        @UpdateManeSize()
        @ApplyRace()
        @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return if not @ent\IsPony()
        @SlowUpdate(createModels)

    Remove: =>
        @RemoveModels()
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        switch state\GetKey()
            when 'ManeType'
                @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
            when 'ManeTypeLower'
                @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
            when 'TailType'
                @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
            when 'TailSize', 'PonySize'
                @UpdateTailSize()
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
            when 'Race'
                @ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

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

    ApplyRace: =>
        return unless @isValid
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 2)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 3)

--
-- 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	Tail01
-- 43	Tail02
-- 44	Tail03
--

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

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

    @BONE_TAIL_1 = 42
    @BONE_TAIL_2 = 43
    @BONE_TAIL_3 = 44

    @BONE_MANE_1 = 40
    @BONE_MANE_2 = 33
    @BONE_MANE_3 = 34
    @BONE_MANE_4 = 36
    @BONE_MANE_5 = 37
    @BONE_MANE_6 = 38
    @BONE_MANE_7 = 39

    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    new: (...) =>
        super(...)
        @UpperManeModelUpdateCooldown = 0
        @UpperManeModelUpdateCount = 0
        @LowerManeModelUpdateCooldown = 0
        @LowerManeModelUpdateCount = 0
        @TailModelUpdateCooldown = 0
        @TailModelUpdateCount = 0

    UpdateCooldowns: =>
        super()
        if CLIENT
            @UpperManeModelUpdateCooldown = 0
            @UpperManeModelUpdateCount = 0
            @LowerManeModelUpdateCooldown = 0
            @LowerManeModelUpdateCount = 0
            @TailModelUpdateCooldown = 0
            @TailModelUpdateCount = 0
        rTime = RealTime()
        if @UpperManeModelUpdateCooldown < rTime
            @UpperManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @UpperManeModelUpdateCount = 0
        if @LowerManeModelUpdateCooldown < rTime
            @LowerManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @LowerManeModelUpdateCount = 0
        if @TailModelUpdateCooldown < rTime
            @TailModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @TailModelUpdateCount = 0

    CreateUpperManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelUP if IsValid(@maneModelUP)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.upperMane and ent.manePlayer == @ent
                @maneModelUP = ent
                @GetData()\SetUpperManeModel(@maneModelUP)
                PPM2.DebugPrint('Resuing ', @maneModelUP, ' as upper mane model for ', @ent)
                return ent

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

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"

        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = true
            .upperMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new upper mane model for ', @ent, ' as ', @maneModelUP)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelLower if IsValid(@maneModelLower)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.lowerMane and ent.manePlayer == @ent
                @maneModelLower = ent
                @GetData()\SetLowerManeModel(@maneModelLower)
                PPM2.DebugPrint('Resuing ', @maneModelLower, ' as lower mane model for ', @ent)
                return ent

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

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"

        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = true
            .lowerMane = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetNoDraw(true) if CLIENT
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new lower mane model for ', @ent, ' as ', @maneModelLower)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        return NULL unless @ent\IsPony()
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @tailModel if IsValid(@tailModel)
        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isTail and ent.manePlayer == @ent
                @tailModel = ent
                @GetData()\SetTailModel(@tailModel)
                PPM2.DebugPrint('Resuing ', @tailModel, ' as tail model for ', @ent)
                return ent

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

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"

        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = true
            .isTail = true
            .manePlayer = @ent
            \DrawShadow(true) if CLIENT
            \SetModel(model)
            \SetPos(@ent\EyePos())
            \Spawn()
            \Activate()
            \SetNoDraw(true) if CLIENT
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent\GetEntity())
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        PPM2.DebugPrint('Creating new tail model for ', @ent, ' as ', @tailModel)

        if SERVER
            timer.Simple .5, ->
                return unless @isValid
                return unless IsValid(@tailModel)
                @GetData()\SetTailModel(@tailModel)
                @ent\SetNWEntity('PPM2.TailModel', @tailModel) if IsValid(@ent)
        else
            @GetData()\SetTailModel(@tailModel)

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        @GetData()\SetUpperManeModel(@maneModelUP) if IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        @GetData()\SetLowerManeModel(@maneModelLower) if IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        @GetData()\SetTailModel(@tailModel) if IsValid(@tailModel)
        return @tailModel

    GetUpperMane: => @maneModelUP or NULL
    GetLowerMane: => @maneModelLower or NULL
    GetTail: => @tailModel or NULL

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        super(targetEnt)
        return unless IsValid(targetEnt)
        maneUpper = @CreateUpperManeModelIfNotExists()
        maneLower = @CreateLowerManeModelIfNotExists()
        tail = @CreateTailModelIfNotExists()
        if IsValid(maneUpper)
            maneUpper\SetParent(targetEnt)
            maneUpper\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(maneLower)
            maneLower\SetParent(targetEnt)
            maneLower\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(tail)
            tail\SetParent(targetEnt)
            tail\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    UpdateUpperMane: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        return NULL if not IsValid(@maneModelUP)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model) if model ~= @maneModelUP\GetModel()
        @maneModelUP\SetBodygroup(1, bodygroupID) if @maneModelUP\GetBodygroup(1) ~= bodygroupID
        @maneModelUP\SetParent(@ent\GetEntity()) if @maneModelUP\GetParent() ~= @ent and IsValid(@ent)
        @GetData()\SetUpperManeModel(@maneModelUP)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        return NULL if not IsValid(@maneModelLower)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model) if model ~= @maneModelLower\GetModel()
        @maneModelLower\SetBodygroup(1, bodygroupID) if @maneModelLower\GetBodygroup(1) ~= bodygroupID
        @maneModelLower\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetLowerManeModel(@maneModelLower)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        return NULL if not IsValid(@ent)
        --return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        return NULL if not IsValid(@tailModel)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model) if model ~= @tailModel\GetModel()
        @tailModel\SetBodygroup(1, bodygroupID) if @tailModel\GetBodygroup(1) ~= bodygroupID
        @tailModel\SetModelScale(@GetData()\GetTailSize())
        @tailModel\SetParent(@ent\GetEntity()) if IsValid(@ent)
        @GetData()\SetTailModel(@tailModel)
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_MALE_2 = 35
    @FLEX_ID_MALE_BODY = 36
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

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

    SlowUpdate: (createModels = CLIENT) =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0

        if @GetData()\GetNewMuzzle()
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        else
            @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
            @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)

        @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY,     maleModifier * @GetData()\GetMaleBuff())

        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS,         @GetData()\GetFangs() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GetData()\GetClawTeeth() and 1 or 0)

        @UpdateTailSize()
        @UpdateManeSize()

        @ApplyRace()
        if createModels
            @UpdateUpperMane()
            @UpdateLowerMane()
            @UpdateTailModel()
            @CreateSocksModelIfNotExists() if createModels and @GetData()\GetSocksAsModel()
    RemoveModels: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()
    ApplyBodygroups: (createModels = CLIENT) =>
        return unless @isValid
        return if not IsValid(@ent)
        @ResetBodygroups()
        return @RemoveModels() if not @ent\IsPony()
        @SlowUpdate(createModels)

    ApplyRace: =>
        return unless @isValid
        return if not IsValid(@ent)
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS + 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, @GetData()\GetWingsType())

    DataChanges: (state) =>
        return unless @isValid
        return if not IsValid(@ent)
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'NewMuzzle'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                if @GetData()\GetNewMuzzle()
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
                else
                    @ent\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
                    @ent\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'BatPonyEars'
                @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
            when 'Fangs'
                @ent\SetFlexWeight(@@FLEX_ID_FANGS, @GetData()\GetFangs() and 1 or 0)
            when 'ClawTeeth'
                @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GetData()\GetClawTeeth() and 1 or 0)
            when 'ManeTypeNew'
                @UpdateUpperMane() if CLIENT
            when 'ManeTypeLowerNew'
                @UpdateLowerMane() if CLIENT
            when 'TailSize', 'TailTypeNew'
                return if SERVER
                @UpdateTailModel()
                @UpdateTailSize()
            when 'PonySize'
                return if SERVER
                @UpdateTailSize()
            when 'Race'
                @ApplyRace()
            when 'WingsType'
                @ApplyRace()
            when 'MaleBuff'
                maleModifier = @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0
                @ent\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GetData()\GetMaleBuff())
            when 'SocksAsModel'
                return if SERVER
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)

if CLIENT
    hook.Add 'PPM2_PACResetBones', 'PPM2.Bodygroups', (ent, data) ->
        if bodygroup = data\GetBodygroupController()
            bodygroup.ent = ent
            bodygroup\UpdateTailSize()
            bodygroup\UpdateManeSize()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

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


Set form content type to multipart/form-data

html = require "lapis.html"
import render_and_pass from require "utils"

class ApplyResult extends html.Widget
    content: =>
        chosen_tasks = @application\get_chosen_tasks!
        apply_content = capture ->
            h1 @m.subheading
            raw @m.text
            details ->
                div class: "left", @m.info_label
                div class: "right", ->
                    strong "#{@application.first_name} #{@application.last_name}"
                    raw ", #{@a.form.classes[@application.class]} ("
                    a href: "mailto:#{@application.email}",
                         @application.email
                    raw ")"

                div class: "left", @m.chosen_tasks_label
                div class: "right", ->
                    ul ->
                        for t in *chosen_tasks
                            t = @a.tasks[t.task]
                            li t.name

            form enctype: "multipart/form-data", method: "POST", ->
                with @m.form
                    input type: "hidden", name: "csrf-token", value: @csrf_token
                    label for: "pitch", id: "pitch-label", .pitch_label
                    textarea required: "required", id: "pitch", name: "pitch", placeholder: .pitch_placeholder
                    for task in *chosen_tasks
                        t = @a.tasks[task.task]
                        h2 t.name
                        raw t.prompt
                        p ->
                            a class: "upload", ->
                                span .select_files
                                input
                                    required: "required", type: "file", name: "tasks[#{task.task}]",
                                    accept: table.concat [".#{x}" for x in *t.filetypes], ','
                            span class: "filename"

                    div class: "status"

                    button type: "submit", id: "submit-button", .submit_text
                    div class: "spinner", style: "display: none", .please_wait

        render_and_pass widget, "views.apply-base", { :apply_content }

html = require "lapis.html"
import render_and_pass from require "utils"

class ApplyResult extends html.Widget
    content: =>
        chosen_tasks = @application\get_chosen_tasks!
        apply_content = capture ->
            h1 @m.subheading
            raw @m.text
            details ->
                div class: "left", @m.info_label
                div class: "right", ->
                    strong "#{@application.first_name} #{@application.last_name}"
                    raw ", #{@a.form.classes[@application.class]} ("
                    a href: "mailto:#{@application.email}",
                         @application.email
                    raw ")"

                div class: "left", @m.chosen_tasks_label
                div class: "right", ->
                    ul ->
                        for t in *chosen_tasks
                            t = @a.tasks[t.task]
                            li t.name

            form method: "POST", ->
                with @m.form
                    input type: "hidden", name: "csrf-token", value: @csrf_token
                    label for: "pitch", id: "pitch-label", .pitch_label
                    textarea required: "required", id: "pitch", name: "pitch", placeholder: .pitch_placeholder
                    for task in *chosen_tasks
                        t = @a.tasks[task.task]
                        h2 t.name
                        raw t.prompt
                        p ->
                            a class: "upload", ->
                                span .select_files
                                input required: "required", type: "file", name: "tasks[#{tid}]"
                            span class: "filename"

                    div class: "status"

                    button type: "submit", id: "submit-button", .submit_text
                    div class: "spinner", style: "display: none", .please_wait

        render_and_pass widget, "views.apply-base", { :apply_content }