utf8: use say, add flip as alias

utf8: use say, add flip as alias

Former-commit-id: 0f2123c5f667d6f7e381a495b6d707e0696f9496 [formerly d7e4786bdefe12fa4305c7c467043467651a59ab]
Former-commit-id: e674ec2ff9c86e681563c4a47892a132b2ffa338

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)

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) =>
    @Msg 'privmsg', destination, source, wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, zalgo(arg, 10)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2ud, arg)


Increased catface animation

Increased catface animation

--
-- 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	eyes_updown
-- 1	eyes_rightleft
-- 2	JawOpen
-- 3	JawClose
-- 4	Smirk
-- 5	Frown
-- 6	Stretch
-- 7	Pucker
-- 8	Grin
-- 9	CatFace
-- 10	Mouth_O
-- 11	Mouth_O2
-- 12	Mouth_Full
-- 13	Tongue_Out
-- 14	Tongue_Up
-- 15	Tongue_Down
-- 16	NoEyelashes
-- 17	Eyes_Blink
-- 18	Left_Blink
-- 19	Right_Blink
-- 20	Scrunch
-- 21	FatButt
-- 22	Stomach_Out
-- 23	Stomach_In
-- 24	Throat_Bulge
--

net.Receive 'PPM2.DamageAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\IsPlayer()
    hook.Call('PPM2_HurtAnimation', nil, ent)

net.Receive 'PPM2.KillAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\IsPlayer()
    hook.Call('PPM2_KillAnimation', nil, ent)

DISABLE_FLEXES = CreateConVar('ppm2_disable_flexes', '0', {FCVAR_ARCHIVE}, 'Disable pony flexes controllers. Saves some FPS.')

class FlexState
    new: (controller, flexName = '', flexID = 0, scale = 1, speed = 1, active = true, min = 0, max = 1, useModifiers = true) =>
        @controller = controller
        @ent = controller.ent
        @name = flexName
        @flexName = flexName
        @flexID = flexID
        @id = flexID
        @scale = scale
        @speed = speed
        @originalscale = scale
        @originalspeed = speed
        @min = min
        @max = max
        @current = -1
        @target = 0
        @speedModify = 1
        @scaleModify = 1
        @speedModifiers = {}
        @scaleModifiers = {}
        @modifiers = {}
        @modifiersNames = {}
        @useModifiers = useModifiers
        @nextModifierID = 0
        @active = active
        @activeID = "DisableFlex#{@flexName}"

    GetFlexID: => @flexID
    GetFlexName: => @flexName

    GetModifierID: (name = '') =>
        return @modifiersNames[name] if @modifiersNames[name]
        @nextModifierID += 1
        id = @nextModifierID
        @modifiersNames[name] = id
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return id
    SetModifierWeight: (modifID, val = 0) =>
        return if not modifID
        return if not @modifiers[modifID]
        @modifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @scaleModifiers[modifID]
        @scaleModifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @speedModifiers[modifID]
        @speedModifiers[modifID] = val
    ResetModifiers: (name = '') =>
        return false if not @modifiersNames[name]
        id = @modifiersNames[name]
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return true
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller
    GetValue: => @current
    GetRealValue: => @target
    SetValue: (val = @target) =>
        @current = math.Clamp(val, @min, @max) * @scale * @scaleModify
        @target = @target
    SetRealValue: (val = @target) => @target = math.Clamp(val, @min, @max) * @scale * @scaleModify

    GetScale: => @scale
    GetSpeed: => @speed
    GetScaleModify: => @scaleModify
    GetSpeedModify: => @speedModify
    GetOriginalScale: => @originalscale
    GetOriginalSpeed: => @originalspeed

    SetScale: (val = @scale) => @scale = val
    GetSpeed: (val = @speed) => @speed = val
    SetScaleModify: (val = @scaleModify) => @scaleModify = val
    GetSpeedModify: (val = @speedModify) => @speedModify = val

    GetIsActive: => @active
    SetIsActive: (val = true) => @active = val

    AddValue: (val = 0) => @SetValue(@current + val)
    AddRealValue: (val = 0) => @SetRealValue(@target + val)
    Think: (delta = 0) =>
        return if not @active
        if @useModifiers
            @target = 0
            @scale = @originalscale * @scaleModify
            @speed = @originalspeed * @speedModify
            @target += modif for modif in *@modifiers
            @scale += modif for modif in *@scaleModifiers
            @speed += modif for modif in *@speedModifiers
            @target = math.Clamp(@target, @min, @max) * @scale
        @ent = @controller.ent
        @current = Lerp(delta * 10 * @speed * @speedModify, @current, @target) if @current ~= @target
        @ent\SetFlexWeight(@flexID, @current)
    DataChanges: (state) =>
        return if state\GetKey() ~= @activeID
        @SetIsActive(not state\GetValue())
        @GetController()\RebuildIterableList()
        @Reset()
    Reset: (resetVars = true) =>
        for name, id in pairs @modifiersNames
            @speedModifiers[id] = 0
            @scaleModifiers[id] = 0
            @modifiers[id] = 0
        if resetVars
            @scaleModify = 1
            @speedModify = 1
        @scale = @originalscale * @scaleModify
        @speed = @originalspeed * @speedModify
        @target = 0
        @current = 0
        @ent\SetFlexWeight(@flexID, 0)

PPM2.FlexState = FlexState

class FlexSequence
    new: (controller, data) =>
        {
            'name': @name
            'repeat': @dorepeat
            'frames': @frames
            'time': @time
            'func': @func
            'create': @createfunc
            'ids': @flexIDsIterable
            'numid': @numid
        } = data

        @flexIDS = {}
        @flexStates = {}
        i = 1
        for id in *data.ids
            state = controller\GetFlexState(id)
            num = state\GetModifierID(@name)
            @["flex_#{id}"] = num
            @flexIDS[id] = num
            @flexStates[id] = state
            @flexStates[i] = state
            @flexIDS[i] = num
            i += 1

        @ent = controller.ent
        @controller = controller
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1
        @speed = 1
        @scale = 1
        @valid = true
        @createfunc() if @createfunc

    Reset: =>
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1

    GetController: => @controller
    GetEntity: => @ent
    GetName: => @name
    GetRepeat: => @dorepeat
    GetFrames: => @frames
    GetFrame: => @frames
    GetTime: => @time
    GetThinkFunc: => @func
    GetCreatFunc: => @createfunc
    GetSpeed: => @speed
    GetAnimationSpeed: => @speed
    GetScale: => @scale
    GetModifierID: (id = '') => @flexIDS[id]
    GetFlexState: (id = '') => @flexStates[id]

    SetModifierWeight: (id = '', val = 0) => @GetFlexState(id)\SetModifierWeight(@GetModifierID(id), val)

    IsValid: => @valid
    Think: (delta = 0) =>
        @ent = @controller.ent
        return false if not IsValid(@ent)
        return false if not @func
        if @HasFinished()
            @Stop()
            return false

        @deltaAnim = (@finish - RealTime()) / @time
        if @deltaAnim < 0
            @deltaAnim = 1
            @frame = 0
            @start = RealTime()
            @finish = @start + @time
        @frame += 1

        status = @func(delta, 1 - @deltaAnim)
        if status == false
            @Stop()
            return false

        return true
    Stop: =>
        for id in *@flexIDsIterable
            @GetController()\GetFlexState(id)\ResetModifiers(@name)
        @valid = false
    Remove: => @Stop()
    HasFinished: =>
        return false if @dorepeat
        return RealTime() > @finish

PPM2.FlexSequence = FlexSequence

class PonyFlexController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @FLEX_LIST = {
        {flex: 'eyes_updown',       scale: 1, speed: 1, active: false}
        {flex: 'eyes_rightleft',    scale: 1, speed: 1, active: false}
        {flex: 'JawOpen',           scale: 1, speed: 1, active: true}
        {flex: 'JawClose',          scale: 1, speed: 1, active: true}
        {flex: 'Smirk',             scale: 1, speed: 1, active: true}
        {flex: 'Frown',             scale: 1, speed: 1, active: true}
        {flex: 'Stretch',           scale: 1, speed: 1, active: false}
        {flex: 'Pucker',            scale: 1, speed: 1, active: false}
        {flex: 'Grin',              scale: 1, speed: 1, active: true}
        {flex: 'CatFace',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O2',          scale: 1, speed: 1, active: true}
        {flex: 'Mouth_Full',        scale: 1, speed: 1, active: false}
        {flex: 'Tongue_Out',        scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Up',         scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Down',       scale: 1, speed: 1, active: true}
        {flex: 'NoEyelashes',       scale: 1, speed: 1, active: false}
        {flex: 'Eyes_Blink',        scale: 1, speed: 1, active: false}
        {flex: 'Left_Blink',        scale: 1, speed: 1, active: true}
        {flex: 'Right_Blink',       scale: 1, speed: 1, active: true}
        {flex: 'Scrunch',           scale: 1, speed: 1, active: true}
        {flex: 'FatButt',           scale: 1, speed: 1, active: false}
        {flex: 'Stomach_Out',       scale: 1, speed: 1, active: true}
        {flex: 'Stomach_In',        scale: 1, speed: 1, active: true}
        {flex: 'Throat_Bulge',      scale: 1, speed: 1, active: false}
    }

    @FLEX_SEQUENCES = {
        {
            'name': 'eyes_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Left_Blink', 'Right_Blink'}
            'func': (delta, timeOfAnim) =>
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                value = math.abs(math.sin(RealTime() * .5) * .15)
                leftState\SetModifierWeight(left, value)
                rightState\SetModifierWeight(right, value)
        }

        {
            'name': 'body_idle'
            'autostart': true
            'repeat': true
            'time': 2
            'ids': {'Stomach_Out', 'Stomach_In'}
            'func': (delta, timeOfAnim) =>
                In, Out = @GetModifierID(1), @GetModifierID(2)
                InState, OutState = @GetFlexState(1), @GetFlexState(2)
                abs = math.abs(0.5 - timeOfAnim)
                InState\SetModifierWeight(In, abs)
                OutState\SetModifierWeight(Out, abs)
        }

        {
            'name': 'health_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Frown', 'Left_Blink', 'Right_Blink', 'Scrunch', 'Mouth_O', 'JawOpen', 'Grin'}
            'func': (delta, timeOfAnim) =>
                frown = @GetModifierID(1)
                frownState = @GetFlexState(1)
                left, right = @GetModifierID(2), @GetModifierID(3)
                leftState, rightState = @GetFlexState(2), @GetFlexState(3)
                Mouth_O, Mouth_OState = @GetModifierID(4), @GetFlexState(4)
                Scrunch = @GetModifierID(4)
                ScrunchState = @GetFlexState(4)

                hp, mhp = @ent\Health(), @ent\GetMaxHealth()
                mhp = 1 if mhp == 0
                div = hp / mhp
                strength = math.Clamp(1.5 - div * 1.5, 0, 1)
                frownState\SetModifierWeight(frown, strength)
                ScrunchState\SetModifierWeight(Scrunch, strength * .5)
                leftState\SetModifierWeight(left, strength * .1)
                rightState\SetModifierWeight(right, strength * .1)
                Mouth_OState\SetModifierWeight(Mouth_O, strength * .8)

                JawOpen = @GetModifierID(6)
                JawOpenState = @GetFlexState(6)

                if strength > .75
                    JawOpenState\SetModifierWeight(JawOpen, strength * .2 + math.sin(RealTime() * strength * 3) * .1)
                else
                    JawOpenState\SetModifierWeight(JawOpen, 0)

                if div >= 2
                    @SetModifierWeight(7, .5)
                else
                    @SetModifierWeight(7, 0)
        }

        {
            'name': 'greeny'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                strength = .5 + math.sin(RealTime() * 2) * .25
                GrinState\SetModifierWeight(Grin, strength)
        }

        {
            'name': 'big_grin'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'xd'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin', 'Left_Blink', 'Right_Blink', 'JawOpen'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, .6)

                Left_Blink = @GetModifierID(2)
                Left_BlinkState = @GetFlexState(2)
                Left_BlinkState\SetModifierWeight(Left_Blink, .9)

                Right_Blink = @GetModifierID(3)
                Right_BlinkState = @GetFlexState(3)
                Right_BlinkState\SetModifierWeight(Right_Blink, .9)

                JawOpen = @GetModifierID(4)
                JawOpenState = @GetFlexState(4)
                JawOpenState\SetModifierScale(JawOpen, 2)
                JawOpenState\SetModifierWeight(JawOpen, (timeOfAnim % .1) * 2)
        }

        {
            'name': 'tongue'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'JawOpen', 'Tongue_Out'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .1)
                @SetModifierWeight(2, 1)
        }

        {
            'name': 'sad'
            'autostart': false
            'repeat': false
            'time': 4
            'ids': {'Frown'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, 1)
        }

        {
            'name': 'cat'
            'autostart': false
            'repeat': false
            'time': 5
            'ids': {'CatFace'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'ooo'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Mouth_O2', 'Mouth_O'}
            'func': (delta, timeOfAnim) =>
                timeOfAnim *= 2
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
                Grin = @GetModifierID(2)
                GrinState = @GetFlexState(2)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
        }

        {
            'name': 'talk'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'talk_endless'
            'autostart': false
            'repeat': true
            'time': 4
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                volume = @ent\VoiceVolume() * 6
                jaw *= volume
                out *= volume
                up *= volume
                down *= volume
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'eyes_blink'
            'autostart': true
            'repeat': true
            'time': 7
            'ids': {'Left_Blink', 'Right_Blink'}
            'create': =>
                @nextBlink = math.random(300, 600) / 1000
                @nextBlinkLength = math.random(50, 75) / 1000
                @min, @max = @nextBlink, @nextBlink + @nextBlinkLength
            'func': (delta, timeOfAnim) =>
                if @min > timeOfAnim or @max < timeOfAnim
                    if @blinkHit
                        @blinkHit = false
                        left, right = @GetModifierID(1), @GetModifierID(2)
                        leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                        leftState\SetModifierWeight(left, 0)
                        rightState\SetModifierWeight(right, 0)
                    return
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                leftState\SetModifierWeight(left, .9)
                rightState\SetModifierWeight(right, .9)
                @blinkHit = true
        }

        {
            'name': 'hurt'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Frown', 'Grin', 'Scrunch'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .08)
                @SetModifierWeight(2, .69)
                @SetModifierWeight(3, .36)
                @SetModifierWeight(4, .81)
        }

        {
            'name': 'kill_grin'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Smirk', 'Frown', 'Grin'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .51)
                @SetModifierWeight(2, .38)
                @SetModifierWeight(3, .66)
        }
    }

    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
        for i, flex in pairs child.FLEX_LIST
            flex.id = i - 1
            flex.targetName = "target#{flex.flex}"
        child.FLEX_IDS = {flex.id, flex for flex in *child.FLEX_LIST}
        child.FLEX_TABLE = {flex.flex, flex for flex in *child.FLEX_LIST}
        seq.numid = i for i, seq in pairs child.FLEX_SEQUENCES
        child.FLEX_SEQUENCES_TABLE = {seq.name, seq for seq in *child.FLEX_SEQUENCES}
        child.FLEX_SEQUENCES_TABLE[seq.numid] = seq for seq in *child.FLEX_SEQUENCES
    @__inherited(@)

    @NEXT_HOOK_ID = 0

    new: (data) =>
        @controller = data
        @ent = data.ent
        @states = [FlexState(@, flex, id, scale, speed, active) for {:flex, :id, :scale, :speed, :active} in *@@FLEX_LIST]
        @statesTable = {state\GetFlexName(), state for state in *@states}
        @statesTable[state\GetFlexName()\lower()] = state for state in *@states
        @statesTable[state\GetFlexID()] = state for state in *@states
        @RebuildIterableList()
        @hooks = {}
        @@NEXT_HOOK_ID += 1
        @fid = @@NEXT_HOOK_ID
        @hookID = "PPM2.FlexController.#{@@NEXT_HOOK_ID}"
        @lastThink = RealTime()
        @currentSequences = {}
        @currentSequencesIterable = {}
        @ResetSequences()
        @Hook('OnPlayerChat', @OnPlayerChat)
        @Hook('PlayerStartVoice', @PlayerStartVoice)
        @Hook('PlayerEndVoice', @PlayerEndVoice)
        @Hook('PPM2_HurtAnimation', @PPM2_HurtAnimation)
        @Hook('PPM2_KillAnimation', @PPM2_KillAnimation)

    StartSequence: (seqID = '') =>
        return @currentSequences[seqID] if @currentSequences[seqID]
        return if not @@FLEX_SEQUENCES_TABLE[seqID]
        @currentSequences[seqID] = FlexSequence(@, @@FLEX_SEQUENCES_TABLE[seqID])
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return @currentSequences[seqID]

    RestartSequence: (seqID = '') =>
        if @currentSequences[seqID]
            @currentSequences[seqID]\Reset()
            return @currentSequences[seqID]
        return @StartSequence(seqID)

    EndSequence: (seqID = '', callStop = true) =>
        return false if not @currentSequences[seqID]
        @currentSequences[seqID]\Stop() if callStop
        @currentSequences[seqID] = nil
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return true

    ResetSequences: =>
        for seq in *@currentSequencesIterable
            seq\Stop()

        @currentSequences = {}
        @currentSequencesIterable = {}
        state\Reset(false) for state in *@statesIterable

        for seq in *@@FLEX_SEQUENCES
            continue if not seq.autostart
            @StartSequence(seq.name)

    PlayerRespawn: =>
        @ResetSequences()

    HasSequence: (seqID = '') => @currentSequences[seqID] and true or false

    GetFlexState: (name = '') => @statesTable[name]
    RebuildIterableList: =>
        @statesIterable = for state in *@states
            continue if not state\GetIsActive()
            state
    DataChanges: (state) =>
        flexState\DataChanges(state) for flexState in *@states
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller

    Hook: (id, func) =>
        newFunc = (...) ->
            if not IsValid(@ent)
                @ent = @GetData().ent
            if not IsValid(@ent) or @GetData()\GetData() ~= @ent\GetPonyData()
                @RemoveHooks()
                return
            func(@, ...)
            return nil
        hook.Add id, @hookID, newFunc
        table.insert(@hooks, id)

    OnPlayerChat: (ply = NULL, text = '', teamOnly = false, isDead = false) =>
        return if ply ~= @ent or teamOnly or isDead
        switch text\lower()
            when 'o'
                @RestartSequence('ooo')
            when ':o'
                @RestartSequence('ooo')
            when 'о'
                @RestartSequence('ooo')
            when 'О'
                @RestartSequence('ooo')
            when ':о'
                @RestartSequence('ooo')
            when ':О'
                @RestartSequence('ooo')
            when ':3'
                @RestartSequence('cat')
            when ':з'
                @RestartSequence('cat')
            when ':d'
                @RestartSequence('big_grin')
            when 'xd'
                @RestartSequence('xd')
            when 'exdi'
                @RestartSequence('xd')
            when ':p'
                @RestartSequence('tongue')
            when ':р'
                @RestartSequence('tongue')
            when ':Р'
                @RestartSequence('tongue')
            when ':c'
                @RestartSequence('sad')
            when ':('
                @RestartSequence('sad')
            when '('
                @RestartSequence('sad')
            when ':с'
                @RestartSequence('sad')
            when ':С'
                @RestartSequence('sad')
            else
                if string.find(text, 'hehehe') or string.find(text, 'hahaha')
                    @RestartSequence('greeny')
                else
                    @RestartSequence('talk')
    PlayerStartVoice: (ply = NULL) =>
        return if ply ~= @ent
        @StartSequence('talk_endless')
    PlayerEndVoice: (ply = NULL) =>
        return if ply ~= @ent
        @EndSequence('talk_endless')
    PPM2_HurtAnimation: (ply = NULL) =>
        return if ply ~= @ent
        @RestartSequence('hurt')
    PPM2_KillAnimation: (ply = NULL) =>
        return if ply ~= @ent
        @RestartSequence('kill_grin')

    RemoveHooks: =>
        for iHook in *@hooks
            hook.Remove iHook, @hookID

    Think: =>
        return if DISABLE_FLEXES\GetBool()
        if not IsValid(@ent)
            @ent = @GetData().ent
        return if not IsValid(@ent) or @ent\IsDormant() or not @ent\Alive()
        delta = RealTime() - @lastThink
        @lastThink = RealTime()
        state\Think(delta) for state in *@statesIterable
        for seq in *@currentSequencesIterable
            if not seq\IsValid()
                @EndSequence(seq\GetName(), false)
                break
            seq\Think(delta)

do
    ppm2_disable_flexes = (cvar, oldval, newval) ->
        for ply in *player.GetAll()
            data = ply\GetPonyData()
            continue if not data
            flex = data\GetFlexController()
            continue if not flex
            flex\ResetSequences()
    cvars.AddChangeCallback 'ppm2_disable_flexes', ppm2_disable_flexes, 'ppm2_disable_flexes'

PPM2.PonyFlexController = PonyFlexController
PPM2.GetFlexController = (model = 'models/ppm/player_default_base_new.mdl') -> PonyFlexController.AVALIABLE_CONTROLLERS[model]

--
-- 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	eyes_updown
-- 1	eyes_rightleft
-- 2	JawOpen
-- 3	JawClose
-- 4	Smirk
-- 5	Frown
-- 6	Stretch
-- 7	Pucker
-- 8	Grin
-- 9	CatFace
-- 10	Mouth_O
-- 11	Mouth_O2
-- 12	Mouth_Full
-- 13	Tongue_Out
-- 14	Tongue_Up
-- 15	Tongue_Down
-- 16	NoEyelashes
-- 17	Eyes_Blink
-- 18	Left_Blink
-- 19	Right_Blink
-- 20	Scrunch
-- 21	FatButt
-- 22	Stomach_Out
-- 23	Stomach_In
-- 24	Throat_Bulge
--

net.Receive 'PPM2.DamageAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\IsPlayer()
    hook.Call('PPM2_HurtAnimation', nil, ent)

net.Receive 'PPM2.KillAnimation', ->
    ent = net.ReadEntity()
    return if not IsValid(ent) or not ent\IsPlayer()
    hook.Call('PPM2_KillAnimation', nil, ent)

DISABLE_FLEXES = CreateConVar('ppm2_disable_flexes', '0', {FCVAR_ARCHIVE}, 'Disable pony flexes controllers. Saves some FPS.')

class FlexState
    new: (controller, flexName = '', flexID = 0, scale = 1, speed = 1, active = true, min = 0, max = 1, useModifiers = true) =>
        @controller = controller
        @ent = controller.ent
        @name = flexName
        @flexName = flexName
        @flexID = flexID
        @id = flexID
        @scale = scale
        @speed = speed
        @originalscale = scale
        @originalspeed = speed
        @min = min
        @max = max
        @current = -1
        @target = 0
        @speedModify = 1
        @scaleModify = 1
        @speedModifiers = {}
        @scaleModifiers = {}
        @modifiers = {}
        @modifiersNames = {}
        @useModifiers = useModifiers
        @nextModifierID = 0
        @active = active
        @activeID = "DisableFlex#{@flexName}"

    GetFlexID: => @flexID
    GetFlexName: => @flexName

    GetModifierID: (name = '') =>
        return @modifiersNames[name] if @modifiersNames[name]
        @nextModifierID += 1
        id = @nextModifierID
        @modifiersNames[name] = id
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return id
    SetModifierWeight: (modifID, val = 0) =>
        return if not modifID
        return if not @modifiers[modifID]
        @modifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @scaleModifiers[modifID]
        @scaleModifiers[modifID] = val
    SetModifierScale: (modifID, val = 0) =>
        return if not modifID
        return if not @speedModifiers[modifID]
        @speedModifiers[modifID] = val
    ResetModifiers: (name = '') =>
        return false if not @modifiersNames[name]
        id = @modifiersNames[name]
        @speedModifiers[id] = 0
        @scaleModifiers[id] = 0
        @modifiers[id] = 0
        return true
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller
    GetValue: => @current
    GetRealValue: => @target
    SetValue: (val = @target) =>
        @current = math.Clamp(val, @min, @max) * @scale * @scaleModify
        @target = @target
    SetRealValue: (val = @target) => @target = math.Clamp(val, @min, @max) * @scale * @scaleModify

    GetScale: => @scale
    GetSpeed: => @speed
    GetScaleModify: => @scaleModify
    GetSpeedModify: => @speedModify
    GetOriginalScale: => @originalscale
    GetOriginalSpeed: => @originalspeed

    SetScale: (val = @scale) => @scale = val
    GetSpeed: (val = @speed) => @speed = val
    SetScaleModify: (val = @scaleModify) => @scaleModify = val
    GetSpeedModify: (val = @speedModify) => @speedModify = val

    GetIsActive: => @active
    SetIsActive: (val = true) => @active = val

    AddValue: (val = 0) => @SetValue(@current + val)
    AddRealValue: (val = 0) => @SetRealValue(@target + val)
    Think: (delta = 0) =>
        return if not @active
        if @useModifiers
            @target = 0
            @scale = @originalscale * @scaleModify
            @speed = @originalspeed * @speedModify
            @target += modif for modif in *@modifiers
            @scale += modif for modif in *@scaleModifiers
            @speed += modif for modif in *@speedModifiers
            @target = math.Clamp(@target, @min, @max) * @scale
        @ent = @controller.ent
        @current = Lerp(delta * 10 * @speed * @speedModify, @current, @target) if @current ~= @target
        @ent\SetFlexWeight(@flexID, @current)
    DataChanges: (state) =>
        return if state\GetKey() ~= @activeID
        @SetIsActive(not state\GetValue())
        @GetController()\RebuildIterableList()
        @Reset()
    Reset: (resetVars = true) =>
        for name, id in pairs @modifiersNames
            @speedModifiers[id] = 0
            @scaleModifiers[id] = 0
            @modifiers[id] = 0
        if resetVars
            @scaleModify = 1
            @speedModify = 1
        @scale = @originalscale * @scaleModify
        @speed = @originalspeed * @speedModify
        @target = 0
        @current = 0
        @ent\SetFlexWeight(@flexID, 0)

PPM2.FlexState = FlexState

class FlexSequence
    new: (controller, data) =>
        {
            'name': @name
            'repeat': @dorepeat
            'frames': @frames
            'time': @time
            'func': @func
            'create': @createfunc
            'ids': @flexIDsIterable
            'numid': @numid
        } = data

        @flexIDS = {}
        @flexStates = {}
        i = 1
        for id in *data.ids
            state = controller\GetFlexState(id)
            num = state\GetModifierID(@name)
            @["flex_#{id}"] = num
            @flexIDS[id] = num
            @flexStates[id] = state
            @flexStates[i] = state
            @flexIDS[i] = num
            i += 1

        @ent = controller.ent
        @controller = controller
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1
        @speed = 1
        @scale = 1
        @valid = true
        @createfunc() if @createfunc

    Reset: =>
        @frame = 0
        @start = RealTime()
        @finish = @start + @time
        @deltaAnim = 1

    GetController: => @controller
    GetEntity: => @ent
    GetName: => @name
    GetRepeat: => @dorepeat
    GetFrames: => @frames
    GetFrame: => @frames
    GetTime: => @time
    GetThinkFunc: => @func
    GetCreatFunc: => @createfunc
    GetSpeed: => @speed
    GetAnimationSpeed: => @speed
    GetScale: => @scale
    GetModifierID: (id = '') => @flexIDS[id]
    GetFlexState: (id = '') => @flexStates[id]

    SetModifierWeight: (id = '', val = 0) => @GetFlexState(id)\SetModifierWeight(@GetModifierID(id), val)

    IsValid: => @valid
    Think: (delta = 0) =>
        @ent = @controller.ent
        return false if not IsValid(@ent)
        return false if not @func
        if @HasFinished()
            @Stop()
            return false

        @deltaAnim = (@finish - RealTime()) / @time
        if @deltaAnim < 0
            @deltaAnim = 1
            @frame = 0
            @start = RealTime()
            @finish = @start + @time
        @frame += 1

        status = @func(delta, 1 - @deltaAnim)
        if status == false
            @Stop()
            return false

        return true
    Stop: =>
        for id in *@flexIDsIterable
            @GetController()\GetFlexState(id)\ResetModifiers(@name)
        @valid = false
    Remove: => @Stop()
    HasFinished: =>
        return false if @dorepeat
        return RealTime() > @finish

PPM2.FlexSequence = FlexSequence

class PonyFlexController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @FLEX_LIST = {
        {flex: 'eyes_updown',       scale: 1, speed: 1, active: false}
        {flex: 'eyes_rightleft',    scale: 1, speed: 1, active: false}
        {flex: 'JawOpen',           scale: 1, speed: 1, active: true}
        {flex: 'JawClose',          scale: 1, speed: 1, active: true}
        {flex: 'Smirk',             scale: 1, speed: 1, active: true}
        {flex: 'Frown',             scale: 1, speed: 1, active: true}
        {flex: 'Stretch',           scale: 1, speed: 1, active: false}
        {flex: 'Pucker',            scale: 1, speed: 1, active: false}
        {flex: 'Grin',              scale: 1, speed: 1, active: true}
        {flex: 'CatFace',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O',           scale: 1, speed: 1, active: true}
        {flex: 'Mouth_O2',          scale: 1, speed: 1, active: true}
        {flex: 'Mouth_Full',        scale: 1, speed: 1, active: false}
        {flex: 'Tongue_Out',        scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Up',         scale: 1, speed: 1, active: true}
        {flex: 'Tongue_Down',       scale: 1, speed: 1, active: true}
        {flex: 'NoEyelashes',       scale: 1, speed: 1, active: false}
        {flex: 'Eyes_Blink',        scale: 1, speed: 1, active: false}
        {flex: 'Left_Blink',        scale: 1, speed: 1, active: true}
        {flex: 'Right_Blink',       scale: 1, speed: 1, active: true}
        {flex: 'Scrunch',           scale: 1, speed: 1, active: true}
        {flex: 'FatButt',           scale: 1, speed: 1, active: false}
        {flex: 'Stomach_Out',       scale: 1, speed: 1, active: true}
        {flex: 'Stomach_In',        scale: 1, speed: 1, active: true}
        {flex: 'Throat_Bulge',      scale: 1, speed: 1, active: false}
    }

    @FLEX_SEQUENCES = {
        {
            'name': 'eyes_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Left_Blink', 'Right_Blink'}
            'func': (delta, timeOfAnim) =>
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                value = math.abs(math.sin(RealTime() * .5) * .15)
                leftState\SetModifierWeight(left, value)
                rightState\SetModifierWeight(right, value)
        }

        {
            'name': 'body_idle'
            'autostart': true
            'repeat': true
            'time': 2
            'ids': {'Stomach_Out', 'Stomach_In'}
            'func': (delta, timeOfAnim) =>
                In, Out = @GetModifierID(1), @GetModifierID(2)
                InState, OutState = @GetFlexState(1), @GetFlexState(2)
                abs = math.abs(0.5 - timeOfAnim)
                InState\SetModifierWeight(In, abs)
                OutState\SetModifierWeight(Out, abs)
        }

        {
            'name': 'health_idle'
            'autostart': true
            'repeat': true
            'time': 5
            'ids': {'Frown', 'Left_Blink', 'Right_Blink', 'Scrunch', 'Mouth_O', 'JawOpen', 'Grin'}
            'func': (delta, timeOfAnim) =>
                frown = @GetModifierID(1)
                frownState = @GetFlexState(1)
                left, right = @GetModifierID(2), @GetModifierID(3)
                leftState, rightState = @GetFlexState(2), @GetFlexState(3)
                Mouth_O, Mouth_OState = @GetModifierID(4), @GetFlexState(4)
                Scrunch = @GetModifierID(4)
                ScrunchState = @GetFlexState(4)

                hp, mhp = @ent\Health(), @ent\GetMaxHealth()
                mhp = 1 if mhp == 0
                div = hp / mhp
                strength = math.Clamp(1.5 - div * 1.5, 0, 1)
                frownState\SetModifierWeight(frown, strength)
                ScrunchState\SetModifierWeight(Scrunch, strength * .5)
                leftState\SetModifierWeight(left, strength * .1)
                rightState\SetModifierWeight(right, strength * .1)
                Mouth_OState\SetModifierWeight(Mouth_O, strength * .8)

                JawOpen = @GetModifierID(6)
                JawOpenState = @GetFlexState(6)

                if strength > .75
                    JawOpenState\SetModifierWeight(JawOpen, strength * .2 + math.sin(RealTime() * strength * 3) * .1)
                else
                    JawOpenState\SetModifierWeight(JawOpen, 0)

                if div >= 2
                    @SetModifierWeight(7, .5)
                else
                    @SetModifierWeight(7, 0)
        }

        {
            'name': 'greeny'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                strength = .5 + math.sin(RealTime() * 2) * .25
                GrinState\SetModifierWeight(Grin, strength)
        }

        {
            'name': 'big_grin'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'xd'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Grin', 'Left_Blink', 'Right_Blink', 'JawOpen'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, .6)

                Left_Blink = @GetModifierID(2)
                Left_BlinkState = @GetFlexState(2)
                Left_BlinkState\SetModifierWeight(Left_Blink, .9)

                Right_Blink = @GetModifierID(3)
                Right_BlinkState = @GetFlexState(3)
                Right_BlinkState\SetModifierWeight(Right_Blink, .9)

                JawOpen = @GetModifierID(4)
                JawOpenState = @GetFlexState(4)
                JawOpenState\SetModifierScale(JawOpen, 2)
                JawOpenState\SetModifierWeight(JawOpen, (timeOfAnim % .1) * 2)
        }

        {
            'name': 'tongue'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'JawOpen', 'Tongue_Out'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .1)
                @SetModifierWeight(2, 1)
        }

        {
            'name': 'sad'
            'autostart': false
            'repeat': false
            'time': 4
            'ids': {'Frown'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, 1)
        }

        {
            'name': 'cat'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'CatFace'}
            'func': (delta, timeOfAnim) =>
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, 1)
        }

        {
            'name': 'ooo'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'Mouth_O2', 'Mouth_O'}
            'func': (delta, timeOfAnim) =>
                timeOfAnim *= 2
                Grin = @GetModifierID(1)
                GrinState = @GetFlexState(1)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
                Grin = @GetModifierID(2)
                GrinState = @GetFlexState(2)
                GrinState\SetModifierWeight(Grin, timeOfAnim)
        }

        {
            'name': 'talk'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'talk_endless'
            'autostart': false
            'repeat': true
            'time': 4
            'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
            'create': =>
                @talkAnim = for i = 0, 1, 0.05
                    rand = math.random(1, 100) / 100
                    if rand <= .25
                        {1 * rand, 0.4 * rand, 2 * rand, 0}
                    elseif rand >= .25 and rand < .4
                        rand *= .8
                        {2 * rand, .6 * rand, 0, 1 * rand}
                    elseif rand >= .4 and rand < .75
                        rand *= .6
                        {1 * rand, 0, 1 * rand, 2 * rand}
                    elseif rand >= .75
                        rand *= .4
                        {1.5 * rand, 0, 1 * rand, 0}
            'func': (delta, timeOfAnim) =>
                JawOpen = @GetModifierID(1)
                JawOpenState = @GetFlexState(1)
                Tongue_OutOpen = @GetModifierID(2)
                Tongue_OutOpenState = @GetFlexState(2)
                Tongue_UpOpen = @GetModifierID(3)
                Tongue_UpOpenState = @GetFlexState(3)
                Tongue_DownOpen = @GetModifierID(4)
                Tongue_DownOpenState = @GetFlexState(4)
                cPos = math.floor(timeOfAnim * 20) + 1
                data = @talkAnim[cPos]
                return if not data
                {jaw, out, up, down} = data
                volume = @ent\VoiceVolume() * 6
                jaw *= volume
                out *= volume
                up *= volume
                down *= volume
                JawOpenState\SetModifierWeight(JawOpen, jaw)
                Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
                Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
                Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
        }

        {
            'name': 'eyes_blink'
            'autostart': true
            'repeat': true
            'time': 7
            'ids': {'Left_Blink', 'Right_Blink'}
            'create': =>
                @nextBlink = math.random(300, 600) / 1000
                @nextBlinkLength = math.random(50, 75) / 1000
                @min, @max = @nextBlink, @nextBlink + @nextBlinkLength
            'func': (delta, timeOfAnim) =>
                if @min > timeOfAnim or @max < timeOfAnim
                    if @blinkHit
                        @blinkHit = false
                        left, right = @GetModifierID(1), @GetModifierID(2)
                        leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                        leftState\SetModifierWeight(left, 0)
                        rightState\SetModifierWeight(right, 0)
                    return
                left, right = @GetModifierID(1), @GetModifierID(2)
                leftState, rightState = @GetFlexState(1), @GetFlexState(2)
                leftState\SetModifierWeight(left, .9)
                rightState\SetModifierWeight(right, .9)
                @blinkHit = true
        }

        {
            'name': 'hurt'
            'autostart': false
            'repeat': false
            'time': 2
            'ids': {'JawOpen', 'Frown', 'Grin', 'Scrunch'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .08)
                @SetModifierWeight(2, .69)
                @SetModifierWeight(3, .36)
                @SetModifierWeight(4, .81)
        }

        {
            'name': 'kill_grin'
            'autostart': false
            'repeat': false
            'time': 3
            'ids': {'Smirk', 'Frown', 'Grin'}
            'func': (delta, timeOfAnim) =>
                @SetModifierWeight(1, .51)
                @SetModifierWeight(2, .38)
                @SetModifierWeight(3, .66)
        }
    }

    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
        for i, flex in pairs child.FLEX_LIST
            flex.id = i - 1
            flex.targetName = "target#{flex.flex}"
        child.FLEX_IDS = {flex.id, flex for flex in *child.FLEX_LIST}
        child.FLEX_TABLE = {flex.flex, flex for flex in *child.FLEX_LIST}
        seq.numid = i for i, seq in pairs child.FLEX_SEQUENCES
        child.FLEX_SEQUENCES_TABLE = {seq.name, seq for seq in *child.FLEX_SEQUENCES}
        child.FLEX_SEQUENCES_TABLE[seq.numid] = seq for seq in *child.FLEX_SEQUENCES
    @__inherited(@)

    @NEXT_HOOK_ID = 0

    new: (data) =>
        @controller = data
        @ent = data.ent
        @states = [FlexState(@, flex, id, scale, speed, active) for {:flex, :id, :scale, :speed, :active} in *@@FLEX_LIST]
        @statesTable = {state\GetFlexName(), state for state in *@states}
        @statesTable[state\GetFlexName()\lower()] = state for state in *@states
        @statesTable[state\GetFlexID()] = state for state in *@states
        @RebuildIterableList()
        @hooks = {}
        @@NEXT_HOOK_ID += 1
        @fid = @@NEXT_HOOK_ID
        @hookID = "PPM2.FlexController.#{@@NEXT_HOOK_ID}"
        @lastThink = RealTime()
        @currentSequences = {}
        @currentSequencesIterable = {}
        @ResetSequences()
        @Hook('OnPlayerChat', @OnPlayerChat)
        @Hook('PlayerStartVoice', @PlayerStartVoice)
        @Hook('PlayerEndVoice', @PlayerEndVoice)
        @Hook('PPM2_HurtAnimation', @PPM2_HurtAnimation)
        @Hook('PPM2_KillAnimation', @PPM2_KillAnimation)

    StartSequence: (seqID = '') =>
        return @currentSequences[seqID] if @currentSequences[seqID]
        return if not @@FLEX_SEQUENCES_TABLE[seqID]
        @currentSequences[seqID] = FlexSequence(@, @@FLEX_SEQUENCES_TABLE[seqID])
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return @currentSequences[seqID]

    RestartSequence: (seqID = '') =>
        if @currentSequences[seqID]
            @currentSequences[seqID]\Reset()
            return @currentSequences[seqID]
        return @StartSequence(seqID)

    EndSequence: (seqID = '', callStop = true) =>
        return false if not @currentSequences[seqID]
        @currentSequences[seqID]\Stop() if callStop
        @currentSequences[seqID] = nil
        @currentSequencesIterable = [seq for i, seq in pairs @currentSequences]
        return true

    ResetSequences: =>
        for seq in *@currentSequencesIterable
            seq\Stop()

        @currentSequences = {}
        @currentSequencesIterable = {}
        state\Reset(false) for state in *@statesIterable

        for seq in *@@FLEX_SEQUENCES
            continue if not seq.autostart
            @StartSequence(seq.name)

    PlayerRespawn: =>
        @ResetSequences()

    HasSequence: (seqID = '') => @currentSequences[seqID] and true or false

    GetFlexState: (name = '') => @statesTable[name]
    RebuildIterableList: =>
        @statesIterable = for state in *@states
            continue if not state\GetIsActive()
            state
    DataChanges: (state) =>
        flexState\DataChanges(state) for flexState in *@states
    GetEntity: => @ent
    GetData: => @controller
    GetController: => @controller

    Hook: (id, func) =>
        newFunc = (...) ->
            if not IsValid(@ent)
                @ent = @GetData().ent
            if not IsValid(@ent) or @GetData()\GetData() ~= @ent\GetPonyData()
                @RemoveHooks()
                return
            func(@, ...)
            return nil
        hook.Add id, @hookID, newFunc
        table.insert(@hooks, id)

    OnPlayerChat: (ply = NULL, text = '', teamOnly = false, isDead = false) =>
        return if ply ~= @ent or teamOnly or isDead
        switch text\lower()
            when 'o'
                @RestartSequence('ooo')
            when ':o'
                @RestartSequence('ooo')
            when 'о'
                @RestartSequence('ooo')
            when 'О'
                @RestartSequence('ooo')
            when ':о'
                @RestartSequence('ooo')
            when ':О'
                @RestartSequence('ooo')
            when ':3'
                @RestartSequence('cat')
            when ':з'
                @RestartSequence('cat')
            when ':d'
                @RestartSequence('big_grin')
            when 'xd'
                @RestartSequence('xd')
            when 'exdi'
                @RestartSequence('xd')
            when ':p'
                @RestartSequence('tongue')
            when ':р'
                @RestartSequence('tongue')
            when ':Р'
                @RestartSequence('tongue')
            when ':c'
                @RestartSequence('sad')
            when ':('
                @RestartSequence('sad')
            when '('
                @RestartSequence('sad')
            when ':с'
                @RestartSequence('sad')
            when ':С'
                @RestartSequence('sad')
            else
                if string.find(text, 'hehehe') or string.find(text, 'hahaha')
                    @RestartSequence('greeny')
                else
                    @RestartSequence('talk')
    PlayerStartVoice: (ply = NULL) =>
        return if ply ~= @ent
        @StartSequence('talk_endless')
    PlayerEndVoice: (ply = NULL) =>
        return if ply ~= @ent
        @EndSequence('talk_endless')
    PPM2_HurtAnimation: (ply = NULL) =>
        return if ply ~= @ent
        @RestartSequence('hurt')
    PPM2_KillAnimation: (ply = NULL) =>
        return if ply ~= @ent
        @RestartSequence('kill_grin')

    RemoveHooks: =>
        for iHook in *@hooks
            hook.Remove iHook, @hookID

    Think: =>
        return if DISABLE_FLEXES\GetBool()
        if not IsValid(@ent)
            @ent = @GetData().ent
        return if not IsValid(@ent) or @ent\IsDormant() or not @ent\Alive()
        delta = RealTime() - @lastThink
        @lastThink = RealTime()
        state\Think(delta) for state in *@statesIterable
        for seq in *@currentSequencesIterable
            if not seq\IsValid()
                @EndSequence(seq\GetName(), false)
                break
            seq\Think(delta)

do
    ppm2_disable_flexes = (cvar, oldval, newval) ->
        for ply in *player.GetAll()
            data = ply\GetPonyData()
            continue if not data
            flex = data\GetFlexController()
            continue if not flex
            flex\ResetSequences()
    cvars.AddChangeCallback 'ppm2_disable_flexes', ppm2_disable_flexes, 'ppm2_disable_flexes'

PPM2.PonyFlexController = PonyFlexController
PPM2.GetFlexController = (model = 'models/ppm/player_default_base_new.mdl') -> PonyFlexController.AVALIABLE_CONTROLLERS[model]


meta header thingie

meta header thingie

pop = {
    _VERSION = 'Pop.Box v0.0.0'
    _DESCRIPTION = 'A GUI library for LOVE.'
    _URL = 'http://github.com/Guard13007/Pop.Box'
    _LICENSE = '
        The MIT License (MIT)

        Copyright (c) 2015-2016 Paul Liverman III

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

unless love.getVersion
    error "Pop.Box only supports LOVE versions >= 0.9.1"

import filesystem, graphics from love
import insert from table
import inheritsFromElement from require "#{...}/util"

path = ...

pop.elements = {}
pop.skins = {}
--pop.events = {} --NOTE leave this commented out for now, as it may be needed again

pop.screen = false  -- initialized in pop.load()
pop.focused = false

-- loads elements and skins, creates pop.screen (intended to only be called once at the beginning)
pop.load = ->
    elements = filesystem.getDirectoryItems "#{path}/elements"

    for i = 1, #elements
        -- only attempt to load lua files
        unless elements[i]\sub(-4) == ".lua"
            continue

        -- load into pop.elements table
        name = elements[i]\sub 1, -5
        pop.elements[name] = require "#{path}/elements/#{name}"

        if pop.elements[name].load
            pop.elements[name].load pop

        print "element loaded: \"#{name}\""

        -- create pop.element() wrapper if possible
        unless pop[name]
            if pop.elements[name].wrap
                pop[name] = pop.elements[name].wrap pop
            else
                pop[name] = (...) ->
                    return pop.create(name, ...)

            print "wrapper created: \"pop.#{name}()\""

    -- works just like above, except no wrappers
    skins = filesystem.getDirectoryItems "#{path}/skins"

    for i = 1, #skins
        unless skins[i]\sub(-4) == ".lua"
            continue

        name = skins[i]\sub 1, -5
        pop.skins[name] = require "#{path}/skins/#{name}"

        print "skin loaded: \"#{name}\""

    -- load extensions by just running them via require
    extensions = filesystem.getDirectoryItems "#{path}/extensions"

    for i = 1, #extensions
        unless extensions[i]\sub(-4) == ".lua"
            continue

        name = extensions[i]\sub 1, -5
        require "#{path}/extensions/#{name}"

        print "extension loaded: \"#{name}\""

    -- main window (called screen because there is a window element class)
    pop.screen = pop.create("element", false)\setSize(graphics.getWidth!, graphics.getHeight!)
    print "created \"pop.screen\""

-- creates an element with specified parent (parent can be false or non-existent)
pop.create = (element, parent=pop.screen, ...) ->
    -- if valid parent element (includes default of pop.screen when no parent has been passed)
    if inheritsFromElement parent
        element = pop.elements[element](parent, ...)
        insert parent.child, element
    -- if explicitly no parent
    elseif parent == false
        element = pop.elements[element](false, ...)
    -- else we use pop.screen, and "parent" is actually first argument
    else
        element = pop.elements[element](pop.screen, parent, ...)
        insert pop.screen.child, element

    return element

pop.update = (dt, element=pop.screen) ->
    unless element.excludeUpdate
        if element.update
            element\update dt
        for i = 1, #element.child
            pop.update dt, element.child[i]

pop.draw = (element=pop.screen) ->
    unless element.excludeDraw
        if element.draw
            element\draw!
        for i = 1, #element.child
            pop.draw element.child[i]

pop.mousemoved = (x, y, dx, dy) ->
    if pop.focused and pop.focused.mousemoved
        return pop.focused\mousemoved x, y, dx, dy

    return false

pop.mousepressed = (x, y, button, element) ->
    unless element
        print "mousepressed", x, y, button
        element = pop.screen

    handled = false

    if (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h)
        for i = #element.child, 1, -1
            handled = pop.mousepressed x, y, button, element.child[i]
            if handled
                break

        unless handled
            if element.mousepressed and (not element.excludeDraw)
                if handled = element\mousepressed x - element.x, y - element.y, button
                    pop.focused = element
                    --NOTE this might end up being needed in the future
                    --  if it is, add an ability for a mousepressed handler to cancel saving
                    --  the event, and make sure the window element's area does this
                    --pop.events[button] = element

    return handled

pop.mousereleased = (x, y, button, element) ->
    clickedHandled = false
    mousereleasedHandled = false

    if element
        if (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h)
            for i = #element.child, 1, -1
                clickedHandled, mousereleasedHandled = pop.mousereleased x, y, button, element.child[i]
                if clickedHandled or mousereleasedHandled
                    break

            unless clickedHandled or mousereleasedHandled
                if element.clicked and (not element.excludeDraw)
                    clickedHandled = element\clicked x - element.x, y - element.y, button
                if element.mousereleased
                    mousereleasedHandled = element\mousereleased x - element.x, y - element.y, button

                if clickedHandled
                    pop.focused = element

    --else
    --    print "mousereleased", x, y, button
    --    if element = pop.events[button]
    --        if element.clicked and (not element.excludeDraw) --and (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h)
    --            if clickedHandled = element\clicked x - element.x, y - element.y, button
    --                pop.events[button] = nil

    --        if element.mousereleased
    --            if mousereleasedHandled = element\mousereleased x - element.x, y - element.y, button
    --                pop.events[button] = nil

    --    if (not clickedHandled) and (not mousereleasedHandled)
    --        clickedHandled, mousereleasedHandled = pop.mousereleased x, y, button, pop.screen

    return clickedHandled, mousereleasedHandled

pop.keypressed = (key) ->
    print "keypressed", key

    element = pop.focused
    if element and element.keypressed and (not element.excludeDraw)
        return element.keypressed key

    return false

pop.keyreleased = (key) ->
    print "keyreleased", key

    element = pop.focused
    if element and element.keyreleased
        return element.keyreleased key

    return false

pop.textinput = (text) ->
    print "textinput", text

    element = pop.focused
    if element and element.textinput and (not element.excludeDraw)
        return element.textinput text

    return false

--TODO rewrite skin system to not rely on knowing internals of elements,
--     instead call functions like setColor and setBackground
-- skins an element (and its children unless depth == true or 0)
--  depth can be an integer for how many levels to go down when skinning
--  defaults to pop.screen and the default skin
pop.skin = (element=pop.screen, skin=pop.skins.default, depth) ->
    if element.background and skin.background
        element.background = skin.background
    if element.color and skin.color
        element.color = skin.color
    if element.font and skin.font
        element.font = skin.font

    unless depth or (depth == 0)
        if depth == tonumber depth
            for i = 1, #element.child
                pop.skin element.child[i], skin, depth - 1
        else
            for i = 1, #element.child
                pop.skin element.child[i], skin, false

pop.debugDraw = (element=pop.screen) ->
    if element.debugDraw
        element\debugDraw!
    else
        graphics.setLineWidth 1
        graphics.setLineColor 0, 0, 0, 100
        graphics.rectangle "fill", element.x, element.y, element.w, element.h
        graphics.setColor 150, 150, 150, 150
        graphics.rectangle "line", element.x, element.y, element.w, element.h
        graphics.setColor 200, 200, 200, 255
        graphics.print ".", element.x, element.y

    for i = 1, #element.child
        pop.debugDraw element.child[i]

pop.printElementStack = (element=pop.screen, depth=0) ->
    cls = element.__class.__name

    if cls == "text"
        cls = cls .. " (\"#{element\getText!\gsub "\n", "\\n"}\")"
    elseif cls == "box"
        bg = element\getBackground!

        if type(bg) == "table"
            bg = "#{bg[1]}, #{bg[2]}, #{bg[3]}, #{bg[4]}"

        cls = cls .. " (#{bg})"

    print string.rep("-", depth) .. " #{cls}"

    for i = 1, #element.child
        pop.printElementStack element.child[i], depth + 1

pop.load!

return pop

unless love.getVersion
    error "Pop.Box only supports LOVE versions >= 0.9.1"

import filesystem, graphics from love
import insert from table
import inheritsFromElement from require "#{...}/util"

path = ...

pop = {}

pop.elements = {}
pop.skins = {}
--pop.events = {} --NOTE leave this commented out for now, as it may be needed again

pop.screen = false  -- initialized in pop.load()
pop.focused = false

-- loads elements and skins, creates pop.screen (intended to only be called once at the beginning)
pop.load = ->
    elements = filesystem.getDirectoryItems "#{path}/elements"

    for i = 1, #elements
        -- only attempt to load lua files
        unless elements[i]\sub(-4) == ".lua"
            continue

        -- load into pop.elements table
        name = elements[i]\sub 1, -5
        pop.elements[name] = require "#{path}/elements/#{name}"

        if pop.elements[name].load
            pop.elements[name].load pop

        print "element loaded: \"#{name}\""

        -- create pop.element() wrapper if possible
        unless pop[name]
            if pop.elements[name].wrap
                pop[name] = pop.elements[name].wrap pop
            else
                pop[name] = (...) ->
                    return pop.create(name, ...)

            print "wrapper created: \"pop.#{name}()\""

    -- works just like above, except no wrappers
    skins = filesystem.getDirectoryItems "#{path}/skins"

    for i = 1, #skins
        unless skins[i]\sub(-4) == ".lua"
            continue

        name = skins[i]\sub 1, -5
        pop.skins[name] = require "#{path}/skins/#{name}"

        print "skin loaded: \"#{name}\""

    -- load extensions by just running them via require
    extensions = filesystem.getDirectoryItems "#{path}/extensions"

    for i = 1, #extensions
        unless extensions[i]\sub(-4) == ".lua"
            continue

        name = extensions[i]\sub 1, -5
        require "#{path}/extensions/#{name}"

        print "extension loaded: \"#{name}\""

    -- main window (called screen because there is a window element class)
    pop.screen = pop.create("element", false)\setSize(graphics.getWidth!, graphics.getHeight!)
    print "created \"pop.screen\""

-- creates an element with specified parent (parent can be false or non-existent)
pop.create = (element, parent=pop.screen, ...) ->
    -- if valid parent element (includes default of pop.screen when no parent has been passed)
    if inheritsFromElement parent
        element = pop.elements[element](parent, ...)
        insert parent.child, element
    -- if explicitly no parent
    elseif parent == false
        element = pop.elements[element](false, ...)
    -- else we use pop.screen, and "parent" is actually first argument
    else
        element = pop.elements[element](pop.screen, parent, ...)
        insert pop.screen.child, element

    return element

pop.update = (dt, element=pop.screen) ->
    unless element.excludeUpdate
        if element.update
            element\update dt
        for i = 1, #element.child
            pop.update dt, element.child[i]

pop.draw = (element=pop.screen) ->
    unless element.excludeDraw
        if element.draw
            element\draw!
        for i = 1, #element.child
            pop.draw element.child[i]

pop.mousemoved = (x, y, dx, dy) ->
    if pop.focused and pop.focused.mousemoved
        return pop.focused\mousemoved x, y, dx, dy

    return false

pop.mousepressed = (x, y, button, element) ->
    unless element
        print "mousepressed", x, y, button
        element = pop.screen

    handled = false

    if (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h)
        for i = #element.child, 1, -1
            handled = pop.mousepressed x, y, button, element.child[i]
            if handled
                break

        unless handled
            if element.mousepressed and (not element.excludeDraw)
                if handled = element\mousepressed x - element.x, y - element.y, button
                    pop.focused = element
                    --NOTE this might end up being needed in the future
                    --  if it is, add an ability for a mousepressed handler to cancel saving
                    --  the event, and make sure the window element's area does this
                    --pop.events[button] = element

    return handled

pop.mousereleased = (x, y, button, element) ->
    clickedHandled = false
    mousereleasedHandled = false

    if element
        if (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h)
            for i = #element.child, 1, -1
                clickedHandled, mousereleasedHandled = pop.mousereleased x, y, button, element.child[i]
                if clickedHandled or mousereleasedHandled
                    break

            unless clickedHandled or mousereleasedHandled
                if element.clicked and (not element.excludeDraw)
                    clickedHandled = element\clicked x - element.x, y - element.y, button
                if element.mousereleased
                    mousereleasedHandled = element\mousereleased x - element.x, y - element.y, button

                if clickedHandled
                    pop.focused = element

    --else
    --    print "mousereleased", x, y, button
    --    if element = pop.events[button]
    --        if element.clicked and (not element.excludeDraw) --and (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h)
    --            if clickedHandled = element\clicked x - element.x, y - element.y, button
    --                pop.events[button] = nil

    --        if element.mousereleased
    --            if mousereleasedHandled = element\mousereleased x - element.x, y - element.y, button
    --                pop.events[button] = nil

    --    if (not clickedHandled) and (not mousereleasedHandled)
    --        clickedHandled, mousereleasedHandled = pop.mousereleased x, y, button, pop.screen

    return clickedHandled, mousereleasedHandled

pop.keypressed = (key) ->
    print "keypressed", key

    element = pop.focused
    if element and element.keypressed and (not element.excludeDraw)
        return element.keypressed key

    return false

pop.keyreleased = (key) ->
    print "keyreleased", key

    element = pop.focused
    if element and element.keyreleased
        return element.keyreleased key

    return false

pop.textinput = (text) ->
    print "textinput", text

    element = pop.focused
    if element and element.textinput and (not element.excludeDraw)
        return element.textinput text

    return false

--TODO rewrite skin system to not rely on knowing internals of elements,
--     instead call functions like setColor and setBackground
-- skins an element (and its children unless depth == true or 0)
--  depth can be an integer for how many levels to go down when skinning
--  defaults to pop.screen and the default skin
pop.skin = (element=pop.screen, skin=pop.skins.default, depth) ->
    if element.background and skin.background
        element.background = skin.background
    if element.color and skin.color
        element.color = skin.color
    if element.font and skin.font
        element.font = skin.font

    unless depth or (depth == 0)
        if depth == tonumber depth
            for i = 1, #element.child
                pop.skin element.child[i], skin, depth - 1
        else
            for i = 1, #element.child
                pop.skin element.child[i], skin, false

pop.debugDraw = (element=pop.screen) ->
    if element.debugDraw
        element\debugDraw!
    else
        graphics.setLineWidth 1
        graphics.setLineColor 0, 0, 0, 100
        graphics.rectangle "fill", element.x, element.y, element.w, element.h
        graphics.setColor 150, 150, 150, 150
        graphics.rectangle "line", element.x, element.y, element.w, element.h
        graphics.setColor 200, 200, 200, 255
        graphics.print ".", element.x, element.y

    for i = 1, #element.child
        pop.debugDraw element.child[i]

pop.printElementStack = (element=pop.screen, depth=0) ->
    cls = element.__class.__name

    if cls == "text"
        cls = cls .. " (\"#{element\getText!\gsub "\n", "\\n"}\")"
    elseif cls == "box"
        bg = element\getBackground!

        if type(bg) == "table"
            bg = "#{bg[1]}, #{bg[2]}, #{bg[3]}, #{bg[4]}"

        cls = cls .. " (#{bg})"

    print string.rep("-", depth) .. " #{cls}"

    for i = 1, #element.child
        pop.printElementStack element.child[i], depth + 1

pop.load!

return pop


superroute

superroute

lapis = require "lapis"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
migrations=require "lapis.db.migrations"
config = (require "lapis.config").get!

class extends lapis.Application
  @before_filter =>
    if config.measure_performance
      after_dispatch ->
        print to_json ngx.ctx.performance
    @modules or={}
    @session.modules or={}
  "/_lazuli/console": if config.enable_console
    (require "lapis.console").make!
  else
    => "console disabled in config"
  "/_lazuli/migrate": if config.enable_web_migration
    =>
      migrations.create_migrations_table!
      migrations.run_migrations require "migrations"
  else
    => "web migration disabled in config"
  new: (...)=>
    @modules or={}
    super ...
  enable: (feature,forcelapis=false)=>
    if not forcelapis
      have,fn=pcall require, "lazuli.modules."..feature..".enabler"
      if have and type(fn)=="function"
        return fn @
    super feature
  superroute: (r)=>
    App=@__parent
    cache=nil
    name=next r
    return [r]: =>
      if not cache
        for app_route in pairs App.__base
          if type(app_route) == "table"
            app_route_name = next app_route
            if app_route_name == name
              cache=App.__base[app_route]
      if r[1]
        r[1] @, App.__base[app_route](@)
      else
        App.__base[app_route](@)

lapis = require "lapis"

import after_dispatch from require "lapis.nginx.context"
import to_json from require "lapis.util"
migrations=require "lapis.db.migrations"
config = (require "lapis.config").get!

class extends lapis.Application
  @before_filter =>
    if config.measure_performance
      after_dispatch ->
        print to_json ngx.ctx.performance
    @modules or={}
    @session.modules or={}
  "/_lazuli/console": if config.enable_console
    (require "lapis.console").make!
  else
    => "console disabled in config"
  "/_lazuli/migrate": if config.enable_web_migration
    =>
      migrations.create_migrations_table!
      migrations.run_migrations require "migrations"
  else
    => "web migration disabled in config"
  new: (...)=>
    @modules or={}
    super ...
  enable: (feature,forcelapis=false)=>
    if not forcelapis
      have,fn=pcall require, "lazuli.modules."..feature..".enabler"
      if have and type(fn)=="function"
        return fn @
    super feature
  superroute: (r)=>
    App=@__parent
    cache=nil
    name=next r
    return =>
      if not cache
        for app_route in pairs App.__base
          if type(app_route) == "table"
            app_route_name = next app_route
            if app_route_name == name
              cache=App.__base[app_route]
      if r[1]
        r[1] @, App.__base[app_route](@)
      else
        App.__base[app_route](@)


plugins/data: Add RENAME command, fix 353 for Oragono

import IRCClient from require 'irc'

unpack = unpack or table.unpack

IRCClient\add_hook 'CONNECT', =>
	@channels = {}
	@users    = {}
	@server   = {
		caps:       {}
		ircv3_caps: {}
		batches:    {}
	}

IRCClient\add_hook 'CONNECT', =>
	-- Welcome
	@data = {} if not @data
	@data.last_connect = os.time()
	@send_raw 'CAP LS 302'

IRCClient\add_handler 'BATCH', (prefix, args, trail, tags)=>
	tag_type, tag = args[1]\match '(.)(.+)'
	if tag_type == '+'
		@server.batches[tag] = {unpack(args, 2)}
	elseif tag_type == '-'
		@server.batches[tag] = nil

IRCClient\add_handler '005', (prefix, args)=>
	-- Capabilities
	isupport_caps = {select 2, unpack args}
	for cap in *isupport_caps
		if cap\sub(1, 1) == "-"
			-- remove support
			if cap\find "=" then
				@server.caps[cap\match "^%-(.+)="] = nil
			else
				@server.caps[cap\sub 2] = nil
		elseif cap\find "="
			key, value = cap\match '^(.-)=(.+)'
			@server.caps[key] = value
		else
			@server.caps[cap] = true

IRCClient\add_handler 'AWAY', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].away = trail

IRCClient\add_handler 'ACCOUNT', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].account = args[1] != "*" and args[1] or nil

IRCClient\add_handler 'RENAME', (prefix, args, trail)=>
	{old, new} = args
	for _, user in pairs @channels[old].users
		user.channels[new] = user.channels[old]
		user.channels[old] = nil
	@channels[new] = @channels[old]
	@channels[old] = nil

IRCClient\add_handler 'JOIN', (prefix, args, trail, tags={})=>
	-- user JOINs a channel
	local channel
	local account
	if @server.ircv3_caps['extended-join']
		account = args[2] if args[2] != '*'
		channel = args[1]
	elseif @server.ircv3_caps['account-tag'] and tags.account
		account = tags.account
		channel = args[1]
	else
		channel = args[1] or trail
	nick, username, host = prefix\match '^(.-)!(.-)@(.-)$'
	if prefix\match '^.-!.-@.-$'
		if not @users[nick] then
			@users[nick] = {
				account: account
				channels: {
					[channel]: {
						status: ""
					}
				},
				:username,
				:host
			}
		else
			if not @users[nick].channels
				@users[nick].channels = {
					[channel]: {
						status: ""
					}
				}
			else
				@users[nick].channels[channel] = status: ""
		@users[nick].account = account if account
	if not @channels[channel]
		-- ::TODO:: optimize out for ISUPPORT WHOX
		if not @server.ircv3_caps['userhost-in-names']
			@send_raw ('WHO %s')\format channel
		-- else
		--	@send_raw ('NAMES %s')\format channel

		if @server.caps['WHOX']
			@send_raw "WHO #{channel} %nat,001"

		@channels[channel] = {
			users: {
				[nick]: @users[nick]
			}
		}
	else
		@channels[channel].users[nick] = @users[nick]

IRCClient\add_hook 'WHOX_001', (nick, account)=>
	@users[nick].account = account if @users[nick] and account ~= '0'

IRCClient\add_handler 'NICK', (prefix, args, trail)=>
	old = prefix\match('^(.-)!') or prefix
	new = args[1] or trail
	for channel_name in pairs @users[old].channels
		@channels[channel_name].users[new] = @channels[channel_name].users[old]
		@channels[channel_name].users[old] = nil
	@users[new] = @users[old]
	@users[old] = nil

IRCClient\add_handler 'MODE', (prefix, args)=>
	-- User or bot called /mode
	if args[1] and args[1]\sub(1,1) == "#"
		@send_raw ('NAMES %s')\format args[1]

IRCClient\add_handler '353', (prefix, args, trail)=>
	-- Result of NAMES
	trail = trail or args[#args] -- Oragono bug workaround
	channel = args[3]
	statuses = @server.caps.PREFIX and @server.caps.PREFIX\match '%(.-%)(.+)' or "+@"
	statuses = "[" .. statuses\gsub("%p", "%%%1") .. "]"
	for text in trail\gmatch '%S+'
		local status, pre, nick, user, host
		if text\match statuses
			status, pre = text\match ('^(%s+)(.+)')\format statuses
		else
			status, pre = '', text
		if @server.ircv3_caps['userhost-in-names']
			nick, user, host = pre\match '^(.-)!(.-)@(.-)$'
		else
			nick = pre
		if not @users[nick]
			@users[nick] = {channels: {}}
		@users[nick].user = user if user
		@users[nick].host = host if host
		if @channels[channel].users[nick]
			if @users[nick].channels[channel]
				@users[nick].channels[channel].status = status
			else
				@users[nick].channels[channel] = :status
		else
			@channels[channel].users[nick] = @users[nick]
			@users[nick].channels[channel] = :status

IRCClient\add_handler '352', (prefix, args)=>
	_, user, host, _, nick, away = unpack args
	@users[nick] = {channels: {}} if not @users[nick]
	@users[nick].user = user
	@users[nick].host = host
	@users[nick].away = away\sub(1, 1) == "G"

IRCClient\add_handler 'CHGHOST', (prefix, args)=>
	nick = prefix\match '^(.-)!'
	@users[nick].user = args[1]
	@users[nick].host = args[2]

IRCClient\add_handler 'KICK', (prefix, args)=>
	channel = args[1]
	nick = args[2]
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil

IRCClient\add_handler 'PART', (prefix, args)=>
	-- User or bot parted channel, clear from lists
	channel = args[1]
	nick = prefix\match '^(.-)!'
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil -- User left network, garbagecollect

IRCClient\add_handler 'QUIT', (prefix, args)=>
	-- User or bot parted network, nuke from lists
	nick = prefix\match '^(.-)!'
	for channel in pairs @users[nick].channels
		@channels[channel].users[nick] = nil
	@users[nick] = nil

import IRCClient from require 'irc'

unpack = unpack or table.unpack

IRCClient\add_hook 'CONNECT', =>
	@channels = {}
	@users    = {}
	@server   = {
		caps:       {}
		ircv3_caps: {}
		batches:    {}
	}

IRCClient\add_hook 'CONNECT', =>
	-- Welcome
	@data = {} if not @data
	@data.last_connect = os.time()
	@send_raw 'CAP LS 302'

IRCClient\add_handler 'BATCH', (prefix, args, trail, tags)=>
	tag_type, tag = args[1]\match '(.)(.+)'
	if tag_type == '+'
		@server.batches[tag] = {unpack(args, 2)}
	elseif tag_type == '-'
		@server.batches[tag] = nil

IRCClient\add_handler '005', (prefix, args)=>
	-- Capabilities
	isupport_caps = {select 2, unpack args}
	for cap in *isupport_caps
		if cap\sub(1, 1) == "-"
			-- remove support
			if cap\find "=" then
				@server.caps[cap\match "^%-(.+)="] = nil
			else
				@server.caps[cap\sub 2] = nil
		elseif cap\find "="
			key, value = cap\match '^(.-)=(.+)'
			@server.caps[key] = value
		else
			@server.caps[cap] = true

IRCClient\add_handler 'AWAY', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].away = trail

IRCClient\add_handler 'ACCOUNT', (prefix, args, trail)=>
	nick = prefix\match '^(.-)!'
	@users[nick].account = args[1] != "*" and args[1] or nil

IRCClient\add_handler 'JOIN', (prefix, args, trail, tags={})=>
	-- user JOINs a channel
	local channel
	local account
	if @server.ircv3_caps['extended-join']
		account = args[2] if args[2] != '*'
		channel = args[1]
	elseif @server.ircv3_caps['account-tag'] and tags.account
		account = tags.account
		channel = args[1]
	else
		channel = args[1] or trail
	nick, username, host = prefix\match '^(.-)!(.-)@(.-)$'
	if prefix\match '^.-!.-@.-$'
		if not @users[nick] then
			@users[nick] = {
				account: account
				channels: {
					[channel]: {
						status: ""
					}
				},
				:username,
				:host
			}
		else
			if not @users[nick].channels
				@users[nick].channels = {
					[channel]: {
						status: ""
					}
				}
			else
				@users[nick].channels[channel] = status: ""
		@users[nick].account = account if account
	if not @channels[channel]
		-- ::TODO:: optimize out for ISUPPORT WHOX
		if not @server.ircv3_caps['userhost-in-names']
			@send_raw ('WHO %s')\format channel
		-- else
		--	@send_raw ('NAMES %s')\format channel

		if @server.caps['WHOX']
			@send_raw "WHO #{channel} %nat,001"

		@channels[channel] = {
			users: {
				[nick]: @users[nick]
			}
		}
	else
		@channels[channel].users[nick] = @users[nick]

IRCClient\add_hook 'WHOX_001', (nick, account)=>
	@users[nick].account = account if @users[nick] and account ~= '0'

IRCClient\add_handler 'NICK', (prefix, args, trail)=>
	old = prefix\match('^(.-)!') or prefix
	new = args[1] or trail
	for channel_name in pairs @users[old].channels
		@channels[channel_name].users[new] = @channels[channel_name].users[old]
		@channels[channel_name].users[old] = nil
	@users[new] = @users[old]
	@users[old] = nil

IRCClient\add_handler 'MODE', (prefix, args)=>
	-- User or bot called /mode
	if args[1] and args[1]\sub(1,1) == "#"
		@send_raw ('NAMES %s')\format args[1]

IRCClient\add_handler '353', (prefix, args, trail)=>
	-- Result of NAMES
	channel = args[3]
	statuses = @server.caps.PREFIX and @server.caps.PREFIX\match '%(.-%)(.+)' or "+@"
	statuses = "[" .. statuses\gsub("%p", "%%%1") .. "]"
	for text in trail\gmatch '%S+'
		local status, pre, nick, user, host
		if text\match statuses
			status, pre = text\match ('^(%s+)(.+)')\format statuses
		else
			status, pre = '', text
		if @server.ircv3_caps['userhost-in-names']
			nick, user, host = pre\match '^(.-)!(.-)@(.-)$'
		else
			nick = pre
		if not @users[nick]
			@users[nick] = {channels: {}}
		@users[nick].user = user if user
		@users[nick].host = host if host
		if @channels[channel].users[nick]
			if @users[nick].channels[channel]
				@users[nick].channels[channel].status = status
			else
				@users[nick].channels[channel] = :status
		else
			@channels[channel].users[nick] = @users[nick]
			@users[nick].channels[channel] = :status

IRCClient\add_handler '352', (prefix, args)=>
	_, user, host, _, nick, away = unpack args
	@users[nick] = {channels: {}} if not @users[nick]
	@users[nick].user = user
	@users[nick].host = host
	@users[nick].away = away\sub(1, 1) == "G"

IRCClient\add_handler 'CHGHOST', (prefix, args)=>
	nick = prefix\match '^(.-)!'
	@users[nick].user = args[1]
	@users[nick].host = args[2]

IRCClient\add_handler 'KICK', (prefix, args)=>
	channel = args[1]
	nick = args[2]
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil

IRCClient\add_handler 'PART', (prefix, args)=>
	-- User or bot parted channel, clear from lists
	channel = args[1]
	nick = prefix\match '^(.-)!'
	@users[nick].channels[channel] = nil
	if not next @users[nick].channels
		@users[nick] = nil -- User left network, garbagecollect

IRCClient\add_handler 'QUIT', (prefix, args)=>
	-- User or bot parted network, nuke from lists
	nick = prefix\match '^(.-)!'
	for channel in pairs @users[nick].channels
		@channels[channel].users[nick] = nil
	@users[nick] = nil


Fix return accessors for graphics.prototype.Rect

require "LunoPunk.Graphic"
require "LunoPunk.graphics.Image"

export ^

class Rect extends Graphic
	new: (width, height, color = 0xFFFFFF) =>
		super!

		@__width = width
		@__height = height
		@__color = color
		createImage @

	render: (point, camera) =>
		@__point.x = point.x + @x
		@__point.y = point.y + @y
		@__img\render @__point, camera

	createImage = =>
		@__img = Image.createRect @__width, @__height, @__color

	width: (value) =>
		return @__width if value == nil or value == @__width
		@__width = value
		@__img = createImage @

	height: (value) =>
		return @__height if value == nil or value == @__height
		@__height = value
		@__img = createImage @

	color: (value) =>
		return @__color if value == nil or value == @__color
		@__color = value
		@__img = createImage @

require "LunoPunk.Graphic"
require "LunoPunk.graphics.Image"

export ^

class Rect extends Graphic
	new: (width, height, color = 0xFFFFFF) =>
		super!

		@__width = width
		@__height = height
		@__color = color
		createImage @

	render: (point, camera) =>
		@__point.x = point.x + @x
		@__point.y = point.y + @y
		@__img\render @__point, camera

	createImage = =>
		@__img = Image.createRect @__width, @__height, @__color

	width: (value) =>
		return value if value == nil or value == @__width
		@__width = value
		@__img = createImage @

	height: (value) =>
		return value if value == nil or value == @__height
		@__height = value
		@__img = createImage @

	color: (value) =>
		return value if value == nil or value == @__color
		@__color = value
		@__img = createImage @