utf8: Fix trailing whitespace.

utf8: Fix trailing whitespace.

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
ci = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl = '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud = [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]

an2ci = {}
-- Circled letters are 3 bytes, just use sub string
for i=1, #an
  f = i*3+1-3
  t = i*3
  an2ci[an\sub(i,i)] = ci\sub(f, t)

an2bl = {}
i=1
-- Since blackletters have varying byte length, use the common lua pattern to find multibyte chars
for uchar in string.gfind(bl, "([%z\1-\127\194-\244][\128-\191]*)")
  an2bl[an\sub(i,i)] = uchar
  i = i +1

an2ud = {}
i=1
for uchar in string.gfind(ud, "([%z\1-\127\194-\244][\128-\191]*)")
  an2ud[an\sub(i,i)] = uchar
  i = i +1

codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(an2bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(an2ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 10)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(an2ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(an2ud, arg)


navnedag: add reverse function

navnedag: add reverse function

Former-commit-id: e5ce1d0e724627827aeb0a72a0d10a358998d39d [formerly 58e438dacd38dba058e4fef0021ae4bdcad9663c]
Former-commit-id: b486f8940e32fdcbc57b26f4174514ae7ae067f2

os = require'os'
util = require'util'
data =
  "01.01":"Jesus"
  "02.01":"Dagfinn Dagfrid"
  "03.01":"Alfred Alf"
  "04.01":"Roar Roger"
  "05.01":"Hanna Hanne"
  "06.01":"Aslaug Åslaug"
  "07.01":"Eldbjørg Knut"
  "08.01":"Torfinn Turid"
  "09.01":"Gunnar Gunn"
  "10.01":"Sigmund Sigrun"
  "11.01":"Børge Børre"
  "12.01":"Reinhard Reinert"
  "13.01":"Gisle Gislaug"
  "14.01":"Herbjørn Herbjørg"
  "15.01":"Laurits Laura"
  "16.01":"Hjalmar Hilmar"
  "17.01":"Anton Tønnes Tony"
  "18.01":"Hildur Hild"
  "19.01":"Marius Margunn"
  "20.01":"Fabian Sebastian Bastian"
  "21.01":"Agnes Agnete"
  "22.01":"Ivan Vanja"
  "23.01":"Emil Emilie Emma"
  "24.01":"Joar Jarle Jarl"
  "25.01":"Paul Pål"
  "26.01":"Øystein Esten"
  "27.01":"Gaute Gurly Gry"
  "28.01":"Karl Karoline"
  "29.01":"Herdis Hermod Hermann"
  "30.01":"Gunnhild Gunda"
  "31.01":"Idun Ivar"
  "01.02":"Birte Bjarte"
  "02.02":"Jomar Jostein"
  "03.02":"Ansgar Asgeir"
  "04.02":"Veronika Vera"
  "05.02":"Agate Ågot"
  "06.02":"Dortea Dorte"
  "07.02":"Rikard Rigmor Riborg"
  "08.02":"Åshild Åsne"
  "09.02":"Lone Leikny"
  "10.02":"Ingfrid Ingrid"
  "11.02":"Ingve Yngve"
  "12.02":"Randulf Randi Ronja"
  "13.02":"Svanhild Svanlaug"
  "14.02":"Hjørdis Jardar"
  "15.02":"Sigfred Sigbjørn"
  "16.02":"Julian Juliane Jill"
  "17.02":"Aleksandra Sandra Sondre"
  "18.02":"Frøydis Frode"
  "19.02":"Ella Elna"
  "20.02":"Halldis Halldor"
  "21.02":"Samuel Selma Celine"
  "22.02":"Tina Tine Tim"
  "23.02":"Torstein Torunn"
  "24.02":"Mattias Mattis Mats"
  "25.02":"Viktor Viktoria"
  "26.02":"Inger Ingjerd"
  "27.02":"Laila Lill"
  "28.02":"Marina Maren"
  "01.03":"Audny Audun"
  "02.03":"Erna Ernst"
  "03.03":"Gunnbjørg Gunnveig"
  "04.03":"Ada Adrian"
  "05.03":"Patrick Patricia"
  "06.03":"Annfrid Andor"
  "07.03":"Arild Are"
  "08.03":"Beate Betty Bettina"
  "09.03":"Sverre Sindre"
  "10.03":"Edel Edle"
  "11.03":"Edvin Tale"
  "12.03":"Gregor Gro"
  "13.03":"Greta Grete"
  "14.03":"Mathilde Mette"
  "15.03":"Christel Christer Chris"
  "16.03":"Gudmund Gudny"
  "17.03":"Gjertrud Trude"
  "18.03":"Aleksander Sander Edvard"
  "19.03":"Josef Josefine"
  "20.03":"Joakim Kim"
  "21.03":"Bendik Bengt Bent"
  "22.03":"Paula Pauline"
  "23.03":"Gerda Gerd"
  "24.03":"Ulrikke Rikke"
  "25.03":"Maria Marie Mari"
  "26.03":"Gabriel Glenn"
  "27.03":"Rudolf Rudi"
  "28.03":"Åsta Åste"
  "29.03":"Jonas Jonatan"
  "30.03":"Holger Olga"
  "31.03":"Vebjørn Vegard"
  "01.04":"Aron Arve Arvid"
  "02.04":"Sigvard Sivert"
  "03.04":"Gunnvald Gunnvor"
  "04.04":"Nanna Nancy Nina"
  "05.04":"Irene Eirin Eril"
  "06.04":"Åsmund Asmund"
  "07.04":"Oddveig Oddvin"
  "08.04":"Asle Atle"
  "09.04":"Rannveig Rønnaug"
  "10.04":"Ingvald Ingveig"
  "11.04":"Ylva Ulf"
  "12.04":"Julius Julie"
  "13.04":"Asta Astrid"
  "14.04":"Ellinor Nora"
  "15.04":"Oda Odin Odd"
  "16.04":"Magnus Mons"
  "17.04":"Elise Else Elsa"
  "18.04":"Eilen Eilert"
  "19.04":"Arnfinn Arnstein"
  "20.04":"Kjellaug Kjellrun"
  "21.04":"Jeanette Jannike"
  "22.04":"Oddgeir Oddny"
  "23.04":"Georg Jørgen Jørg"
  "24.04":"Albert Olaug"
  "25.04":"Markus Mark"
  "26.04":"Terese Tea"
  "27.04":"Charles Charlotte Lotte"
  "28.04":"Vivi Vivian"
  "29.04":"Toralf Torolf"
  "30.04":"Gina Gitte"
  "01.05":"Filip Valborg"
  "02.05":"Åsa Åse"
  "03.05":"Gjermund Gøril"
  "04.05":"Monika Mona"
  "05.05":"Gudbrand Gullborg"
  "06.05":"Guri Gyri"
  "07.05":"Maia Mai Maiken"
  "08.05":"Åge Åke"
  "09.05":"Kasper Jesper"
  "10.05":"Asbjørg Asbjørn Espen"
  "11.05":"Magda Malvin"
  "12.05":"Normann Norvald"
  "13.05":"Linda Line Linn"
  "14.05":"Kristian Kristen Karsten"
  "15.05":"Hallvard Hallvor"
  "16.05":"Sara Siren"
  "17.05":"Harald Ragnhild"
  "18.05":"Eirik Erik Erika"
  "19.05":"Torjus Torje Truls"
  "20.05":"Bjørnar Bjørnhild"
  "21.05":"Helene Ellen Eli"
  "22.05":"Henning Henny"
  "23.05":"Oddleif Oddlaug"
  "24.05":"Ester Iris"
  "25.05":"Ragna Ragnar"
  "26.05":"Annbjørg Annlaug"
  "27.05":"Katinka Cato"
  "28.05":"Vilhelm William Willy"
  "29.05":"Magnar Magnhild"
  "30.05":"Gard Geir"
  "31.05":"Pernille Preben"
  "01.06":"June Juni"
  "02.06":"Runa Runar Rune"
  "03.06":"Rasmus Rakel"
  "04.06":"Heidi Heid"
  "05.06":"Torbjørg Torbjørn Torben"
  "06.06":"Gustav Gyda"
  "07.06":"Robert Robin"
  "08.06":"Renate René"
  "09.06":"Kolbein Kolbjørn"
  "10.06":"Ingolf Ingunn"
  "11.06":"Borgar Bjørge Bjørg"
  "12.06":"Sigfrid Sigrid Siri"
  "13.06":"Tone Tonje Tanja"
  "14.06":"Erlend Erland"
  "15.06":"Vigdis Viggo"
  "16.06":"Torhild Toril Tiril"
  "17.06":"Botolf Bodil"
  "18.06":"Bjarne Bjørn"
  "19.06":"Erling Elling"
  "20.06":"Salve Sølve Sølvi"
  "21.06":"Agnar Annar"
  "22.06":"Håkon Maud"
  "23.06":"Elfrid Eldrid"
  "24.06":"Johannes Jon Hans"
  "25.06":"Jørund Jorunn"
  "26.06":"Jenny Jonny"
  "27.06":"Aina Ina Ine"
  "28.06":"Lea Leo Leon"
  "29.06":"Peter Petter Per"
  "30.06":"Solbjørg Solgunn"
  "01.07":"Ask Embla"
  "02.07":"Kjartan Kjellfrid"
  "03.07":"Andrea Andrine André"
  "04.07":"Ulrik Ulla"
  "05.07":"Mirjam Mina"
  "06.07":"Torgrim Torgunn"
  "07.07":"Håvard Hulda"
  "08.07":"Sunniva Synnøve Synne"
  "09.07":"Gøran Jøran Ørjan"
  "10.07":"Anita Anja"
  "11.07":"Kjetil Kjell"
  "12.07":"Elias Eldar"
  "13.07":"Mildrid Melissa Mia"
  "14.07":"Solfrid Solrun"
  "15.07":"Oddmund Oddrun"
  "16.07":"Susanne Sanna"
  "17.07":"Guttorm Gorm"
  "18.07":"Arnulf Ørnulf"
  "19.07":"Gerhard Gjert"
  "20.07":"Margareta Margit Marit"
  "21.07":"Johanne Janne Jane"
  "22.07":"Malene Malin Mali"
  "23.07":"Brita Brit Britt"
  "24.07":"Kristine Kristin Kristi"
  "25.07":"Jakob Jack Jim"
  "26.07":"Anna Anne Ane"
  "27.07":"Marita Rita"
  "28.07":"Reidar Reidun"
  "29.07":"Olav Ola Ole"
  "30.07":"Aurora Audhild Aud"
  "31.07":"Elin Eline"
  "01.08":"Peder Petra"
  "02.08":"Karen Karin"
  "03.08":"Oline Oliver Olve"
  "04.08":"Arnhild Arna Arnt"
  "05.08":"Osvald Oskar"
  "06.08":"Gunnlaug Gunnleiv"
  "07.08":"Didrik Doris"
  "08.08":"Evy Yvonne"
  "09.08":"Ronald Ronny"
  "10.08":"Lorents Lars Lasse"
  "11.08":"Torvald Tarald"
  "12.08":"Klara Camilla"
  "13.08":"Anny Anine Ann"
  "14.08":"Hallgeir Hallgjerd"
  "15.08":"Margot Mary Marielle"
  "16.08":"Brynjulf Brynhild"
  "17.08":"Verner Wenche"
  "18.08":"Tormod Torodd"
  "19.08":"Sigvald Sigve"
  "20.08":"Bernhard Bernt"
  "21.08":"Ragnvald Ragni"
  "22.08":"Harriet Harry"
  "23.08":"Signe Signy"
  "24.08":"Belinda Bertil"
  "25.08":"Ludvig Lovise Louise"
  "26.08":"Øyvind Eivind Even"
  "27.08":"Roald Rolf"
  "28.08":"Artur August"
  "29.08":"Johan Jone Jo"
  "30.08":"Benjamin Ben"
  "31.08":"Berta Berte"
  "01.09":"Solveig Solvår"
  "02.09":"Lisa Lise Liss"
  "03.09":"Alise Alvhild Vilde"
  "04.09":"Ida Idar"
  "05.09":"Brede Brian Njål"
  "06.09":"Sollaug Siril Siv"
  "07.09":"Regine Rose"
  "08.09":"Amalie Alma Allan"
  "09.09":"Trygve Tyra Trym"
  "10.09":"Tord Tor"
  "11.09":"Dagny Dag"
  "12.09":"Jofrid Jorid"
  "13.09":"Stian Stig"
  "14.09":"Ingebjørg Ingeborg"
  "15.09":"Aslak Eskil"
  "16.09":"Lillian Lilly"
  "17.09":"Hildebjørg Hildegunn"
  "18.09":"Henriette Henry"
  "19.09":"Konstanse Connie"
  "20.09":"Tobias Tage"
  "21.09":"Trine Trond"
  "22.09":"Kyrre Kåre"
  "23.09":"Snorre Snefrid"
  "24.09":"Jan Jens"
  "25.09":"Ingvar Yngvar"
  "26.09":"Einar Endre"
  "27.09":"Dagmar Dagrun"
  "28.09":"Lena Lene"
  "29.09":"Mikael Mikal Mikkel"
  "30.09":"Helga Helge Hege"
  "01.10":"Rebekka Remi"
  "02.10":"Live Liv"
  "03.10":"Evald Evelyn"
  "04.10":"Frans Frank"
  "05.10":"Brynjar Boye Bo"
  "06.10":"Målfrid Møyfrid"
  "07.10":"Birgitte Birgit Berit"
  "08.10":"Benedikte Bente"
  "09.10":"Leidulf Leif"
  "10.10":"Fridtjof Frida Frits"
  "11.10":"Kevin Kennet Kent"
  "12.10":"Valter Vibeke"
  "13.10":"Torgeir Terje Tarjei"
  "14.10":"Kaia Kai"
  "15.10":"Hedvig Hedda"
  "16.10":"Flemming Finn"
  "17.10":"Marta Marte"
  "18.10":"Kjersti Kjerstin"
  "19.10":"Tora Tore"
  "20.10":"Henrik Heine Henrikke"
  "21.10":"Bergljot Birger"
  "22.10":"Karianne Karine Kine"
  "23.10":"Severin Søren"
  "24.10":"Eilif Eivor"
  "25.10":"Margrete Merete Märtha"
  "26.10":"Amandus Amanda"
  "27.10":"Sturla Sture"
  "28.10":"Simon Simen"
  "29.10":"Noralf Norunn"
  "30.10":"Aksel Åmund Ove"
  "31.10":"Edit Edna"
  "01.11":"Veslemøy Vetle"
  "02.11":"Tove Tuva"
  "03.11":"Raymond Roy"
  "04.11":"Otto Ottar"
  "05.11":"Egil Egon"
  "06.11":"Leonard Lennart"
  "07.11":"Ingebrigt Ingelin"
  "08.11":"Ingvild Yngvild"
  "09.11":"Tordis Teodor"
  "10.11":"Gudbjørg Gudveig"
  "11.11":"Martin Morten Martine"
  "12.11":"Torkjell Torkil"
  "13.11":"Kirsten Kirsti"
  "14.11":"Fredrik Fred Freddy"
  "15.11":"Oddfrid Oddvar"
  "16.11":"Edmund Edgar"
  "17.11":"Hugo Hogne Hauk"
  "18.11":"Magne Magny"
  "19.11":"Elisabet Lisbet"
  "20.11":"Halvdan Helle"
  "21.11":"Mariann Marianne"
  "22.11":"Cecilie Silje Sissel"
  "23.11":"Klement Klaus"
  "24.11":"Gudrun Guro"
  "25.11":"Katarina Katrine Kari"
  "26.11":"Konrad Kurt"
  "27.11":"Torlaug Torleif"
  "28.11":"Ruben Rut"
  "29.11":"Sofie Sonja"
  "30.11":"Andreas Anders"
  "01.12":"Arnold Arnljot Arnt"
  "02.12":"Borghild Borgny Bård"
  "03.12":"Sveinung Svein"
  "04.12":"Barbara Barbro"
  "05.12":"Stine Ståle"
  "06.12":"Nils Nikolai"
  "07.12":"Hallfrid Hallstein"
  "08.12":"Marlene Marion Morgan"
  "09.12":"Anniken Annette"
  "10.12":"Judit og Jytte"
  "11.12":"Daniel Dan"
  "12.12":"Pia Peggy"
  "13.12":"Lucia Lydia"
  "14.12":"Steinar Stein"
  "15.12":"Hilda Hilde"
  "16.12":"Oddbjørg Oddbjørn"
  "17.12":"Inga Inge"
  "18.12":"Kristoffer Kate"
  "19.12":"Iselin Isak"
  "20.12":"Abraham Amund"
  "21.12":"Tomas Tom Tommy"
  "22.12":"Ingemar Ingar"
  "23.12":"Sigurd Sjur"
  "24.12":"Adam Eva"
  "25.12":"Ingen navnedag"
  "26.12":"Stefan Steffen"
  "27.12":"Narve Natalie"
  "28.12":"Unni Une Unn"
  "29.12":"Vidar Vemund"
  "30.12":"David Diana Dina"
  "31.12":"Sylfest Sylvia Sylvi"

PRIVMSG:
  "^%pnavnedag$": (source, destination) =>
    now = os.date'%d.%m'
    names = table.concat(util.split(data[now], ' '), ', ')
    say names
  "^%pnavnedag (%d+).(%d+)$": (source, destination, day, month) =>
    day = string.format('%02d', day)
    month = string.format('%02d', month)
    now = day .. '.' .. month
    names = table.concat(util.split(data[now], ' '), ', ')
    say names
  "^%pnavnedag (.*)$": (source, destination, input) =>
    for key, val in pairs(data)
      names = util.split(val, ' ')
      for name in *names
        if util.utf8.lower(name) == util.utf8.lower(input)
          say "#{key}: #{table.concat(names, ', ')}"


Fix https://gitlab.com/DBotThePony/PPM/-/issues/293

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

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

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

class PonySizeController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

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

	@LEGS_BONE_ROOT = 'LrigPelvis'

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

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

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

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

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

	@NEXT_OBJ_ID = 0

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

		@validSkeleton = true

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

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

	IsValid: => @controller\IsValid()
	GetEntity: => @controller\GetEntity()
	IsNetworked: => @controller\IsNetworked()
	AllowResize: => not @controller\IsNetworked() or ALLOW_TO_MODIFY_SCALE\GetBool()

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

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

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

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

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

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

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

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

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

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

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

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

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

	ResetNeck: (ent = @GetEntity()) =>
		return if not CLIENT
		return if not IsValid(@GetEntity())
		return if not @validSkeleton
		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 = @GetEntity()) =>
		return if not CLIENT
		return if not IsValid(ent)
		return if not @validSkeleton

		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: => @GrabData('LegsSize')
	GetLegsScale: => @GrabData('LegsSize')
	GetNeckSize: => @GrabData('NeckSize')
	GetNeckScale: => @GrabData('NeckSize')
	GetPonySize: => @GrabData('PonySize')
	GetPonyScale: => @GrabData('PonySize')

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

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

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

	CalculatePonyHeight: => (@@HULL_MAXS.z - @@HULL_MINS.z) * @GetLegsModifier() * @GetPonySize()
	CalculatePonyHeightFull: => (@@HULL_MAXS.z - @@HULL_MINS.z) * @GetLegsModifier() * @GetPonySize() * @GetNeckModifier() * 1.17
	CalculatePonyWidth: => (@@HULL_MAXS.x - @@HULL_MINS.x) * @GetLegsModifier() * @GetPonySize()

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

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

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

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

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

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

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

	ModifyJumpHeight: (ent = @GetEntity()) =>
		return if CLIENT
		return if not @GetEntity().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

	GetNeckModifier: (mult = 0.6) =>
		if @AllowResize()
			1 + (@GetNeckSize() - 1) * mult
		else
			1

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

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

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

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

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

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

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

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

		@ModifyViewOffset(ent)
		@ModifyModelScale(ent)

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

	ModifyNeck: (ent = @GetEntity()) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		size = (@GetNeckSize() - 1) * 3
		return if size\abs() * 4 <= 0.05
		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))

	@LEGS_PONY_HEIGHT_MODIF = 3.8
	@LEGS_BACK_MODIF = 2.5
	@LEGS_BACK_MODIF2 = 0.5
	@LEGS_BACK_MODIF_ = 1
	@LEGS_BACK_MODIF_2 = 0.7

	ModifyLegs: (ent = @GetEntity()) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		realSizeModify = @GetLegsSize() - 1
		return if realSizeModify\abs() * 4 <= 0.05
		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 * @@LEGS_PONY_HEIGHT_MODIF) + \GetManipulateBonePosition(@LEGS_BONE_ROOT))

			\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 * @@LEGS_BACK_MODIF_, -size * @@LEGS_BACK_MODIF_2, 0) + (boneAnimTable[@LEGS_BEHIND_1_1] or emptyVector))
			\ManipulateBonePosition(@LEGS_BEHIND_1_2, Vector(size * @@LEGS_BACK_MODIF_, -size * @@LEGS_BACK_MODIF_2, 0) + (boneAnimTable[@LEGS_BEHIND_1_2] or emptyVector))

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

			\ManipulateBonePosition(@LEGS_BEHIND_3_1, Vector(size * @@LEGS_BACK_MODIF2, 0, 0) + (boneAnimTable[@LEGS_BEHIND_3_1] or emptyVector))
			\ManipulateBonePosition(@LEGS_BEHIND_3_2, Vector(size * @@LEGS_BACK_MODIF2, 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   right_hand
-- 43   wing_l
-- 44   wing_r
-- 45   Tail01
-- 46   Tail02
-- 47   Tail03
-- 48   wing_l_bat
-- 49   wing_r_bat
-- 50   wing_open_l
-- 51   wing_open_r

PPM2.PonySizeController = PonySizeController

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

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

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

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

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

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

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

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

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

PPM2.NewPonySizeContoller = NewPonySizeContoller

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

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

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

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

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

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

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

class PonySizeController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

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

	@LEGS_BONE_ROOT = 'LrigPelvis'

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

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

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

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

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

	@NEXT_OBJ_ID = 0

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

		@validSkeleton = true

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

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

	IsValid: => @controller\IsValid()
	GetEntity: => @controller\GetEntity()
	IsNetworked: => @controller\IsNetworked()
	AllowResize: => not @controller\IsNetworked() or ALLOW_TO_MODIFY_SCALE\GetBool()

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

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

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

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

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

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

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

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

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

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

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

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

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

	ResetNeck: (ent = @GetEntity()) =>
		return if not CLIENT
		return if not IsValid(@GetEntity())
		return if not @validSkeleton
		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 = @GetEntity()) =>
		return if not CLIENT
		return if not IsValid(ent)
		return if not @validSkeleton

		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: => @GrabData('LegsSize')
	GetLegsScale: => @GrabData('LegsSize')
	GetNeckSize: => @GrabData('NeckSize')
	GetNeckScale: => @GrabData('NeckSize')
	GetPonySize: => @GrabData('PonySize')
	GetPonyScale: => @GrabData('PonySize')

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

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

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

	CalculatePonyHeight: => (@@HULL_MAXS.z - @@HULL_MINS.z) * @GetLegsModifier() * @GetPonySize()
	CalculatePonyHeightFull: => (@@HULL_MAXS.z - @@HULL_MINS.z) * @GetLegsModifier() * @GetPonySize() * @GetNeckModifier() * 1.17
	CalculatePonyWidth: => (@@HULL_MAXS.x - @@HULL_MINS.x) * @GetLegsModifier() * @GetPonySize()

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

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

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

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

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

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

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

	ModifyJumpHeight: (ent = @GetEntity()) =>
		return if CLIENT
		return if not @GetEntity().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

	GetNeckModifier: (mult = 0.6) =>
		if @AllowResize()
			1 + (@GetNeckSize() - 1) * mult
		else
			1

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

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

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

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

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

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

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

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

		@ModifyViewOffset(ent)
		@ModifyModelScale(ent)

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

	ModifyNeck: (ent = @GetEntity()) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		size = (@GetNeckSize() - 1) * 3
		return if size\abs() * 4 <= 0.05
		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 = @GetEntity()) =>
		return if not IsValid(ent)
		return if not @AllowResize()
		return if not @validSkeleton
		realSizeModify = @GetLegsSize() - 1
		return if realSizeModify\abs() * 4 <= 0.05
		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) + \GetManipulateBonePosition(@LEGS_BONE_ROOT))

			\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   right_hand
-- 43   wing_l
-- 44   wing_r
-- 45   Tail01
-- 46   Tail02
-- 47   Tail03
-- 48   wing_l_bat
-- 49   wing_r_bat
-- 50   wing_open_l
-- 51   wing_open_r

PPM2.PonySizeController = PonySizeController

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

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

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

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

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

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

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

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

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

PPM2.NewPonySizeContoller = NewPonySizeContoller

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

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

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

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


Remove SetVelocity calls

Remove SetVelocity calls

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow flight for pegasus and alicorns. It obeys PlayerNoClip hook.')
FORCE_ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight_force', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Ignore PlayerNoClip hook')
SUPPRESS_CLIENTSIDE_CHECK = CreateConVar('ppm2_sv_flight_nocheck', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Suppress PlayerNoClip clientside check (useful with bad coded addons. known are - ULX, Cinema, FAdmin)')
FLIGHT_DAMAGE = CreateConVar('ppm2_sv_flightdmg', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Damage players in flight')

class PonyflyController
	new: (controller) =>
		@controller = controller
		@speedMult = 0.5
		@speedMultDirections = 1.25
		@dragMult = 0.95
		@obbCenter = Vector(0, 0, 0)
		@obbMins = Vector(0, 0, 0)
		@obbMaxs = Vector(0, 0, 0)
		@speedMultDiv = 2
		@speedMultLift = 2
		@pitch = 0
		@yaw = 0
		@roll = 0
		@angleLerp = 1
		@lastVelocity = Vector(0, 0, 0)
		@lastState = false

	GetEntity: => @controller\GetEntity()
	GetData: => @controller
	GetController: => @controller

	Switch: (status = false) =>
		return if not IsValid(@GetEntity()) or not @GetEntity()\IsPlayer()
		return if @lastState == status
		@lastState = status
		if not status
			{:p, :y, :r} = @GetEntity()\EyeAngles()
			newAng = Angle(p, y, 0)
			@GetEntity()\SetEyeAngles(newAng)
			@GetEntity()\SetMoveType(MOVETYPE_WALK)
			@roll = 0
			@pitch = 0
			@yaw = 0
			--@GetEntity()\SetVelocity(@lastVelocity * 50)
			@lastVelocity = Vector(0, 0, 0)
		else
			@lastVelocity = Vector(0, 0, 0)
			--@GetEntity()\SetVelocity(-@GetEntity()\GetVelocity() * .97)
			@GetEntity()\SetMoveType(MOVETYPE_CUSTOM)
			@obbCenter = @GetEntity()\OBBCenter()
			@obbMins = @GetEntity()\OBBMins()
			@obbMaxs = @GetEntity()\OBBMaxs()
			@roll = 0
			@pitch = 0
			@yaw = 0

	Think: (movedata) =>
		pos     = movedata\GetOrigin()
		ang     = movedata\GetAngles()
		fwd     = ang\Forward()
		bcwd    = -fwd
		right   = ang\Right()
		left    = -right
		up      = ang\Up()
		down    = -up
		W       = movedata\KeyDown(IN_FORWARD)
		S       = movedata\KeyDown(IN_BACK)
		D       = movedata\KeyDown(IN_MOVERIGHT)
		A       = movedata\KeyDown(IN_MOVELEFT)
		CTRL    = movedata\KeyDown(IN_DUCK)
		MULT    = FrameTime() * 66

		velocity = movedata\GetVelocity()
		cSpeed = velocity\Length()
		cSpeed = 1 if cSpeed < 1
		dragSqrt = math.min(math.sqrt(cSpeed) / cSpeed * 2, 0.99)
		cSpeedMult = @speedMultDiv / cSpeed
		cSpeedMult = @speedMultDiv if cSpeedMult ~= cSpeedMult
		cSpeedLiftMult = @speedMultLift / cSpeed
		cSpeedLiftMult = @speedMultLift if cSpeedLiftMult ~= cSpeedLiftMult

		dragCalc = math.Clamp(@dragMult / dragSqrt, 0, 0.99)
		pitch = 0
		yaw = 0
		roll = 0

		hit = false
		hitLift = false

		if W
			velocity += fwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch += 20

		if S
			velocity += bcwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch -= 20

		if A
			velocity += left * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll -= 20

		if D
			velocity += right * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll += 20

		if CTRL
			velocity += Vector(0, 0, -MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if @isLiftingUp
			velocity += Vector(0, 0, MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if CLIENT
			lerpMult = game.GetTimeScale() * engine.TickInterval() * @angleLerp
			{:p, :y, :r} = ang
			p -= @pitch
			y -= @yaw
			@pitch = Lerp(lerpMult, @pitch, pitch)
			@yaw = Lerp(lerpMult, @yaw, yaw)
			@roll = Lerp(lerpMult, @roll, roll)
			p += @pitch
			y += @yaw
			r = @roll + math.sin(CurTime()) * 2
			newAng = Angle(p, y, r)
			@GetEntity()\SetEyeAngles(newAng)

		if not hit
			velocity.x *= dragCalc
			velocity.y *= dragCalc

		if not hitLift
			velocity.z *= dragCalc
			velocity.z += math.sin(CurTime() * 2) * .01

		pos += velocity

		movedata\SetVelocity(velocity)
		movedata\SetOrigin(pos)

	SetupMove: (movedata, cmd) =>
		@isLiftingUp = movedata\KeyDown(IN_JUMP)
		if @isLiftingUp
			movedata\SetButtons(movedata\GetButtons() - IN_JUMP)
		cmd\SetButtons(cmd\GetButtons() - IN_JUMP) if cmd\KeyDown(IN_JUMP)

	FinishMove: (movedata) =>
		nativeEntity = @GetEntity()
		mvPos = movedata\GetOrigin()
		pos = nativeEntity\GetPos()
		rpos = pos
		tryMove = util.TraceHull({
			filter: (ent) ->
				return false if nativeEntity == ent
				return true if not IsValid(ent)
				collision = ent\GetCollisionGroup()
				return collision ~= COLLISION_GROUP_WORLD and
					collision ~= COLLISION_GROUP_DEBRIS_TRIGGER and
					collision ~= COLLISION_GROUP_WEAPON and
					collision ~= COLLISION_GROUP_PASSABLE_DOOR and
					collision ~= COLLISION_GROUP_DEBRIS and
					ent\GetOwner() ~= nativeEntity and
					ent\GetParent() ~= nativeEntity and
					ent\IsSolid()
			mins: @obbMins
			maxs: @obbMaxs
			start: rpos
			endpos: mvPos
		})

		velocity = movedata\GetVelocity()
		newVelocity = velocity
		length = velocity\Length()

		if not tryMove.Hit
			nativeEntity\SetPos(mvPos)
		else
			if IsValid(tryMove.Entity)
				newVelocity = Vector(0, 0, 0)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			else
				newVelocity = velocity - tryMove.HitNormal * velocity\Dot(tryMove.HitNormal * 1.1)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			if length > 7 and SERVER and FLIGHT_DAMAGE\GetBool()
				dmgInfo = DamageInfo()
				dmgInfo\SetAttacker(nativeEntity)
				dmgInfo\SetInflictor(nativeEntity)
				dmgInfo\SetDamageType(DMG_CRUSH)
				calcDamage = math.Clamp((length / 4) ^ 2, 1, 100)
				if calcDamage >= 100
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 70
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 50
					nativeEntity\EmitSound("physics/body/body_medium_break#{math.random(2, 4)}.wav", 75)
				elseif calcDamage > 25
					nativeEntity\EmitSound("physics/body/body_medium_impact_hard#{math.random(1, 6)}.wav", 75)
				elseif calcDamage > 10
					nativeEntity\EmitSound("physics/body/body_medium_impact_soft#{math.random(1, 7)}.wav", 75)
				else
					nativeEntity\EmitSound("physics/flesh/flesh_impact_bullet#{math.random(1, 5)}.wav", 75)
				dmgInfo\SetDamage(calcDamage)
				nativeEntity\TakeDamageInfo(dmgInfo)

		@lastVelocity = newVelocity if IsFirstTimePredicted()

PPM2.PonyflyController = PonyflyController

import IsPonyCached, GetPonyData, GetTable, SetIK, IsNewPonyCached from FindMetaTable('Entity')
import AnimRestartGesture, AnimResetGestureSlot from FindMetaTable('Player')

hook.Add 'SetupMove', 'PPM2.Ponyfly', (movedata, cmd) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\SetupMove(movedata, cmd)

hook.Add 'Move', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\Think(movedata)

hook.Add 'FinishMove', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\FinishMove(movedata)

hook.Add 'CalcMainActivity', 'PPM2.Ponyfly', (movedata) =>
	return if not IsNewPonyCached(@)
	if data = GetPonyData(@)
		if data\GetFly()
			if not @isPlayingPPM2Anim
				@isPlayingPPM2Anim = true
				AnimRestartGesture(@, GESTURE_SLOT_CUSTOM, ACT_GMOD_NOCLIP_LAYER, false)
				SetIK(@, false) if CLIENT
			return ACT_GMOD_NOCLIP_LAYER, 370
		else
			if @isPlayingPPM2Anim
				@isPlayingPPM2Anim = false
				AnimResetGestureSlot(@, GESTURE_SLOT_CUSTOM)
				SetIK(@, true) if CLIENT

if SERVER
	concommand.Add 'ppm2_fly', =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not IsValid(@)
		return if not @IsPonyCached()
		data = @GetPonyData()
		return if not data
		if data\GetFly()
			return data\SetFly(false) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, false) or hook.Run('PPM2Fly', @, false)
			data\SetFly(false) if can
		else
			return if @GetPonyRaceFlags()\band(PPM2.RACE_HAS_WINGS) == 0
			return data\SetFly(true) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, true) or hook.Run('PPM2Fly', @, true)
			data\SetFly(true) if can
else
	lastDouble = 0
	lastMessage = 0
	lastMessage2 = 0
	FLIGHT_BIND = CreateConVar('ppm2_flight_djump', '1', {FCVAR_ARCHIVE}, 'Double press of Jump activates flight')

	hook.Add 'PlayerBindPress', 'PPM2.Ponyfly', (bind = '', pressed = false) =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not FLIGHT_BIND\GetBool()
		return if not pressed
		return if bind ~= '+jump' and bind ~= 'jump'
		if lastDouble > RealTimeL()
			return if not @IsPonyCached()
			data = @GetPonyData()
			return if not data

			if @GetPonyRaceFlags()\band(PPM2.RACE_HAS_WINGS) == 0
				if lastMessage < RealTimeL()
					lastMessage = RealTimeL() + 1
					PPM2.LChatPrint('info.ppm2.fly.pegasus')
				return

			if not FORCE_ALLOW_FLIGHT\GetBool() and not SUPPRESS_CLIENTSIDE_CHECK\GetBool()
				can = hook.Run('PlayerNoClip', @, not data\GetFly()) or hook.Run('PPM2Fly', @, not data\GetFly())
				if not can
					if lastMessage2 < RealTimeL()
						lastMessage2 = RealTimeL() + 1
						PPM2.LChatPrint('info.ppm2.fly.cannot', data\GetFly() and 'land' or 'fly')
					return

			RunConsoleCommand('ppm2_fly')
			lastDouble = 0
			return
		lastDouble = RealTimeL() + 0.2

--
-- Copyright (C) 2017-2020 DBotThePony

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.

ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow flight for pegasus and alicorns. It obeys PlayerNoClip hook.')
FORCE_ALLOW_FLIGHT = CreateConVar('ppm2_sv_flight_force', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Ignore PlayerNoClip hook')
SUPPRESS_CLIENTSIDE_CHECK = CreateConVar('ppm2_sv_flight_nocheck', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Suppress PlayerNoClip clientside check (useful with bad coded addons. known are - ULX, Cinema, FAdmin)')
FLIGHT_DAMAGE = CreateConVar('ppm2_sv_flightdmg', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Damage players in flight')

class PonyflyController
	new: (controller) =>
		@controller = controller
		@speedMult = 0.5
		@speedMultDirections = 1.25
		@dragMult = 0.95
		@obbCenter = Vector(0, 0, 0)
		@obbMins = Vector(0, 0, 0)
		@obbMaxs = Vector(0, 0, 0)
		@speedMultDiv = 2
		@speedMultLift = 2
		@pitch = 0
		@yaw = 0
		@roll = 0
		@angleLerp = 1
		@lastVelocity = Vector(0, 0, 0)
		@lastState = false

	GetEntity: => @controller\GetEntity()
	GetData: => @controller
	GetController: => @controller

	Switch: (status = false) =>
		return if not IsValid(@GetEntity()) or not @GetEntity()\IsPlayer()
		return if @lastState == status
		@lastState = status
		if not status
			{:p, :y, :r} = @GetEntity()\EyeAngles()
			newAng = Angle(p, y, 0)
			@GetEntity()\SetEyeAngles(newAng)
			@GetEntity()\SetMoveType(MOVETYPE_WALK)
			@roll = 0
			@pitch = 0
			@yaw = 0
			@GetEntity()\SetVelocity(@lastVelocity * 50)
			@lastVelocity = Vector(0, 0, 0)
		else
			@lastVelocity = Vector(0, 0, 0)
			@GetEntity()\SetVelocity(-@GetEntity()\GetVelocity() * .97)
			@GetEntity()\SetMoveType(MOVETYPE_CUSTOM)
			@obbCenter = @GetEntity()\OBBCenter()
			@obbMins = @GetEntity()\OBBMins()
			@obbMaxs = @GetEntity()\OBBMaxs()
			@roll = 0
			@pitch = 0
			@yaw = 0

	Think: (movedata) =>
		pos     = movedata\GetOrigin()
		ang     = movedata\GetAngles()
		fwd     = ang\Forward()
		bcwd    = -fwd
		right   = ang\Right()
		left    = -right
		up      = ang\Up()
		down    = -up
		W       = movedata\KeyDown(IN_FORWARD)
		S       = movedata\KeyDown(IN_BACK)
		D       = movedata\KeyDown(IN_MOVERIGHT)
		A       = movedata\KeyDown(IN_MOVELEFT)
		CTRL    = movedata\KeyDown(IN_DUCK)
		MULT    = FrameTime() * 66

		velocity = movedata\GetVelocity()
		cSpeed = velocity\Length()
		cSpeed = 1 if cSpeed < 1
		dragSqrt = math.min(math.sqrt(cSpeed) / cSpeed * 2, 0.99)
		cSpeedMult = @speedMultDiv / cSpeed
		cSpeedMult = @speedMultDiv if cSpeedMult ~= cSpeedMult
		cSpeedLiftMult = @speedMultLift / cSpeed
		cSpeedLiftMult = @speedMultLift if cSpeedLiftMult ~= cSpeedLiftMult

		dragCalc = math.Clamp(@dragMult / dragSqrt, 0, 0.99)
		pitch = 0
		yaw = 0
		roll = 0

		hit = false
		hitLift = false

		if W
			velocity += fwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch += 20

		if S
			velocity += bcwd * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			pitch -= 20

		if A
			velocity += left * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll -= 20

		if D
			velocity += right * MULT * @speedMult * cSpeedMult * @speedMultDirections
			hit = true
			roll += 20

		if CTRL
			velocity += Vector(0, 0, -MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if @isLiftingUp
			velocity += Vector(0, 0, MULT * @speedMult * cSpeedLiftMult)
			hitLift = true

		if CLIENT
			lerpMult = game.GetTimeScale() * engine.TickInterval() * @angleLerp
			{:p, :y, :r} = ang
			p -= @pitch
			y -= @yaw
			@pitch = Lerp(lerpMult, @pitch, pitch)
			@yaw = Lerp(lerpMult, @yaw, yaw)
			@roll = Lerp(lerpMult, @roll, roll)
			p += @pitch
			y += @yaw
			r = @roll + math.sin(CurTime()) * 2
			newAng = Angle(p, y, r)
			@GetEntity()\SetEyeAngles(newAng)

		if not hit
			velocity.x *= dragCalc
			velocity.y *= dragCalc

		if not hitLift
			velocity.z *= dragCalc
			velocity.z += math.sin(CurTime() * 2) * .01

		pos += velocity

		movedata\SetVelocity(velocity)
		movedata\SetOrigin(pos)

	SetupMove: (movedata, cmd) =>
		@isLiftingUp = movedata\KeyDown(IN_JUMP)
		if @isLiftingUp
			movedata\SetButtons(movedata\GetButtons() - IN_JUMP)
		cmd\SetButtons(cmd\GetButtons() - IN_JUMP) if cmd\KeyDown(IN_JUMP)

	FinishMove: (movedata) =>
		nativeEntity = @GetEntity()
		mvPos = movedata\GetOrigin()
		pos = nativeEntity\GetPos()
		rpos = pos
		tryMove = util.TraceHull({
			filter: (ent) ->
				return false if nativeEntity == ent
				return true if not IsValid(ent)
				collision = ent\GetCollisionGroup()
				return collision ~= COLLISION_GROUP_WORLD and
					collision ~= COLLISION_GROUP_DEBRIS_TRIGGER and
					collision ~= COLLISION_GROUP_WEAPON and
					collision ~= COLLISION_GROUP_PASSABLE_DOOR and
					collision ~= COLLISION_GROUP_DEBRIS and
					ent\GetOwner() ~= nativeEntity and
					ent\GetParent() ~= nativeEntity and
					ent\IsSolid()
			mins: @obbMins
			maxs: @obbMaxs
			start: rpos
			endpos: mvPos
		})

		velocity = movedata\GetVelocity()
		newVelocity = velocity
		length = velocity\Length()

		if not tryMove.Hit
			nativeEntity\SetPos(mvPos)
		else
			if IsValid(tryMove.Entity)
				newVelocity = Vector(0, 0, 0)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			else
				newVelocity = velocity - tryMove.HitNormal * velocity\Dot(tryMove.HitNormal * 1.1)
				movedata\SetVelocity(newVelocity)
				newPos = tryMove.HitPos + tryMove.HitNormal
				nativeEntity\SetPos(newPos)
				movedata\SetOrigin(newPos)
			if length > 7 and SERVER and FLIGHT_DAMAGE\GetBool()
				dmgInfo = DamageInfo()
				dmgInfo\SetAttacker(nativeEntity)
				dmgInfo\SetInflictor(nativeEntity)
				dmgInfo\SetDamageType(DMG_CRUSH)
				calcDamage = math.Clamp((length / 4) ^ 2, 1, 100)
				if calcDamage >= 100
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 70
					nativeEntity\EmitSound('physics/flesh/flesh_bloody_break.wav', 100)
				elseif calcDamage > 50
					nativeEntity\EmitSound("physics/body/body_medium_break#{math.random(2, 4)}.wav", 75)
				elseif calcDamage > 25
					nativeEntity\EmitSound("physics/body/body_medium_impact_hard#{math.random(1, 6)}.wav", 75)
				elseif calcDamage > 10
					nativeEntity\EmitSound("physics/body/body_medium_impact_soft#{math.random(1, 7)}.wav", 75)
				else
					nativeEntity\EmitSound("physics/flesh/flesh_impact_bullet#{math.random(1, 5)}.wav", 75)
				dmgInfo\SetDamage(calcDamage)
				nativeEntity\TakeDamageInfo(dmgInfo)

		@lastVelocity = newVelocity if IsFirstTimePredicted()

PPM2.PonyflyController = PonyflyController

import IsPonyCached, GetPonyData, GetTable, SetIK, IsNewPonyCached from FindMetaTable('Entity')
import AnimRestartGesture, AnimResetGestureSlot from FindMetaTable('Player')

hook.Add 'SetupMove', 'PPM2.Ponyfly', (movedata, cmd) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\SetupMove(movedata, cmd)

hook.Add 'Move', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\Think(movedata)

hook.Add 'FinishMove', 'PPM2.Ponyfly', (movedata) =>
	return if not IsPonyCached(@)
	data = GetPonyData(@)
	return if not data or not data\GetFly()
	flight = data\GetFlightController()
	return if not flight
	return flight\FinishMove(movedata)

hook.Add 'CalcMainActivity', 'PPM2.Ponyfly', (movedata) =>
	return if not IsNewPonyCached(@)
	if data = GetPonyData(@)
		if data\GetFly()
			if not @isPlayingPPM2Anim
				@isPlayingPPM2Anim = true
				AnimRestartGesture(@, GESTURE_SLOT_CUSTOM, ACT_GMOD_NOCLIP_LAYER, false)
				SetIK(@, false) if CLIENT
			return ACT_GMOD_NOCLIP_LAYER, 370
		else
			if @isPlayingPPM2Anim
				@isPlayingPPM2Anim = false
				AnimResetGestureSlot(@, GESTURE_SLOT_CUSTOM)
				SetIK(@, true) if CLIENT

if SERVER
	concommand.Add 'ppm2_fly', =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not IsValid(@)
		return if not @IsPonyCached()
		data = @GetPonyData()
		return if not data
		if data\GetFly()
			return data\SetFly(false) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, false) or hook.Run('PPM2Fly', @, false)
			data\SetFly(false) if can
		else
			return if @GetPonyRaceFlags()\band(PPM2.RACE_HAS_WINGS) == 0
			return data\SetFly(true) if FORCE_ALLOW_FLIGHT\GetBool()
			can = hook.Run('PlayerNoClip', @, true) or hook.Run('PPM2Fly', @, true)
			data\SetFly(true) if can
else
	lastDouble = 0
	lastMessage = 0
	lastMessage2 = 0
	FLIGHT_BIND = CreateConVar('ppm2_flight_djump', '1', {FCVAR_ARCHIVE}, 'Double press of Jump activates flight')

	hook.Add 'PlayerBindPress', 'PPM2.Ponyfly', (bind = '', pressed = false) =>
		return if not ALLOW_FLIGHT\GetBool()
		return if not FLIGHT_BIND\GetBool()
		return if not pressed
		return if bind ~= '+jump' and bind ~= 'jump'
		if lastDouble > RealTimeL()
			return if not @IsPonyCached()
			data = @GetPonyData()
			return if not data

			if @GetPonyRaceFlags()\band(PPM2.RACE_HAS_WINGS) == 0
				if lastMessage < RealTimeL()
					lastMessage = RealTimeL() + 1
					PPM2.LChatPrint('info.ppm2.fly.pegasus')
				return

			if not FORCE_ALLOW_FLIGHT\GetBool() and not SUPPRESS_CLIENTSIDE_CHECK\GetBool()
				can = hook.Run('PlayerNoClip', @, not data\GetFly()) or hook.Run('PPM2Fly', @, not data\GetFly())
				if not can
					if lastMessage2 < RealTimeL()
						lastMessage2 = RealTimeL() + 1
						PPM2.LChatPrint('info.ppm2.fly.cannot', data\GetFly() and 'land' or 'fly')
					return

			RunConsoleCommand('ppm2_fly')
			lastDouble = 0
			return
		lastDouble = RealTimeL() + 0.2


imageupload: progess bar

imageupload: progess bar

{:simplehttp, :json, :urlEncode} = require'util'
html2unicode = require 'html'
nixio = require'nixio'
fs = require'nixio.fs'
ltn12 = require'ltn12'

urlbase = '/image/'

safe = (fn) ->
  f, ext = fn\match'^(.*)%.(.-)$'
  f = f\gsub '[^%w%-]', ''
  return f..'.'..ext

ivar2.webserver.regUrl "#{urlbase}(.*)$", (req, res) ->
  send = (body, code, content_type) ->
    if not code then code = 200
    if not content_type then content_type = 'text/html'
    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', #body
    res\set_body body
    res\send!
    res.on_error = (res, req, err) ->
      ivar2\Log 'error', 'imageupload: error: %s', err

  file = req.url\match '/file/(.*)$'
  if file
    fn = "cache/images/#{safe file}"
    size = nixio.fs.stat(fn).size
    fd = ltn12.source.file(io.open fn, 'rb')
    --body = fd\read '*a'
    --fd\close!
    content_type = 'image/jpeg'
    if file\match '.png'
      content_type = 'image/png'
    if file\match '.svg'
      content_type = 'image/svg'
    if file\match '.mp4'
      content_type = 'video/mp4'
    if file\match '.mp3'
      content_type = 'audio/mp3'

    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', size
    res\set_body fd
    res\send!
    ivar2\Log 'error', 'imageupload: serving: %s', fn

    return

  channel = req.url\match('channel=(.+)')
  unless channel
    send 'Invalid channel', 404
    return

  channel = html2unicode channel
  unescaped_channel = channel\gsub '%%23', '#'

  html = [[
  <!DOCTYPE html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="IRCSNAP ]]..unescaped_channel..[[">
  <meta name="theme-color" content="#8f4099">

  <title>IRCSNAP</title>

Share to IRC app - ]]..unescaped_channel..[[ edition

Fill in your nick/name and optional text and then click on one of the yellow buttons to attach image. It will appear instantly on IRC!

    <form>

        <input id="sender" type="text" required>

        Your name

        <input id="text" type="text" required>

        Attach text

    </form>

Uploading. Percent complete: 0 %

      📷 Snap picture! <input type="file" accept="image/*" id="capturei" capture="camera" style="visibility:hidden;">
      📂 Browse gallery! <input type="file" accept="image/*" id="capturef" style="visibility:hidden;">
      🎥 Capture video! <input type="file" accept="video/*" capture="camcorder" id="capturev" style="visibility:hidden;">
      🎥 Upload video! <input type="file" accept="video/*" id="capturevf" style="visibility:hidden;">

        What is this?

Share to IRC is a simple web app for sharing media directly from your device to an IRC channel.

Click Tools -> Add to homescreen to create a convenient shortcut for easy and fast access to sharing

  ]]
  if req.method == 'POST'
    fn = req.headers['X-Filename']
    sender = req.headers['X-Sender'] or ''
    text = req.headers['X-Text'] or ''
    file = req.filename
    ivar2\Log 'info', "imageupload: Recieved file name: <#{fn}> datalen: #{#file}, sender: <#{sender}> text: <#{text}>, channel: <#{unescaped_channel}>"
    if fn and file
      html = 'Ok'
      realfn = "#{os.time!}-#{safe fn}"
      save = ->
        --fd = nixio.open "cache/images/#{realfn}", 'w+', 0400
        --written = fd\write file
        --fd\sync!
        --fd\close!
        -- Move tempfile to real location
        fs.move req.filename, "cache/images/#{realfn}"
        if sender ~= ''
          sender = "<#{sender\sub(1, 100)}> "
        if text ~= ''
          text = text\sub(1, 100) .. ' '
        msg = "[IRCSNAP] #{sender}#{text}http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}file/#{realfn}"
        ivar2\Privmsg unescaped_channel, msg
      ok, err = pcall(save)
      unless ok
        ivar2\Log 'error', "imageupload: Error during saving upload: %s", err
    else
      html = 'Not OK'

  send html

-- Attempt to create the cache folder.
nixio.fs.mkdir('cache/images')

PRIVMSG:
  '^%pircsnap$': (source, destination) =>
    channel = urlEncode destination
    say "http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}?channel=#{channel}"

{:simplehttp, :json, :urlEncode} = require'util'
html2unicode = require 'html'
nixio = require'nixio'
fs = require'nixio.fs'
ltn12 = require'ltn12'

urlbase = '/image/'

safe = (fn) ->
  f, ext = fn\match'^(.*)%.(.-)$'
  f = f\gsub '[^%w%-]', ''
  return f..'.'..ext

ivar2.webserver.regUrl "#{urlbase}(.*)$", (req, res) ->
  send = (body, code, content_type) ->
    if not code then code = 200
    if not content_type then content_type = 'text/html'
    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', #body
    res\set_body body
    res\send!
    res.on_error = (res, req, err) ->
      ivar2\Log 'error', 'imageupload: error: %s', err

  file = req.url\match '/file/(.*)$'
  if file
    fn = "cache/images/#{safe file}"
    size = nixio.fs.stat(fn).size
    fd = ltn12.source.file(io.open fn, 'rb')
    --body = fd\read '*a'
    --fd\close!
    content_type = 'image/jpeg'
    if file\match '.png'
      content_type = 'image/png'
    if file\match '.svg'
      content_type = 'image/svg'
    if file\match '.mp4'
      content_type = 'video/mp4'
    if file\match '.mp3'
      content_type = 'audio/mp3'

    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', size
    res\set_body fd
    res\send!
    ivar2\Log 'error', 'imageupload: serving: %s', fn

    return

  channel = req.url\match('channel=(.+)')
  unless channel
    send 'Invalid channel', 404
    return

  channel = html2unicode channel
  unescaped_channel = channel\gsub '%%23', '#'

  html = [[
  <!DOCTYPE html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="IRCSNAP ]]..unescaped_channel..[[">
  <meta name="theme-color" content="#8f4099">

  <title>IRCSNAP</title>

Share to IRC app - ]]..unescaped_channel..[[ edition

Fill in your nick/name and optional text and then click on one of the yellow buttons to attach image. It will appear instantly on IRC!

    <form>

        <input id="sender" type="text" required>

        Your name

        <input id="text" type="text" required>

        Attach text

    </form>

Uploading. Percent complete: 0 %

      📷 Snap picture! <input type="file" accept="image/*" id="capturei" capture="camera" style="visibility:hidden;">
      📂 Browse gallery! <input type="file" accept="image/*" id="capturef" style="visibility:hidden;">
      🎥 Capture video! <input type="file" accept="video/*" capture="camcorder" id="capturev" style="visibility:hidden;">
      🎥 Upload video! <input type="file" accept="video/*" id="capturevf" style="visibility:hidden;">

        What is this?

Share to IRC is a simple web app for sharing media directly from your device to an IRC channel.

Click Tools -> Add to homescreen to create a convenient shortcut for easy and fast access to sharing

  ]]
  if req.method == 'POST'
    fn = req.headers['X-Filename']
    sender = req.headers['X-Sender'] or ''
    text = req.headers['X-Text'] or ''
    file = req.filename
    ivar2\Log 'info', "imageupload: Recieved file name: <#{fn}> datalen: #{#file}, sender: <#{sender}> text: <#{text}>, channel: <#{unescaped_channel}>"
    if fn and file
      html = 'Ok'
      realfn = "#{os.time!}-#{safe fn}"
      save = ->
        --fd = nixio.open "cache/images/#{realfn}", 'w+', 0400
        --written = fd\write file
        --fd\sync!
        --fd\close!
        -- Move tempfile to real location
        fs.move req.filename, "cache/images/#{realfn}"
        if sender ~= ''
          sender = "<#{sender}> "
        msg = "[IRCSNAP] #{sender}#{text\sub(1, 100)} http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}file/#{realfn}"
        ivar2\Privmsg unescaped_channel, msg
      ok, err = pcall(save)
      unless ok
        ivar2\Log 'error', "imageupload: Error during saving upload: %s", err
    else
      html = 'Not OK'

  send html

-- Attempt to create the cache folder.
nixio.fs.mkdir('cache/images')

PRIVMSG:
  '^%pircsnap$': (source, destination) =>
    channel = urlEncode destination
    say "http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}?channel=#{channel}"


Override defaults for production

Override defaults for production

import config from require "lapis.config"

json_file = (fname) ->
  json = require "cjson"
  file = assert io.open fname
  with json.decode file\read "*a"
    file\close!

config {"production", "development"}, ->
  for k,v in pairs json_file "etc/config.json"
    set k, v

config "production", ->
    port 8080
    lua_code_cache on

--c = require("lapis.config").get "development"
--print c.site.name

import config from require "lapis.config"

json_file = (fname) ->
  json = require "cjson"
  file = assert io.open fname
  with json.decode file\read "*a"
    file\close!

config {"production", "development"}, ->
  for k,v in pairs json_file "etc/config.json"
    set k, v

--c = require("lapis.config").get "development"
--print c.site.name