added utility functions for changing username/pw to CustomUsersApplication

import hmac_sha1, encode_base64 from require "lapis.util.encoding"
UsersApplication=require "lib.lazuli.src.lazuli.modules.user_management"
Profiles = require "models.profiles"
ACLs = require "models.acls"
ACL_Entries = require "models.acl_entries"
config = (require "lapis.config").get!
csrf = require "lapis.csrf"

prepareUserProfile=(user)->
  profile=Profiles\getOrCreateByUser user
  nobodyACL=ACLs\create {
    user_id: user.id
    name: "nobody"
    default_policy: false
  }
  everyoneACL=ACLs\create {
    user_id: user.id
    name: "everyone"
    default_policy: true
  }
  usersACL=ACLs\create {
    user_id: user.id
    name: "logged in users"
    default_policy: false
  }
  ACL_Entries\create {
    acl_id: usersACL.id
    target_type: ACL_Entries.target_types.include
    target_id: ACL_Entries.special_targets.include_logged_in
    policy: true
  }
  -- TODO: default ACLs, etc.

class CustomUsersApplication extends UsersApplication
  @before_filter =>
    if config.projectStage=="alpha" or config.projectStage=="beta"
      if @req.parsed_url.path=="/users/register/do" and @req.params_post.key~=encode_base64 hmac_sha1 config.secret, @req.params_post.username
        @write redirect_to: @url_for "index"
    @modules.user_management or={}
    @session.modules.user_management or={}
    if not @modules.user_management.providers
      if type(config.modules.user_management)=="table" and type(config.modules.user_management.providers)=="table"
        @modules.user_management.providers={k,require(k)(@,k,v) for k,v in pairs config.modules.user_management.providers}
      else
        @modules.user_management.providers={}
    @modules.user_management.csrf_token = csrf.generate_token @, "lazuli_modules_usermanagement"
    if @session.modules.user_management.currentuser and not @modules.user_management.currentuser
      @modules.user_management.currentuser = Users\find @session.modules.user_management.currentuser
  @superroute logout: "/logout"
  @superroute login: "/login"
  @superroute register: "/register"
  @superroute register_do: "/register/do", (ret)=>
    if (ret.status ~= 403) and @modules.user_management.currentuser
      prepareUserProfile @modules.user_management.currentuser
    ret
  @superroute login_do: "/login/do", (ret)=>
    if @modules.user_management.currentuser
      if @session.login_redirect
        red=@session.login_redirect
        @session.login_redirect=nil
        redirect_to: red
      else
        redirect_to: @url_for "profile_show", id: @modules.user_management.currentuser.id
    else
      ret

  @change_user_name: (user, newname, password)->
    if type(user)=="number"
      user=Users\find user
    return nil, "user not found" unless user
    oldhash=encode_base64(hmac_sha1(password,user.username..password))
    newhash=encode_base64(hmac_sha1(password,newname..password))
    return nil, "wrong password" unless user.pwHMACs1==oldhash
    user\update{
      username: newname
      pwHMACs1: newhash
    }

  @change_password: (user, newpassword)->
    if type(user)=="number"
      user=Users\find user
    return nil, "user not found" unless user
    newhash=encode_base64(hmac_sha1(newpassword,user.username..newpassword))
    user\update{
      pwHMACs1: newhash
    }


More fixes!

More fixes!

bundle_load = bundle_load
print = print
pairs = pairs
append = table.insert

howl.util.lpeg_lexer ->
  c = capture

  id = (alpha + S'._?')^1 * (alpha + digit + S'_$#@~.?')^0
  ws = c 'whitespace', blank^0
  string_contents = any {
    span "'", "'"
    span '"', '"'
    span '`', '`', '\\'
  }

  identifier = c 'identifier', id

  ul = (c) -> P(c)+P(c\upper!)
  ult = (w) -> [ul c for c in w\gmatch '.']
  uls = (w) -> any ult w
  ulq = (w) -> sequence ult w
  case_word = (words) ->
    any([ulq w for w in *words]) * -#id

  keyword = c 'keyword', case_word {
    'absolute', 'abs', 'alignb', 'align', 'at', 'bits', 'bnd', 'byte', 'common',
    'cpu', 'default', 'db', 'dd', 'do', 'dq', 'dt', 'dword', 'dw', 'endstruc',
    'equ', 'extern', 'float', 'global', 'iend', 'incbin', 'istruc', 'nobnd',
    'nosplit', 'opsize', 'oword', 'qword', 'rel', 'resb', 'resd', 'reso', 'resq',
    'rest', 'resw', 'resy', 'resz', 'sectalign', 'section', 'segment', 'struc',
    'strict', 'times', 'seq', 'tword', 'use16', 'use32', 'word', 'wrt', 'yword',
    'zword'
  }

  instrs = bundle_load 'instructions'
  instr = c 'function', P (file, pos) ->
    word = file\sub(pos)\match'^%a+'
    if word and instrs[word\lower!]
      pos + #word
    else
      false

  macro = c 'preproc', P'%' * any {
    span '[', ']'
    P'!' * (id + string_contents)^-1
    P'??'
    P'00'
    S'+?'
    R'09'
    P'%' * id
    word {
      'arg', 'assign', 'define', 'defstr', 'deftok', 'depend', 'elif', 'else',
      'endif', 'endmacro', 'endrep', 'error', 'fatal', 'ifctx', 'ifdef',
      'ifempty', 'ifenv', 'ifidni', 'ifidn','ifmacro', 'iftoken', 'if',
      'include', 'line', 'local', 'macro','pathsearch', 'pop', 'push',
      'stacksize', 'strcat', 'strlen', 'substr','repl', 'rep', 'rotate',
      'xdefine', 'undef', 'unmacro', 'use', 'warning'
    }
  }

  comment = c 'comment', P';' * scan_until eol

  operator = c 'operator', any {
    S'\\:,()[]+*-/%|^&<>~!@'
    case_word {
      '__float8__', '__float16__', '__float32__', '__float64__', '__float80m__',
      '__float80e__', '__float128l__', '__float128h__',
      '__utf16__', '__utf32__'
    }
  }

  special = c 'special', (P'__USE_' * id * '__') + word {
    '__BITS__', '__DATE_NUM__', '__DATE__', '__FILE__', '__FLOAT_DAZ__',
    '__FLOAT_ROUND__', '__FLOAT__', '__Infinity__', '__LINE__',
    '__NASM_VERSION_ID__', '__NASM_VER__', '__NaN__', '__POSIX_TIME__',
    '__PASS__', '__OUTPUT_FORMAT__', '__QNaN__', '__SECT__', '__SNaN__',
    '__TIME_NUM__', '__TIME__', '__UTC_DATE_NUM__', '__UTC_DATE__',
    '__UTC_TIME_NUM__', '__UTC_TIME__'
  }

  gen_regs = ->
    gen =
      ax: true
      cx: true
      dx: true
      bx: true
      sp: false
      bp: false
      si: false
      di: false

    res = {}
    ins = (p) -> append res, P(p)

    for regbase, hl in pairs gen
      ins S'erER'^-1 * ulq regbase
      ins ul(regbase\sub 1, 1) * S'hlHL' if hl

    ins S'sScCdDeEfFgG' * S'sS'
    for i=15,0,-1
      ins S'rR' * P"#{i}" * S'bBdDwWlL'^-1

    for i=0,7
      ins S'xX'^-1 * S'mM' * S'mM' * P"#{i}"
      ins S'sS' * S'tT' * P"#{i}"

    any(res) * -#id

  reg = c 'special', gen_regs!

  make_num_set = (chr0, chr1, letters, bare_prefix, dec) ->
    chr = if chr1
      chr0 + chr1
    else
      chr0
    usc = chr + P'_'
    digit = chr * usc^0
    seq = digit^1
    oseq = digit^0
    float_suf = P'.' * oseq
    exp_suf = ul'e' * S'+-'^-1 * seq
    p_suf = ul'p' * S'+-'^-1 * oseq

    id_prefix = P'0' * letters
    id_prefix += bare_prefix * #chr if bare_prefix

    run = seq^-1 * float_suf^-1 * (exp_suf + p_suf)^-1

    if dec
      chr * run * letters^-1
    else
      (id_prefix * chr * run) + (chr0 * run * letters)

  number = c 'number', S'+-'^-1 * any {
    make_num_set R'01', nil, uls'by'
    make_num_set R'07', nil, uls'oq'
    make_num_set R'09', R'af' + R'AF', uls'hx', P'$'
    make_num_set R'09', nil, uls'dt', nil, true
  }

  string = c 'string', string_contents

  struc = sequence {
    c 'keyword', 'struc'
    ws
    c 'type_def', id
  }

  label = any {
    sequence {
      c 'keyword', case_word { 'global', 'extern' }
      ws
      c 'fdecl', id
    }
    sequence {
      c 'fdecl', id
      ws
      c 'operator', ':'
    }
  }

  any {
    comment
    string
    struc
    label
    keyword
    instr
    special
    reg
    number
    macro
    operator
    identifier
  }

bundle_load = bundle_load
print = print
pairs = pairs
append = table.insert

howl.util.lpeg_lexer ->
  c = capture

  id = (alpha + S'._?')^1 * (alpha + digit + S'_$#@~.?')^0
  ws = c 'whitespace', blank^0
  string_contents = any {
    span "'", "'"
    span '"', '"'
    span '`', '`', '\\'
  }

  identifier = c 'identifier', id

  ul = (c) -> P(c)+P(c\upper!)
  ult = (w) -> [ul c for c in w\gmatch '.']
  uls = (w) -> any ult w
  ulq = (w) -> sequence ult w
  case_word = (words) ->
    any [ulq w for w in *words]

  keyword = c 'keyword', case_word {
    'absolute', 'abs', 'alignb', 'align', 'at', 'bits', 'bnd', 'byte', 'common',
    'cpu', 'default', 'db', 'dd', 'do', 'dq', 'dt', 'dword', 'dw', 'endstruc',
    'equ', 'extern', 'float', 'global', 'iend', 'incbin', 'istruc', 'nobnd',
    'nosplit', 'opsize', 'oword', 'qword', 'rel', 'resb', 'resd', 'reso', 'resq',
    'rest', 'resw', 'resy', 'resz', 'sectalign', 'section', 'segment', 'struc',
    'strict', 'times', 'seq', 'tword', 'use16', 'use32', 'word', 'wrt', 'yword',
    'zword'
  }

  instrs = bundle_load 'instructions'
  instr = c 'function', P (file, pos) ->
    word = file\sub(pos)\match'^%a+'
    if word and instrs[word\lower!]
      pos + #word
    else
      false

  macro = c 'preproc', P'%' * any {
    span '[', ']'
    P'!' * (id + string_contents)^-1
    P'??'
    P'00'
    S'+?'
    R'09'
    P'%' * id
    word {
      'arg', 'assign', 'define', 'defstr', 'deftok', 'depend', 'elif', 'else',
      'endif', 'endmacro', 'endrep', 'error', 'fatal', 'ifctx', 'ifdef',
      'ifempty', 'ifenv', 'ifidni', 'ifidn','ifmacro', 'iftoken', 'if',
      'include', 'line', 'local', 'macro','pathsearch', 'pop', 'push',
      'stacksize', 'strcat', 'strlen', 'substr','repl', 'rep', 'rotate',
      'xdefine', 'undef', 'unmacro', 'use', 'warning'
    }
  }

  comment = c 'comment', P';' * scan_until eol

  operator = c 'operator', any {
    S'\\:,()[]+*-/%|^&<>~!@'
    case_word {
      '__float8__', '__float16__', '__float32__', '__float64__', '__float80m__',
      '__float80e__', '__float128l__', '__float128h__',
      '__utf16__', '__utf32__'
    }
  }

  special = c 'special', (P'__USE_' * id * '__') + word {
    '__BITS__', '__DATE_NUM__', '__DATE__', '__FILE__', '__FLOAT_DAZ__',
    '__FLOAT_ROUND__', '__FLOAT__', '__Infinity__', '__LINE__',
    '__NASM_VERSION_ID__', '__NASM_VER__', '__NaN__', '__POSIX_TIME__',
    '__PASS__', '__OUTPUT_FORMAT__', '__QNaN__', '__SECT__', '__SNaN__',
    '__TIME_NUM__', '__TIME__', '__UTC_DATE_NUM__', '__UTC_DATE__',
    '__UTC_TIME_NUM__', '__UTC_TIME__'
  }

  gen_regs = ->
    gen =
      ax: true
      cx: true
      dx: true
      bx: true
      sp: false
      bp: false
      si: false
      di: false

    res = {}
    ins = (p) -> append res, P(p)

    for regbase, hl in pairs gen
      ins S'erER'^-1 * ulq regbase
      ins ul(regbase\sub 1, 1) * S'hlHL' if hl

    ins S'sScCdDeEfFgG' * S'sS'
    for i=0,15
      ins S'rR' * P"#{i}" * S'dDwWlL'^-1

    for i=0,7
      ins S'xX'^-1 * S'mM' * S'mM' * P"#{i}"
      ins S'sS' * S'tT' * P"#{i}"

    any res

  reg = c 'special', gen_regs!

  make_num_set = (chr0, chr1, letters, bare_prefix, dec) ->
    chr = if chr1
      chr0 + chr1
    else
      chr0
    usc = chr + P'_'
    digit = chr * usc^0
    seq = digit^1
    oseq = digit^0
    float_suf = P'.' * oseq
    exp_suf = ul'e' * S'+-'^-1 * seq
    p_suf = ul'p' * S'+-'^-1 * oseq

    id_prefix = P'0' * letters
    id_prefix += bare_prefix * #chr if bare_prefix

    run = seq^-1 * float_suf^-1 * (exp_suf + p_suf)^-1

    if dec
      chr * run * letters^-1
    else
      (id_prefix * chr * run) + (chr0 * run * letters)

  number = c 'number', S'+-'^-1 * any {
    make_num_set R'01', nil, uls'by'
    make_num_set R'07', nil, uls'oq'
    make_num_set R'09', R'af' + R'AF', uls'hx', P'$'
    make_num_set R'09', nil, uls'dt', nil, true
  }

  string = c 'string', string_contents

  struc = sequence {
    c 'keyword', 'struc'
    ws
    c 'type_def', id
  }

  label = any {
    sequence {
      c 'keyword', case_word { 'global', 'extern' }
      ws
      c 'fdecl', id
    }
    sequence {
      c 'fdecl', id
      ws
      c 'operator', ':'
    }
  }

  any {
    comment
    string
    struc
    label
    keyword
    instr
    special
    reg
    number
    macro
    operator
    identifier
  }


fix typo

fix typo

sessions = {}
for line in io.lines "session_cookies"
    sessions[#sessions+1] = line

count = 1
export get_session = ->
    temp = count
    count = if count + 1 > #sessions then 1 else count + 1
    sessions[temp]

sessions = {}
for line in io.lines "session_cookies"
    sessions[#sessions+1] = line

count = 1
export get_session = ->
    temp = count
    count = if count + 1 > #session then 1 else count + 1
    sessions[temp]


unload modules on execute

unload modules on execute

cjson = require"cjson"

ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=[];',./!@#$%^&*()_+{}|:\"<>?\\`~ "
DEFAULT_FONT = "12px sans-serif"

-- disable invalid functions
os.remove = -> error "os.remove is disabled"
os.rename = -> error "os.rename is disabled"
os.execute = -> error "os.execute is disabled"
os.exit = -> error "os.exit is disabled"
io.tmpfile = -> error "io.tmpfile is disabled"

post_message = (o) ->
  o = cjson.encode o if type(o) == "table"
  nacl.post_message o

printer = (channel="std_out") ->
  (...) ->
    args = {...}
    str = table.concat [tostring s for s in *args], "\t"
    post_message { channel, str }

async_print = printer"std_out"
async_err = printer"std_err"

msg_id = 0
msg_responses = {}

request_response = (msg) ->
  id = msg_id
  msg_id += 1
  post_message { "request", id, msg }
  while true
    msg = coroutine.yield!
    if msg == "response" and msg_responses[id]
      return with msg_responses[id]
        msg_responses[id] = nil

safe_resume = (co, ...) ->
  success, error_msg = coroutine.resume co, ...
  if not success
    async_err debug.traceback co, error_msg
    nacl.set_game_thread nil
    false
  else
    true

async_require = (module_name) ->
  mod = package.loaded[module_name]
  return mod if mod

  loader = if package.preload[module_name]
    package.preload[module_name]
  else
    msg = request_response { "require", module_name }
    status, code = unpack msg
    return async_err code if status == "error"
    assert loadstring code, module_name

  setfenv loader, getfenv 2
  mod = loader module_name
  mod = true if mod == nil
  package.loaded[module_name] = mod
  mod

nacl.lib = {
  core: nacl
  prefetch: (files) ->
    tuples = {}

    add_files = (list, t="text") ->
      for k, file_or_list in pairs list
        if type(file_or_list) == "table"
          add_files file_or_list, k
        else
          table.insert tuples, { t, file_or_list }

    add_files files

    return if #tuples == 0
    status, msg = unpack request_response { "prefetch", tuples }
    error "prefetch: " .. msg if status != "success"

  track_event: (category, action, label=nil, value=nil, interactive=true) ->
    post_message { "track_event", { category, action, label, value, interactive } }
}

-- scope of the game thread
async_scope = setmetatable {
    print: async_print
    require: async_require
    nacl: nacl.lib
  }, {
    __index: _G
  }

game_thread = nil -- to prevent it from being garbage collected

nacl.show_error = async_err -- used by C to send errors to client

nacl.handle_message = (msg) ->
  error "unknown msg: " .. tostring(msg) if type(msg) != "string"
  print ">>", msg\sub 1, 120

  msg = cjson.decode msg

  switch msg[1]
    -- create a new execution thread
    -- throws out the old one
    when "execute"
      fn, err = loadstring msg[2], "execute"
      if not fn
        async_err err
      else
        setfenv fn, async_scope
        run = -> aroma.run fn

        game_thread = coroutine.create run
        nacl.set_game_thread game_thread
        safe_resume game_thread

    when "response"
      _, id, data = unpack msg
      msg_responses[id] = data
      safe_resume game_thread, "response"
    else
      error "Don't know how to handle message: " .. (msg[1] or msg)

img_cache = {}

image_data_from_url = (url) ->
  msg = request_response { "image", url }
  status, bytes, width, height = unpack msg
  if status == "success"
    nacl.image_data_from_byte_string bytes, width, height
  else
    error bytes

class AudioSource
  new: (@source_id, @url) =>
  msg: (...) => post_message { "audio", @source_id, ... }

  play: => @msg "play"
  stop: => @msg "stop"
  rewind: => @msg "rewind"
  setLooping: (should_loop) => @msg "set_looping", should_loop

  __tostring: => "HTMLAudioSource<" .. @url .. ">"

nacl.init = {
  graphics: =>
    old_new_image = @newImage

    @newImage = (url, ...) ->
      return old_new_image url, ... if type(url) != "string"
      return img_cache[url] if img_cache[url]

      img_data = image_data_from_url url
      img = old_new_image img_data
      img_cache[url] = img
      img

    @newFont = (font_str, alphabet=ALPHABET) ->
      glyphs = request_response { "font", font_str, alphabet }
      cache = aroma.font.newGlyphCache!
      for g in *glyphs
        letter, data_string, w, h = unpack g
        data = nacl.image_data_from_byte_string data_string, w, h
        cache\add_glyph letter\byte!, data
      cache\to_font!

    aroma.boot = ->
      if not self.getFont!
        self.setFont self.newFont DEFAULT_FONT

  audio: =>
    @newSource = (url, kind="static") ->
      msg = request_response { "audio", url, kind }
      status, source_id, err_msg = unpack msg
      if status == "success"
        AudioSource source_id, url
      else
        error error_msg

  image: =>
    old_new_image_data = @newImageData
    @newImageData = (...) ->
      args = {...}
      old_new_image_data ... if type(args[1]) != "string"
      image_data_from_url args[1]
}

functions_to_reset = {"draw", "update", "keypressed", "keyreleased", "focus", "load"}
default_modules = { name, true for name in pairs package.loaded }

nacl.init_all = (aroma) ->
  aroma.run = (setup_fn) ->
    -- unload any modules
    for key in pairs package.loaded
      package.loaded[key] = nil unless default_modules[name]

    blank = ->
    aroma[key] = blank for key in *functions_to_reset
    aroma.graphics.reset!
    aroma.boot!

    setup_fn!

    while true -- listen for game messages
      msg = {coroutine.yield!}
      if aroma[msg[1]]
        name = table.remove msg, 1
        aroma[name] unpack msg
      -- else
      --   print "unknown message"
      --   print unpack msg

cjson = require"cjson"

ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=[];',./!@#$%^&*()_+{}|:\"<>?\\`~ "
DEFAULT_FONT = "12px sans-serif"

-- disable invalid functions
os.remove = -> error "os.remove is disabled"
os.rename = -> error "os.rename is disabled"
os.execute = -> error "os.execute is disabled"
os.exit = -> error "os.exit is disabled"
io.tmpfile = -> error "io.tmpfile is disabled"

post_message = (o) ->
  o = cjson.encode o if type(o) == "table"
  nacl.post_message o

printer = (channel="std_out") ->
  (...) ->
    args = {...}
    str = table.concat [tostring s for s in *args], "\t"
    post_message { channel, str }

async_print = printer"std_out"
async_err = printer"std_err"

msg_id = 0
msg_responses = {}

request_response = (msg) ->
  id = msg_id
  msg_id += 1
  post_message { "request", id, msg }
  while true
    msg = coroutine.yield!
    if msg == "response" and msg_responses[id]
      return with msg_responses[id]
        msg_responses[id] = nil

safe_resume = (co, ...) ->
  success, error_msg = coroutine.resume co, ...
  if not success
    async_err debug.traceback co, error_msg
    nacl.set_game_thread nil
    false
  else
    true

async_require = (module_name) ->
  mod = package.loaded[module_name]
  return mod if mod

  loader = if package.preload[module_name]
    package.preload[module_name]
  else
    msg = request_response { "require", module_name }
    status, code = unpack msg
    return async_err code if status == "error"
    assert loadstring code, module_name

  setfenv loader, getfenv 2
  mod = loader module_name
  mod = true if mod == nil
  package.loaded[module_name] = mod
  mod

nacl.lib = {
  core: nacl
  prefetch: (files) ->
    tuples = {}

    add_files = (list, t="text") ->
      for k, file_or_list in pairs list
        if type(file_or_list) == "table"
          add_files file_or_list, k
        else
          table.insert tuples, { t, file_or_list }

    add_files files

    return if #tuples == 0
    status, msg = unpack request_response { "prefetch", tuples }
    error "prefetch: " .. msg if status != "success"

  track_event: (category, action, label=nil, value=nil, interactive=true) ->
    post_message { "track_event", { category, action, label, value, interactive } }
}

-- scope of the game thread
async_scope = setmetatable {
    print: async_print
    require: async_require
    nacl: nacl.lib
  }, {
    __index: _G
  }

game_thread = nil -- to prevent it from being garbage collected

nacl.show_error = async_err -- used by C to send errors to client

nacl.handle_message = (msg) ->
  error "unknown msg: " .. tostring(msg) if type(msg) != "string"
  print ">>", msg\sub 1, 120

  msg = cjson.decode msg

  switch msg[1]
    -- create a new execution thread
    -- throws out the old one
    when "execute"
      fn, err = loadstring msg[2], "execute"
      if not fn
        async_err err
      else
        setfenv fn, async_scope
        run = -> aroma.run fn

        game_thread = coroutine.create run
        nacl.set_game_thread game_thread
        safe_resume game_thread

    when "response"
      _, id, data = unpack msg
      msg_responses[id] = data
      safe_resume game_thread, "response"
    else
      error "Don't know how to handle message: " .. (msg[1] or msg)

img_cache = {}

image_data_from_url = (url) ->
  msg = request_response { "image", url }
  status, bytes, width, height = unpack msg
  if status == "success"
    nacl.image_data_from_byte_string bytes, width, height
  else
    error bytes

class AudioSource
  new: (@source_id, @url) =>
  msg: (...) => post_message { "audio", @source_id, ... }

  play: => @msg "play"
  stop: => @msg "stop"
  rewind: => @msg "rewind"
  setLooping: (should_loop) => @msg "set_looping", should_loop

  __tostring: => "HTMLAudioSource<" .. @url .. ">"

nacl.init = {
  graphics: =>
    old_new_image = @newImage

    @newImage = (url, ...) ->
      return old_new_image url, ... if type(url) != "string"
      return img_cache[url] if img_cache[url]

      img_data = image_data_from_url url
      img = old_new_image img_data
      img_cache[url] = img
      img

    @newFont = (font_str, alphabet=ALPHABET) ->
      glyphs = request_response { "font", font_str, alphabet }
      cache = aroma.font.newGlyphCache!
      for g in *glyphs
        letter, data_string, w, h = unpack g
        data = nacl.image_data_from_byte_string data_string, w, h
        cache\add_glyph letter\byte!, data
      cache\to_font!

    aroma.boot = ->
      if not self.getFont!
        self.setFont self.newFont DEFAULT_FONT

  audio: =>
    @newSource = (url, kind="static") ->
      msg = request_response { "audio", url, kind }
      status, source_id, err_msg = unpack msg
      if status == "success"
        AudioSource source_id, url
      else
        error error_msg

  image: =>
    old_new_image_data = @newImageData
    @newImageData = (...) ->
      args = {...}
      old_new_image_data ... if type(args[1]) != "string"
      image_data_from_url args[1]
}

functions_to_reset = {"draw", "update", "keypressed", "keyreleased", "focus", "load"}

nacl.init_all = (aroma) ->
  aroma.run = (setup_fn) ->
    blank = ->
    aroma[key] = blank for key in *functions_to_reset
    aroma.graphics.reset!
    aroma.boot!

    setup_fn!

    while true -- listen for game messages
      msg = {coroutine.yield!}
      if aroma[msg[1]]
        name = table.remove msg, 1
        aroma[name] unpack msg
      -- else
      --   print "unknown message"
      --   print unpack msg


Toolgun and physgun usage on death ragdolls

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

timer.Create 'PPM2.ModelWatchdog', 1, 0, ->
    for ply in *player.GetAll()
        model = ply\GetModel()
        ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
        if ply.__ppm2_lastmodel ~= model
            data = ply\GetPonyData()
            if data and data.ModelChanges
                data\ModelChanges(ply.__ppm2_lastmodel, model)
                ply.__ppm2_lastmodel = model
    for task in *PPM2.NetworkedPonyData.RenderTasks
        ply = task.ent
        if IsValid(ply)
            model = ply\GetModel()
            ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
            if ply.__ppm2_lastmodel ~= model
                data = ply\GetPonyData()
                if data and data.ModelChanges
                    data\ModelChanges(ply.__ppm2_lastmodel, model)
                    ply.__ppm2_lastmodel = model

do
    catchError = (err) ->
        PPM2.Message 'Slow Update Error: ', err
        PPM2.Message debug.traceback()
    timer.Create 'PPM2.SlowUpdate', CLIENT and 0.5 or 5, 0, ->
        for ply in *player.GetAll()
            continue if not ply\Alive() or not ply\IsPonyCached() or not ply\GetPonyData()
            data = ply\GetPonyData()
            xpcall(data.SlowUpdate, catchError, data, CLIENT) if data.SlowUpdate
        for task in *PPM2.NetworkedPonyData.RenderTasks
            if IsValid(task.ent) and task.ent\IsPony()
                xpcall(task.SlowUpdate, catchError, task, CLIENT) if task.SlowUpdate

DISABLE_HOOFSTEP_SOUND_CLIENT = CreateConVar('ppm2_cl_no_hoofsound', '0', {FCVAR_ARCHIVE}, 'Disable hoofstep sound play time') if CLIENT
DISABLE_HOOFSTEP_SOUND = CreateConVar('ppm2_no_hoofsound', '0', {FCVAR_ARCHIVE, FCVAR_REPLICATED}, 'Disable hoofstep sound play time')

hook.Add 'PlayerStepSoundTime', 'PPM2.Hooks', (stepType = STEPSOUNDTIME_NORMAL, isWalking = false) =>
    return if not IsValid(@)
    return if not @IsPonyCached()
    return if CLIENT and DISABLE_HOOFSTEP_SOUND_CLIENT\GetBool()
    return if DISABLE_HOOFSTEP_SOUND\GetBool()
    rate = @GetPlaybackRate() * .5
    if @Crouching()
        switch stepType
            when STEPSOUNDTIME_NORMAL
                return not isWalking and (300 / rate) or (600 / rate)
            when STEPSOUNDTIME_ON_LADDER
                return 500 / rate
            when STEPSOUNDTIME_WATER_KNEE
                return not isWalking and (400 / rate) or (800 / rate)
            when STEPSOUNDTIME_WATER_FOOT
                return not isWalking and (350 / rate) or (700 / rate)
    else
        switch stepType
            when STEPSOUNDTIME_NORMAL
                return not isWalking and (150 / rate) or (300 / rate)
            when STEPSOUNDTIME_ON_LADDER
                return 500 / rate
            when STEPSOUNDTIME_WATER_KNEE
                return not isWalking and (250 / rate) or (500 / rate)
            when STEPSOUNDTIME_WATER_FOOT
                return not isWalking and (175 / rate) or (350 / rate)

ENABLE_TOOLGUN = CreateConVar('ppm2_sv_ragdoll_toolgun', '0', {FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow toolgun usage on player death ragdolls')
ENABLE_PHYSGUN = CreateConVar('ppm2_sv_ragdoll_physgun', '1', {FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow physgun usage on player death ragdolls')

hook.Add 'CanTool', 'PPM2.DeathRagdoll', (ply = NULL, tr = {Entity: NULL}, tool = '') -> false if IsValid(tr.Entity) and tr.Entity\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_TOOLGUN\GetBool()
hook.Add 'PhysgunPickup', 'PPM2.DeathRagdoll', (ply = NULL, ent = NULL) -> false if IsValid(ent) and ent\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_PHYSGUN\GetBool()


Fixed jitting model when using render overrides

Example - PAC3 entity with hidden entity

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

-- 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_ARCHIVE, 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
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)

    @NECK_BONE_1 = 4
    @NECK_BONE_2 = 5
    @NECK_BONE_3 = 6
    @NECK_BONE_4 = 7

    @LEGS_BONE_ROOT = 0

    @LEGS_FRONT_1 = 23
    @LEGS_FRONT_2 = 29

    @LEGS_FRONT_3 = 21
    @LEGS_FRONT_4 = 27

    @LEGS_FRONT_5 = 20
    @LEGS_FRONT_6 = 26

    @LEGS_BEHIND_1_1 = 14
    @LEGS_BEHIND_2_1 = 16
    @LEGS_BEHIND_3_1 = 15

    @LEGS_BEHIND_1_2 = 9
    @LEGS_BEHIND_2_2 = 11
    @LEGS_BEHIND_3_2 = 10

    @NEXT_OBJ_ID = 0

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

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

    @STEP_SIZE = 18
    @PONY_HULL = 19
    @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()
        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

    ResetDrawMatrix: (ent = @ent) =>
        return if SERVER
        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)
        @ResetDrawMatrix(ent)
        @ResetNeck(ent)
        @ResetLegs(ent)

    ResetNeck: (ent = @ent) =>
        return if not CLIENT
        return if not IsValid(@ent)
        with ent
            \ManipulateBoneScale(@@NECK_BONE_1, Vector(1, 1, 1))
            \ManipulateBoneScale(@@NECK_BONE_2, Vector(1, 1, 1))
            \ManipulateBoneScale(@@NECK_BONE_3, Vector(1, 1, 1))
            \ManipulateBoneScale(@@NECK_BONE_4, Vector(1, 1, 1))
            \ManipulateBoneAngles(@@NECK_BONE_1, Angle(0, 0, 0))
            \ManipulateBoneAngles(@@NECK_BONE_2, Angle(0, 0, 0))
            \ManipulateBoneAngles(@@NECK_BONE_3, Angle(0, 0, 0))
            \ManipulateBoneAngles(@@NECK_BONE_4, Angle(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_1, Vector(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_2, Vector(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_3, Vector(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_4, Vector(0, 0, 0))

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

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

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

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

            \ManipulateBonePosition(@@LEGS_BONE_ROOT, vec2)
            \ManipulateBonePosition(@@LEGS_FRONT_1, vec2)
            \ManipulateBonePosition(@@LEGS_FRONT_2, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_1_1, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_2_1, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_3_1, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_1_2, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_2_2, vec2)
            \ManipulateBonePosition(@@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()
    PlayerRespawn: =>
        @ResetScale()
        @ModifyScale()

    SlowUpdate: =>
        @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
            \SetHull(HULL_MINS, HULL_MAXS) if .SetHull
            \SetHullDuck(HULL_MINS, HULL_MAXS_DUCK) if .SetHullDuck
            \SetStepSize(@@STEP_SIZE * size * @GetLegsModifier(1.2)) if .SetStepSize

    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

    ModifyDrawMatrix: (ent = @ent) =>
        return if SERVER
        return if not @AllowResize()
        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)
        @ModifyDrawMatrix(ent)
        if @lastPAC3BoneReset < RealTime()
            @ModifyNeck(ent)
            @ModifyLegs(ent)

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

        boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
        emptyVector = Vector(0, 0, 0)

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

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

        boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
        emptyVector = Vector(0, 0, 0)

        with ent
            \ManipulateBonePosition(@@LEGS_BONE_ROOT, Vector(0, 0, size * 5) + (boneAnimTable[@@LEGS_BONE_ROOT] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_1, Vector(size * 1.5, 0, 0) + (boneAnimTable[@@LEGS_FRONT_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_2, Vector(size * 1.5, 0, 0) + (boneAnimTable[@@LEGS_FRONT_2] or emptyVector))

            \ManipulateBonePosition(@@LEGS_FRONT_3, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_FRONT_3] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_4, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_FRONT_4] or emptyVector))

            \ManipulateBonePosition(@@LEGS_FRONT_5, Vector(size, size, 0) + (boneAnimTable[@@LEGS_FRONT_5] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_6, Vector(size, size, 0) + (boneAnimTable[@@LEGS_FRONT_6] or emptyVector))

            \ManipulateBonePosition(@@LEGS_BEHIND_1_1, Vector(size, -size * 0.5, 0) + (boneAnimTable[@@LEGS_BEHIND_1_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_BEHIND_1_2, Vector(size, -size * 0.5, 0) + (boneAnimTable[@@LEGS_BEHIND_1_2] or emptyVector))

            \ManipulateBonePosition(@@LEGS_BEHIND_2_1, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_2_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_BEHIND_2_2, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_2_2] or emptyVector))

            \ManipulateBonePosition(@@LEGS_BEHIND_3_1, Vector(size * 2, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_3_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_BEHIND_3_2, Vector(size * 2, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_3_2] or emptyVector))

--
-- 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 NewPonySizeContoller extends PonySizeController
    @MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

    @NECK_BONE_1 = 26
    @NECK_BONE_2 = 27
    @NECK_BONE_3 = 28
    @NECK_BONE_4 = 29

    @LEGS_FRONT_1 = 19
    @LEGS_FRONT_2 = 25

    @LEGS_FRONT_3 = 17
    @LEGS_FRONT_4 = 23

    @LEGS_FRONT_5 = 16
    @LEGS_FRONT_6 = 22

    @LEGS_BEHIND_1_1 = 2
    @LEGS_BEHIND_1_2 = 7

    @LEGS_BEHIND_2_1 = 4
    @LEGS_BEHIND_2_2 = 9

    @LEGS_BEHIND_3_1 = 3
    @LEGS_BEHIND_3_2 = 8

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

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

ppm2_sv_allow_resize = ->
    for ply in *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 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_ARCHIVE, 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
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)

    @NECK_BONE_1 = 4
    @NECK_BONE_2 = 5
    @NECK_BONE_3 = 6
    @NECK_BONE_4 = 7

    @LEGS_BONE_ROOT = 0

    @LEGS_FRONT_1 = 23
    @LEGS_FRONT_2 = 29

    @LEGS_FRONT_3 = 21
    @LEGS_FRONT_4 = 27

    @LEGS_FRONT_5 = 20
    @LEGS_FRONT_6 = 26

    @LEGS_BEHIND_1_1 = 14
    @LEGS_BEHIND_2_1 = 16
    @LEGS_BEHIND_3_1 = 15

    @LEGS_BEHIND_1_2 = 9
    @LEGS_BEHIND_2_2 = 11
    @LEGS_BEHIND_3_2 = 10

    @NEXT_OBJ_ID = 0

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

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

    @STEP_SIZE = 18
    @PONY_HULL = 19
    @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()
        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

    ResetDrawMatrix: (ent = @ent) =>
        return if SERVER
        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)
        @ResetDrawMatrix(ent)
        @ResetNeck(ent)
        @ResetLegs(ent)

    ResetNeck: (ent = @ent) =>
        return if not CLIENT
        return if not IsValid(@ent)
        with ent
            \ManipulateBoneScale(@@NECK_BONE_1, Vector(1, 1, 1))
            \ManipulateBoneScale(@@NECK_BONE_2, Vector(1, 1, 1))
            \ManipulateBoneScale(@@NECK_BONE_3, Vector(1, 1, 1))
            \ManipulateBoneScale(@@NECK_BONE_4, Vector(1, 1, 1))
            \ManipulateBoneAngles(@@NECK_BONE_1, Angle(0, 0, 0))
            \ManipulateBoneAngles(@@NECK_BONE_2, Angle(0, 0, 0))
            \ManipulateBoneAngles(@@NECK_BONE_3, Angle(0, 0, 0))
            \ManipulateBoneAngles(@@NECK_BONE_4, Angle(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_1, Vector(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_2, Vector(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_3, Vector(0, 0, 0))
            \ManipulateBonePosition(@@NECK_BONE_4, Vector(0, 0, 0))

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

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

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

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

            \ManipulateBonePosition(@@LEGS_BONE_ROOT, vec2)
            \ManipulateBonePosition(@@LEGS_FRONT_1, vec2)
            \ManipulateBonePosition(@@LEGS_FRONT_2, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_1_1, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_2_1, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_3_1, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_1_2, vec2)
            \ManipulateBonePosition(@@LEGS_BEHIND_2_2, vec2)
            \ManipulateBonePosition(@@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()
    PlayerRespawn: =>
        @ResetScale()
        @ModifyScale()

    SlowUpdate: =>
        @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
            \SetHull(HULL_MINS, HULL_MAXS) if .SetHull
            \SetHullDuck(HULL_MINS, HULL_MAXS_DUCK) if .SetHullDuck
            \SetStepSize(@@STEP_SIZE * size * @GetLegsModifier(1.2)) if .SetStepSize

    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

    ModifyDrawMatrix: (ent = @ent) =>
        return if SERVER
        return if not @AllowResize()
        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)
        @ModifyDrawMatrix(ent)
        if @lastPAC3BoneReset < RealTime()
            @ModifyNeck(ent)
            @ModifyLegs(ent)

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

        boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
        emptyVector = Vector(0, 0, 0)

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

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

        boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or {}
        emptyVector = Vector(0, 0, 0)

        with ent
            \ManipulateBonePosition(@@LEGS_BONE_ROOT, Vector(0, 0, size * 5) + (boneAnimTable[@@LEGS_BONE_ROOT] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_1, Vector(size * 1.5, 0, 0) + (boneAnimTable[@@LEGS_FRONT_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_2, Vector(size * 1.5, 0, 0) + (boneAnimTable[@@LEGS_FRONT_2] or emptyVector))

            \ManipulateBonePosition(@@LEGS_FRONT_3, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_FRONT_3] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_4, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_FRONT_4] or emptyVector))

            \ManipulateBonePosition(@@LEGS_FRONT_5, Vector(size, size, 0) + (boneAnimTable[@@LEGS_FRONT_5] or emptyVector))
            \ManipulateBonePosition(@@LEGS_FRONT_6, Vector(size, size, 0) + (boneAnimTable[@@LEGS_FRONT_6] or emptyVector))

            \ManipulateBonePosition(@@LEGS_BEHIND_1_1, Vector(size, -size * 0.5, 0) + (boneAnimTable[@@LEGS_BEHIND_1_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_BEHIND_1_2, Vector(size, -size * 0.5, 0) + (boneAnimTable[@@LEGS_BEHIND_1_2] or emptyVector))

            \ManipulateBonePosition(@@LEGS_BEHIND_2_1, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_2_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_BEHIND_2_2, Vector(size, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_2_2] or emptyVector))

            \ManipulateBonePosition(@@LEGS_BEHIND_3_1, Vector(size * 2, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_3_1] or emptyVector))
            \ManipulateBonePosition(@@LEGS_BEHIND_3_2, Vector(size * 2, 0, 0) + (boneAnimTable[@@LEGS_BEHIND_3_2] or emptyVector))

--
-- 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 NewPonySizeContoller extends PonySizeController
    @MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

    @NECK_BONE_1 = 26
    @NECK_BONE_2 = 27
    @NECK_BONE_3 = 28
    @NECK_BONE_4 = 29

    @LEGS_FRONT_1 = 19
    @LEGS_FRONT_2 = 25

    @LEGS_FRONT_3 = 17
    @LEGS_FRONT_4 = 23

    @LEGS_FRONT_5 = 16
    @LEGS_FRONT_6 = 22

    @LEGS_BEHIND_1_1 = 2
    @LEGS_BEHIND_1_2 = 7

    @LEGS_BEHIND_2_1 = 4
    @LEGS_BEHIND_2_2 = 9

    @LEGS_BEHIND_3_1 = 3
    @LEGS_BEHIND_3_2 = 8

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

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

ppm2_sv_allow_resize = ->
    for ply in *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


Add json_requested function

Add json_requested function

Takes a request object and returns whether or not it has an Accept:
header where it accepts application/json primarily (used to serve the
right Content-Type).

html = require "lapis.html"

class Utils
    render_and_pass: (fn, widget_name, params) ->
        widget_obj = (require widget_name)!
        for k, v in pairs params
            widget_obj[k] = v

        fn widget_obj

    json_requested: (req) ->
        split = (require "utils").split
        return false if not req or not req.res or not req.res.req or not req.res.req.headers

        hdr = req.res.req.headers
        return false if not hdr.accept

        val = hdr.accept
        val = val\match '(.*);' if val\find ';'
        spl = split val, ','
        return false if #spl < 1

        spl[1] = spl[1]\gsub "^%s*(.-)%s*$", "%1"
        return true if spl[1] == 'application/json'
        false

    split: (str, sep) ->
        return false if not sep or sep == '' or not str

        r = {}
        n, init = 1, 1

        while true
            s, e = str\find sep, init, true
            break if not s

            r[#r + 1] = str\sub init, s - 1
            init = e + 1
            n += 1

        r[#r + 1] = if init <= str\len!
            str\sub init
        else
            ""

        r, n


lib/irc: Add support for multiple+index

lib/irc: Add support for multiple+index

--- IRC client class
-- @classmod IRCClient

re = require "re"
socket = require 'cqueues.socket'
Logger = require 'logger'

moonscript = {
	errors: require 'moonscript.errors'
}

escapers =  {['s']: ' ', ['r']: '\r', ['n']: '\n', [';']: ';'}

local IRCClient, ContextTable, priority

priority = {
	HIGH: 1,
	DEFAULT: 2,
	LOW: 3,
}

class ContextTable
	get: (name, opts = {})=>
		:multiple, :index, :uses_priority, :current = opts
		multiple = true if multiple == nil
		uses_priority = true if uses_priority == nil

		local output
		output = current or {} if multiple
		for context, items in pairs(self)
			-- context
			if multiple
				-- add all that exist
				if uses_priority
					for p=priority.HIGH, priority.LOW
						if items[p][name]
							for element in *items[p][name]
								table.insert output, element
				else
					if items[name]
						for element in *items[name]
							table.insert output, element
			else
				-- return the singular if it exists
				if uses_priority
					for p=priority.HIGH, priority.LOW
						return items[p][name] if items[p][name] ~= nil
				else
					return items[name] if items[name] ~= nil

		if multiple and not index
			return output
		if index
			return index\get name, :multiple, :uses_priority, current: output

	remove: (name)=>
		for context_name, tbl in pairs(self)
			tbl[name] = nil

class IRCClient
	commands: ContextTable!
	hooks: ContextTable!
	handlers: ContextTable!
	senders: ContextTable!

	__tostring: => "IRCClient"

	default_config = {
		prefix: "!"
	}

	line_pattern = re.compile [[
		-- tags, command, args
		line <- {|
			{:tags: {| (tags sp)? |} :}
			{:prefix: {| (prefix sp)? |} :}
			{:command: (command / numeric) :}
			{:args: {| (sp arg)* |} :} |}
		tags <- '@' tag (';' tag)*
		tag <- {|
			{:is_client: {'+'} :}? -- check: if tag.is_client
			{:vendor: {[^/]+} '/' :}?
			{:key: {[^=; ]+} -> esc_tag :}
			{:value: ('=' {[^; ]+} -> esc_tag) :}?
		|}
		prefix <- ':' (
			{:nick: {[^ !]+} :} '!'
			{:user: {[^ @]+} :} '@'
			{:host: {[^ ]+} :} /
			{:nick: {[^ ]+} :})
		command <- [A-Za-z]+
		numeric <- %d^+3^-4 -- at most four digits, at least three
		arg <- ':' {.+} / {%S+}
		sp <- %s
	]], esc_tag: (tag)-> tag\gsub "\\(.)", setmetatable({
		[":"]: ";"
		s: " "
		r: "\r"
		n: "\n"
	}, __index: (t, k) -> k)

	--- Get a tag based on search parameters (is_client IS NOT matched by key)
	-- @tparam table tags Tag from IRC parser
	-- @tparam table opts Options to search for (is_client: bool, key: string)
	get_tag: (tags, opts)=>
		:is_client, :key = opts
		for tag in *tags
			continue if is_client ~= nil and tag.is_client ~= is_client
			if key ~= nil
				continue if tag.vendor ~= nil and key ~= "#{tag.vendor}/#{tag.key}"
				continue if tag.vendor == nil and key ~= tag.key
			return tag

		return {}

	--- Generate a new IRCClient
	-- @tparam string server IRC server name
	-- @tparam number port IRC port number
	-- @tparam table config Default configuration
	new: (server, port=6697, config)=>
		assert server
		@config = :server, :port, :config, ssl: port == 6697
		for k, v in pairs default_config
			@config[k] = v
		if config
			for k, v in pairs config
				@config[k] = v

		@commands = ContextTable!
		@hooks    = ContextTable!
		@handlers = ContextTable!
		@senders  = ContextTable!
		@server   = {}

	unpack = unpack or table.unpack

	get_priority = (options, fn)->
		return fn and options.priority or priority.DEFAULT

	handle_options: (options, fn)=>
		unless fn
			fn = options
			options = {}

		if options.async
			return (...)->
				args = {...}
				require("queue")\wrap ->
					@pcall_bare fn, unpack args

		if options.wrap_iter
			tmp_fn = coroutine.wrap fn
			tmp_fn!
			return tmp_fn

		fn

	assert_context: (context_table)=>
		assert @context, "Missing @with_context"
		return context_table[assert @context, "Missing @with_context"]

	--- Change the module context for the current command
	-- @tparam string context module context (typically, the name)
	-- @tparam function fn code to run under context
	with_context: (context, fn)=>
		assert @context == nil, "Already in context: #{@context}"
		@context = context

		self["senders"][context] = {} -- no priority for senders, only one
		for key in *{"hooks", "handlers", "commands"}
			self[key][context] = [{} for p=priority.HIGH, priority.LOW]

		fn self, context
		@context = nil

	--- Add an IRC bot command
	-- @tparam string name Bot command name
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function command Function for handling command
	add_command: (name, options, command)=>
		final_command = command
		commands = @assert_context @commands

		-- `options` is passed, which fills in `command`
		if command
			-- is async, send typing until completed
			if options.async
				-- async, send @+draft/typing
				final_command = (prefix, target, line, ...)=>
					@vars.typing_counter = 0 if not @vars.typing_counter
					@vars.typing_counter += 1
					@send 'TAGMSG', target, "+draft/typing": 'active'
					@pcall command, prefix, target, line, ...
					@vars.typing_counter -= 1
					print @vars.typing_counter
					if @vars.typing_counter == 0
						@send 'TAGMSG', target, "+draft/typing": 'done'
					else
						@send 'TAGMSG', target, "+draft/typing": 'active'

		p = get_priority options, final_command
		commands[p][name] = @handle_options options, final_command

	--- Add a client processing hook
	-- @tparam string name Name of event to hook into
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function hook Processor for hook event
	add_hook: (name, options, hook)=>
		hooks = @assert_context @hooks

		p = get_priority options, hook

		unless hooks[p][name]
			hooks[p][name] = {@handle_options options, hook}
		else
			table.insert hooks[p][name], @handle_options(options, hook)

	--- Add an IRC command handler
	-- @tparam string id IRC command ID (numerics MUST be strings)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function handler IRC command processor
	add_handler: (id, options, handler)=>
		handlers = @assert_context @handlers

		p = get_priority options, handler

		unless handlers[p][id]
			handlers[p][id] = {@handle_options options, handler}
		else
			table.insert hooks[p][name], @handle_options(options, handler)

	--- Add an IRC command sending handler, does NOT take a priority
	-- @tparam string id IRC command ID ("PRIVMSG", "JOIN", etc.)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function sender Function to handle message to be sent
	add_sender: (id, options, sender)=>
		senders = @assert_context @senders
		senders[id] = @handle_options(options, sender)

	--- Reset all modules in the IRCClient
	clear_modules: ()=>
		@senders = {}
		@handlers = {}
		@hooks = {}
		@commands = {}

	--- Connect to the IRC server specified in the configuration
	connect: ()=>
		if @socket
			Logger.debug "Shutting down socket: #{tostring(@socket)}"
			@socket\shutdown!
		host = @config.server
		port = @config.port
		ssl  = @config.ssl
		debug_msg = ('Connecting... {host: "%s", port: "%s"}')\format host, port

		@config.nick = 'Moon-Moon' if not @config.nick
		@config.username = 'Mooooon' if not @config.username
		@config.realname = 'Moon Moon: MoonScript IRC Bot' if not @config.realname

		Logger.debug debug_msg, Logger.level.warn .. '--- Connecting...'
		@socket = assert socket.connect{:host, :port}
		if ssl
			Logger.debug 'Starting TLS exchange...'
			@socket\starttls!
			Logger.debug 'Started TLS exchange'
		Logger.print Logger.level.okay .. '--- Connected'
		@fire_hook 'CONNECT'
		nick = @config.nick
		user = @config.username
		real = @config.realname
		pass = @config.password
		Logger.print Logger.level.warn .. '--- Sending authentication data'
		@send_raw 'NICK', nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw 'PASS', pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw 'USER', user, '*', '*', real
		debug_msg = ('Sent authentication data: {nickname: %s, username: %s, realname: %s}')\format nick, user, real
		Logger.debug debug_msg, Logger.level.okay .. '--- Sent authentication data'

	--- Disconnect from the current IRC server
	disconnect: ()=>
		@socket\shutdown! if @socket
		@fire_hook 'DISCONNECT'

	tag_escapes = {
		";": ":"
		" ": "s"
		"\r": "r"
		"\n": "n"
	}

	--- Serialize a value for being sent in a tag
	serialize_tag_string = (key, value)->
		return key if type(value) == "boolean"
		return key .. "=" .. value\gsub "([: \r\n])", (value)-> "\\#{tbl[value]}"

	--- Serialize client->client tags for sending to the server
	-- @param tags Table of tags to serialize
	serialize_tags = (tags)->
		output = [serialize_tag_string(k, v) for k, v in pairs tags]
		return "@#{table.concat output, ';'}"

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		output = {...}
		arg_count = select("#", ...)
		opts_table = select(arg_count, ...)
		if type(opts_table) == "table"
			-- we have tags, server accepts tags
			if opts_table.tags and next(opts_table.tags) and @server.ircv3_caps["message-tags"]
				table.insert output, 1, serialize_tags opts_table.tags
			-- remove opts_table regardless of caps
			table.remove output, #output
		arg_count = #output
		if output[arg_count]\find " "
			output[arg_count] = ":#{output[arg_count]}"
		@socket\write table.concat(output, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat output, ' '

	--- Send a command using a builtin sender configured with @add_sender
	-- @tparam string name Name of sender to use
	-- @param ... List of arguments to be passed to sender
	-- @see IRCClient\add_sender
	send: (name, ...)=>
		sender = @senders\get name, multiple: false, index: IRCClient.senders, uses_priority: false
		input = sender self, ...
		@socket\write input if input

	date_pattern: "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)Z"

	--- Parse the time of an ISO 8601 compliant string
	-- @tparam string date ISO 8601 compliant YYYY-MM-DDThh:mm:ssZ date
	-- @treturn number Seconds from Epoch from configured date
	parse_time: (date)=>
		year, month, day, hour, min, sec, mil = date\match @date_pattern
		return os.time(:year, :month, :day, :hour, :min, :sec) + tonumber(mil) / 1000

	--- parse an IRC command using line_pattern
	parse: (line)=> line_pattern\match line

	--- Activate hooks configured for the event name
	-- @tparam string hook_name Name of event to "fire"
	-- @treturn boolean True if no errors were encountered, false otherwise
	-- @treturn table Table containing list of errors from hooks
	fire_hook: (hook_name, ...)=>
		Logger.debug "#{Logger.level.warn}--- Running hooks for #{hook_name}"
		has_run = false
		has_errors = false
		errors = {}

		for _, hook in pairs IRCClient.hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running global hook: ' .. hook_name
			ok, err = @pcall hook, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
		for _, hook in pairs @hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running hook: ' .. hook_name
			ok, err = @pcall hook, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)

		Logger.debug Logger.level.error .. "*** Hook not found for #{command}" unless has_run

		return has_errors, errors, has_run

	process_line: (line, opts={})=>
		{:tags, :prefix, :command, :args} = @parse line
		opts.line = line
		@process prefix, command, args, tags, opts

	--- Run handlers for an unparsed command
	-- @tparam table prefix Prefix argument from server (nick, user, host)
	-- @tparam table args Argument list from server, including trailing
	-- @tparam table tags Tags list from server
	-- @tparam table opts Optional arguments, in_batch: IRCv3 batching
	process: (prefix, command, args, tags, opts={})=>
		{:in_batch} = opts
		{:nick, :user, :host} = prefix
		if opts.line
			Logger.debug Logger.level.warn .. '--- | Line: ' .. opts.line
			Logger.debug Logger.level.okay .. '--- |\\ Running trigger: ' .. Logger.level.warn .. command
			if nick and user and host
				Logger.debug "#{Logger.level.okay}--- |\\ User: #{nick}!#{user}@#{host}"
			elseif nick
				Logger.debug "#{Logger.level.okay}--- |\\ Nick: #{nick}"
			if #args > 0
				Logger.debug "#{Logger.level.okay}--- |\\ Arguments: #{table.concat args, ' '}"

		has_run = false

		if not in_batch and tags.batch
			-- will return _, _, true if a hook run; this is necessary because
			-- the protocol might send batches even if we don't support them.
			return unless select(2, @fire_hook "BATCH.#{tags.batch}", prefix,
				args, tags, opts)

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

		for handler in *@handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		for received_line in @socket\lines! do
			@pcall @process_line, received_line, in_batch: false

	--- Call a function and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, self, ...
		if not ok
			Logger.debug "Arguments:"
			for arg in *{...}
				Logger.debug tostring arg
		return ok, err

	--- Call a function without self and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall_bare: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, ...
		if not ok
			Logger.debug "Arguments:"
			Logger.debug tostring arg for arg in *{...}
		return ok, err

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		err = tostring err
		Logger.debug moonscript.errors.rewrite_traceback debug.traceback!, err
		Logger.debug "#{Logger.level.error} ---"
		return err

	--- Log message from IRC server (used in plugins)
	-- @tparam string input Line to print, IRC color formatted
	-- @see logger
	log: (input)=>
		for line in input\gmatch "[^\r\n]+"
			Logger.print '\00311(\003' .. (@server.caps and
				@server.caps['NETWORK'] or @config.server) ..
				"\00311)\003 #{line}"

return {:IRCClient, :priority}

--- IRC client class
-- @classmod IRCClient

re = require "re"
socket = require 'cqueues.socket'
Logger = require 'logger'

moonscript = {
	errors: require 'moonscript.errors'
}

escapers =  {['s']: ' ', ['r']: '\r', ['n']: '\n', [';']: ';'}

local IRCClient, ContextTable, priority

priority = {
	HIGH: 1,
	DEFAULT: 2,
	LOW: 3,
}

class ContextTable
	-- ::TODO:: allow for multiple *and* index
	get: (name, opts = {})=>
		:multiple, :index, :uses_priority = opts
		multiple = true if multiple == nil
		uses_priority = true if uses_priority == nil

		local output
		output = {} if multiple
		for context, items in pairs(self)
			-- context
			if multiple
				-- add all that exist
				if uses_priority
					for p=priority.HIGH, priority.LOW
						if items[p][name]
							for element in *items[p][name]
								table.insert output, element
				else
					if items[name]
						for element in *items[name]
							table.insert output, element
			else
				-- return the singular if it exists
				if uses_priority
					for p=priority.HIGH, priority.LOW
						return items[p][name] if items[p][name] ~= nil
				else
					return items[name] if items[name] ~= nil

		if multiple
			output
		elseif index
			index\get name, :multiple, :uses_priority

	remove: (name)=>
		for context_name, tbl in pairs(self)
			tbl[name] = nil

class IRCClient
	commands: ContextTable!
	hooks: ContextTable!
	handlers: ContextTable!
	senders: ContextTable!

	__tostring: => "IRCClient"

	default_config = {
		prefix: "!"
	}

	line_pattern = re.compile [[
		-- tags, command, args
		line <- {|
			{:tags: {| (tags sp)? |} :}
			{:prefix: {| (prefix sp)? |} :}
			{:command: (command / numeric) :}
			{:args: {| (sp arg)* |} :} |}
		tags <- '@' tag (';' tag)*
		tag <- {|
			{:is_client: {'+'} :}? -- check: if tag.is_client
			{:vendor: {[^/]+} '/' :}?
			{:key: {[^=; ]+} -> esc_tag :}
			{:value: ('=' {[^; ]+} -> esc_tag) :}?
		|}
		prefix <- ':' (
			{:nick: {[^ !]+} :} '!'
			{:user: {[^ @]+} :} '@'
			{:host: {[^ ]+} :} /
			{:nick: {[^ ]+} :})
		command <- [A-Za-z]+
		numeric <- %d^+3^-4 -- at most four digits, at least three
		arg <- ':' {.+} / {%S+}
		sp <- %s
	]], esc_tag: (tag)-> tag\gsub "\\(.)", setmetatable({
		[":"]: ";"
		s: " "
		r: "\r"
		n: "\n"
	}, __index: (t, k) -> k)

	--- Get a tag based on search parameters (is_client IS NOT matched by key)
	-- @tparam table tags Tag from IRC parser
	-- @tparam table opts Options to search for (is_client: bool, key: string)
	get_tag: (tags, opts)=>
		:is_client, :key = opts
		for tag in *tags
			continue if is_client ~= nil and tag.is_client ~= is_client
			if key ~= nil
				continue if tag.vendor ~= nil and key ~= "#{tag.vendor}/#{tag.key}"
				continue if tag.vendor == nil and key ~= tag.key
			return tag

		return {}

	--- Generate a new IRCClient
	-- @tparam string server IRC server name
	-- @tparam number port IRC port number
	-- @tparam table config Default configuration
	new: (server, port=6697, config)=>
		assert server
		@config = :server, :port, :config, ssl: port == 6697
		for k, v in pairs default_config
			@config[k] = v
		if config
			for k, v in pairs config
				@config[k] = v

		@commands = ContextTable!
		@hooks    = ContextTable!
		@handlers = ContextTable!
		@senders  = ContextTable!
		@server   = {}

	unpack = unpack or table.unpack

	get_priority = (options, fn)->
		return fn and options.priority or priority.DEFAULT

	handle_options: (options, fn)=>
		unless fn
			fn = options
			options = {}

		if options.async
			return (...)->
				args = {...}
				require("queue")\wrap ->
					@pcall_bare fn, unpack args

		if options.wrap_iter
			tmp_fn = coroutine.wrap fn
			tmp_fn!
			return tmp_fn

		fn

	assert_context: (context_table)=>
		assert @context, "Missing @with_context"
		return context_table[assert @context, "Missing @with_context"]

	--- Change the module context for the current command
	-- @tparam string context module context (typically, the name)
	-- @tparam function fn code to run under context
	with_context: (context, fn)=>
		assert @context == nil, "Already in context: #{@context}"
		@context = context

		self["senders"][context] = {} -- no priority for senders, only one
		for key in *{"hooks", "handlers", "commands"}
			self[key][context] = [{} for p=priority.HIGH, priority.LOW]

		fn self, context
		@context = nil

	--- Add an IRC bot command
	-- @tparam string name Bot command name
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function command Function for handling command
	add_command: (name, options, command)=>
		final_command = command
		commands = @assert_context @commands

		-- `options` is passed, which fills in `command`
		if command
			-- is async, send typing until completed
			if options.async
				-- async, send @+draft/typing
				final_command = (prefix, target, line, ...)=>
					@vars.typing_counter = 0 if not @vars.typing_counter
					@vars.typing_counter += 1
					@send 'TAGMSG', target, "+draft/typing": 'active'
					@pcall command, prefix, target, line, ...
					@vars.typing_counter -= 1
					print @vars.typing_counter
					if @vars.typing_counter == 0
						@send 'TAGMSG', target, "+draft/typing": 'done'
					else
						@send 'TAGMSG', target, "+draft/typing": 'active'

		p = get_priority options, final_command
		commands[p][name] = @handle_options options, final_command

	--- Add a client processing hook
	-- @tparam string name Name of event to hook into
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function hook Processor for hook event
	add_hook: (name, options, hook)=>
		hooks = @assert_context @hooks

		p = get_priority options, hook

		unless hooks[p][name]
			hooks[p][name] = {@handle_options options, hook}
		else
			table.insert hooks[p][name], @handle_options(options, hook)

	--- Add an IRC command handler
	-- @tparam string id IRC command ID (numerics MUST be strings)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function handler IRC command processor
	add_handler: (id, options, handler)=>
		handlers = @assert_context @handlers

		p = get_priority options, handler

		unless handlers[p][id]
			handlers[p][id] = {@handle_options options, handler}
		else
			table.insert hooks[p][name], @handle_options(options, handler)

	--- Add an IRC command sending handler, does NOT take a priority
	-- @tparam string id IRC command ID ("PRIVMSG", "JOIN", etc.)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function sender Function to handle message to be sent
	add_sender: (id, options, sender)=>
		senders = @assert_context @senders
		senders[id] = @handle_options(options, sender)

	--- Reset all modules in the IRCClient
	clear_modules: ()=>
		@senders = {}
		@handlers = {}
		@hooks = {}
		@commands = {}

	--- Connect to the IRC server specified in the configuration
	connect: ()=>
		if @socket
			Logger.debug "Shutting down socket: #{tostring(@socket)}"
			@socket\shutdown!
		host = @config.server
		port = @config.port
		ssl  = @config.ssl
		debug_msg = ('Connecting... {host: "%s", port: "%s"}')\format host, port

		@config.nick = 'Moon-Moon' if not @config.nick
		@config.username = 'Mooooon' if not @config.username
		@config.realname = 'Moon Moon: MoonScript IRC Bot' if not @config.realname

		Logger.debug debug_msg, Logger.level.warn .. '--- Connecting...'
		@socket = assert socket.connect{:host, :port}
		if ssl
			Logger.debug 'Starting TLS exchange...'
			@socket\starttls!
			Logger.debug 'Started TLS exchange'
		Logger.print Logger.level.okay .. '--- Connected'
		@fire_hook 'CONNECT'
		nick = @config.nick
		user = @config.username
		real = @config.realname
		pass = @config.password
		Logger.print Logger.level.warn .. '--- Sending authentication data'
		@send_raw 'NICK', nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw 'PASS', pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw 'USER', user, '*', '*', real
		debug_msg = ('Sent authentication data: {nickname: %s, username: %s, realname: %s}')\format nick, user, real
		Logger.debug debug_msg, Logger.level.okay .. '--- Sent authentication data'

	--- Disconnect from the current IRC server
	disconnect: ()=>
		@socket\shutdown! if @socket
		@fire_hook 'DISCONNECT'

	tag_escapes = {
		";": ":"
		" ": "s"
		"\r": "r"
		"\n": "n"
	}

	--- Serialize a value for being sent in a tag
	serialize_tag_string = (key, value)->
		return key if type(value) == "boolean"
		return key .. "=" .. value\gsub "([: \r\n])", (value)-> "\\#{tbl[value]}"

	--- Serialize client->client tags for sending to the server
	-- @param tags Table of tags to serialize
	serialize_tags = (tags)->
		output = [serialize_tag_string(k, v) for k, v in pairs tags]
		return "@#{table.concat output, ';'}"

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		output = {...}
		arg_count = select("#", ...)
		opts_table = select(arg_count, ...)
		if type(opts_table) == "table"
			-- we have tags, server accepts tags
			if opts_table.tags and next(opts_table.tags) and @server.ircv3_caps["message-tags"]
				table.insert output, 1, serialize_tags opts_table.tags
			-- remove opts_table regardless of caps
			table.remove output, #output
		arg_count = #output
		if output[arg_count]\find " "
			output[arg_count] = ":#{output[arg_count]}"
		@socket\write table.concat(output, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat output, ' '

	--- Send a command using a builtin sender configured with @add_sender
	-- @tparam string name Name of sender to use
	-- @param ... List of arguments to be passed to sender
	-- @see IRCClient\add_sender
	send: (name, ...)=>
		sender = @senders\get name, multiple: false, index: IRCClient.senders, uses_priority: false
		input = sender self, ...
		@socket\write input if input

	date_pattern: "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)Z"

	--- Parse the time of an ISO 8601 compliant string
	-- @tparam string date ISO 8601 compliant YYYY-MM-DDThh:mm:ssZ date
	-- @treturn number Seconds from Epoch from configured date
	parse_time: (date)=>
		year, month, day, hour, min, sec, mil = date\match @date_pattern
		return os.time(:year, :month, :day, :hour, :min, :sec) + tonumber(mil) / 1000

	--- parse an IRC command using line_pattern
	parse: (line)=> line_pattern\match line

	--- Activate hooks configured for the event name
	-- @tparam string hook_name Name of event to "fire"
	-- @treturn boolean True if no errors were encountered, false otherwise
	-- @treturn table Table containing list of errors from hooks
	fire_hook: (hook_name, ...)=>
		Logger.debug "#{Logger.level.warn}--- Running hooks for #{hook_name}"
		has_run = false
		has_errors = false
		errors = {}

		for _, hook in pairs IRCClient.hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running global hook: ' .. hook_name
			ok, err = @pcall hook, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
		for _, hook in pairs @hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running hook: ' .. hook_name
			ok, err = @pcall hook, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)

		Logger.debug Logger.level.error .. "*** Hook not found for #{command}" unless has_run

		return has_errors, errors, has_run

	process_line: (line, opts={})=>
		{:tags, :prefix, :command, :args} = @parse line
		opts.line = line
		@process prefix, command, args, tags, opts

	--- Run handlers for an unparsed command
	-- @tparam table prefix Prefix argument from server (nick, user, host)
	-- @tparam table args Argument list from server, including trailing
	-- @tparam table tags Tags list from server
	-- @tparam table opts Optional arguments, in_batch: IRCv3 batching
	process: (prefix, command, args, tags, opts={})=>
		{:in_batch} = opts
		{:nick, :user, :host} = prefix
		if opts.line
			Logger.debug Logger.level.warn .. '--- | Line: ' .. opts.line
			Logger.debug Logger.level.okay .. '--- |\\ Running trigger: ' .. Logger.level.warn .. command
			if nick and user and host
				Logger.debug "#{Logger.level.okay}--- |\\ User: #{nick}!#{user}@#{host}"
			elseif nick
				Logger.debug "#{Logger.level.okay}--- |\\ Nick: #{nick}"
			if #args > 0
				Logger.debug "#{Logger.level.okay}--- |\\ Arguments: #{table.concat args, ' '}"

		has_run = false

		if not in_batch and tags.batch
			-- will return _, _, true if a hook run; this is necessary because
			-- the protocol might send batches even if we don't support them.
			return unless select(2, @fire_hook "BATCH.#{tags.batch}", prefix,
				args, tags, opts)

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

		for handler in *@handlers\get command
			has_run = true unless has_run
			@pcall handler, prefix, args, tags, opts

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		for received_line in @socket\lines! do
			@pcall @process_line, received_line, in_batch: false

	--- Call a function and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, self, ...
		if not ok
			Logger.debug "Arguments:"
			for arg in *{...}
				Logger.debug tostring arg
		return ok, err

	--- Call a function without self and, when failed, print debug information
	-- @tparam function fn Function to be called
	-- @param ... vararg list to pass to function
	pcall_bare: (fn, ...)=>
		ok, err = xpcall fn, self\log_traceback, ...
		if not ok
			Logger.debug "Arguments:"
			Logger.debug tostring arg for arg in *{...}
		return ok, err

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		err = tostring err
		Logger.debug moonscript.errors.rewrite_traceback debug.traceback!, err
		Logger.debug "#{Logger.level.error} ---"
		return err

	--- Log message from IRC server (used in plugins)
	-- @tparam string input Line to print, IRC color formatted
	-- @see logger
	log: (input)=>
		for line in input\gmatch "[^\r\n]+"
			Logger.print '\00311(\003' .. (@server.caps and
				@server.caps['NETWORK'] or @config.server) ..
				"\00311)\003 #{line}"

return {:IRCClient, :priority}