Fix some issues in db.lua

Fix some issues in db.lua

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

NAME, T = ...

-- We import `type` because of possible performance issues
-- if db methods are called from places such as OnUpdate
-- callbacks
import type from _G

-- Same deal with importinhg equal from our table utils
import equal from T.table

{event_manager: em} = T

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

local parse_key, extract_name, extract_keys, prepare

class Database
    GET = 0
    SET = 1
    RESET = 2

    -- This will make sure updates are fired for the key
    -- and any parent keys, for example:
    -- send_update(db, 'one.two.three') will fire updates
    -- for:
    -- one
    -- one.two
    -- one.two.three
    send_update = (db, key) ->
        em\fire 'SHAREXP_DB_UPDATED', db, key

    new: (@global_name) =>
        @log = T.Logger 'db.' .. @global_name
        @loaded = false
        @queue = {}
        em\fire 'SHAREXP_DB_CREATED', @

    __call: (...) =>
        @get ...

    enqueue: (method, key, value) =>
        if @loaded
            error 'Database.enqueue called when db loaded'
        if method != GET and method != SET and method != RESET
            error 'Database.enqueue called with invalid method arg: ' .. method
        @queue[#@queue + 1] = {:method, :key, :value}

    load: =>
        _G[@global_name] = {} if type(_G[@global_name]) != 'table'
        @global = _G[@global_name]
        @loaded = true
        for item in *@queue
            with item
                switch .method
                    when GET
                        @get .key, .value
                    when SET
                        @set .key, .value
                    when RESET
                        @reset .key
        @queue = nil
        em\fire 'SHAREXP_DB_LOADED', @

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

    get: (key, default) =>
        if @loaded
            tbl = parse_key @, key
            name = extract_name key
            prepare tbl, name, default
            tbl[name]._VALUE, tbl[name]._DEFAULT
        else
            @log\warn 'get called before load, returning approximate value for %s', key
            @enqueue GET, key, default
            local entry
            for item in *@queue
                entry = item if item.key == key
            entry.value if entry else default

    get_value: (key, default) =>
        (@get key, default)

    get_default: (key) =>
        if @loaded
            _, default = @get key
            default
        else
            @log\warn 'get_default called before load, returning nil for %s', key
            nil

    set: (key, value, silent) =>
        if @loaded
            tbl = parse_key @, key
            name = extract_name key
            prepare tbl, name, value
            tbl[name]._VALUE = value
            send_update @, key unless silent
        else
            @log\warn 'set called before load, queueing set action on %s', key
            @enqueue SET, key, value

    reset: (key, silent) =>
        if @loaded
            tbl = parse_key @, key
            name = extract_name key
            return if type(tbl[name]) != 'table'
            tbl[name]._VALUE = tbl[name]._DEFAULT
            send_update @, key unless silent
        else
            @log\warn 'reset called before load, queueing reset action on %s', key
            @enqueue RESET, key

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

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

extract_name = (key) ->
    key\match DELIMITER_NAME_MATCH

extract_keys = (key) ->
    [token for token in key\gmatch DELIMITER_TOKEN_MATCH]

prepare = (tbl, key, default) ->
    if type(tbl[key]) != 'table'
        tbl[key] = { _VALUE: default, _DEFAULT: default }
    else
        default_type = type default
        with tbl[key]
            ._VALUE = default if type(._VALUE) != default_type and default_type != 'nil'
            if type(._DEFAULT) == 'table'
                ._DEFAULT = default unless default_type == 'nil' or equal ._DEFAULT, default
            else
                ._DEFAULT = default if ._DEFAULT != default and default_type != 'nil'

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

NAME, T = ...

-- We import `type` because of possible performance issues
-- if db methods are called from places such as OnUpdate
-- callbacks
import type from _G

-- Same deal with importinhg equal from our table utils
import equal from T.table

{event_manager: em} = T

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

local check_global, parse_key, extract_name, extract_keys, prepare

class Database
    GET = 0
    SET = 1
    RESET = 2

    -- This will make sure updates are fired for the key
    -- and any parent keys, for example:
    -- send_update(db, 'one.two.three') will fire updates
    -- for:
    -- one
    -- one.two
    -- one.two.three
    send_update = (db, key) ->
        em\fire 'SHAREXP_DB_UPDATED', db, key

    new: (@global_name) =>
        @log = T.Logger 'db.' .. @global_name
        @loaded = false
        @queue = {}
        em\fire 'SHAREXP_DB_CREATED', @

    __call: (...) =>
        @get ...

    enqueue: (method, key, value) =>
        if @loaded
            error 'Database.enqueue called when db loaded'
        if method != GET and method != SET and method != RESET
            error 'Database.enqueue called with invalid method arg: ' .. method
        @queue[#@queue + 1] = {:method, :key, :value}

    load: =>
        _G[@global_name] = {} if type(_G[@global_name]) != 'table'
        @global = _G[@global_name]
        @loaded = true
        for item in *@queue
            with item
                switch .method
                    when GET
                        @get .key, .value
                    when SET
                        @set .key, .value
                    when RESET
                        @reset .key
        @queue = nil
        em\fire 'SHAREXP_DB_LOADED', @

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

    get: (key, default) =>
        if @loaded
            tbl = parse_key @, key
            name = extract_name key
            prepare tbl, name, default
            tbl[name]._VALUE, tbl[name]._DEFAULT
        else
            @log\warn 'get called before load, returning approximate value for %s', key
            @enqueue GET, key, default
            local entry
            for item in *@queue
                entry = item if item.key == key
            entry.value if entry else default

    get_value: (key, default) =>
        (@get key, default)

    get_default: (key) =>
        if @loaded
            _, default = @get key
            default
        else
            @log\warn 'get_default called before load, returning nil for %s', key
            nil

    set: (key, value, silent) =>
        if @loaded
            tbl = parse_key @, key
            name = extract_name key
            prepare tbl, name, value
            tbl[name]._VALUE = value
            send_update @, key unless silent
        else
            @log\warn 'set called before load, queueing set action on %s', key
            @enqueue SET, key, value

    reset: (key, silent) =>
        if @loaded
            tbl = parse_key @, key
            name = extract_name key
            return if type(tbl[name]) != 'table'
            tbl[name]._VALUE = tbl[name]._DEFAULT
            send_update @, key unless silent
        else
            @log\warn 'reset called before load, queueing reset action on %s', key
            @enqueue RESET, key

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

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

extract_name = (key) ->
    key\match DELIMITER_NAME_MATCH

extract_keys = (key) ->
    [token for token in key\gmatch DELIMITER_TOKEN_MATCH]

prepare = (tbl, key, default) ->
    if type(tbl[key]) != 'table'
        tbl[key] = { _VALUE: default, _DEFAULT: default }
    else
        default_type = type default
        with tbl[key]
            ._VALUE = default if type(._VALUE) != default_type and default_type != 'nil'
            if type(._DEFAULT) == 'table'
                ._DEFAULT = default unless default_type == 'nil' or equal ._DEFAULT, default
            else
                ._DEFAULT = default if ._DEFAULT != default and default_type != 'nil'

T.database = database
T.database_char = database_char


Fix typo, thanks @deltheil!

Fix typo, thanks @deltheil!

class About extends require "views.base"
  content: =>

    h2 class: "list-header", "About Lua Toolbox"
    div class: "cell", ->
        p ->
            text "Lua Toolbox is an "
            a href: "https://github.com/catwell/lua-toolbox/", "Open Source"
            text " website aiming at helping with the discovery and selection"
            text " of Lua modules."
        p ->
            text "For that purpose, it lets its users endorse modules and"
            text " ranks them by number of endorsements. It also has a labels"
            text " system to classify modules by intended use."
        p ->
            text "To help with the project or report bugs, use GitHub issues."
            text " To report security issues that should be fixed before they"
            text " are disclosed, please get in touch with "
            a href:  "http://catwell.info", "Pierre Chapuis"
            text "."

    h2 class: "list-header", "Metrics"
    div class: "cell", ->
        h3 "Endorsements per module"
        object type: "image/svg+xml", data: table.concat {
            "#{@chartspree_root}/bar.svg",
            "?series=#{table.concat(@endorsement_data.values, ",")}",
            "&_labels=#{table.concat(@endorsement_data.labels, ",")}",
            "&_show_legend=false",
            "&_height=300px",
        }

class About extends require "views.base"
  content: =>

    h2 class: "list-header", "About Lua Toolbox"
    div class: "cell", ->
        p ->
            text "Lua Toolbox is an "
            a href: "https://github.com/catwell/lua-toolbox/", "Open Source"
            text " website aiming at helping with the discovery and selection"
            text " of Lua modules."
        p ->
            text "For that pupose, it lets its users endorse modules and"
            text " ranks them by number of endorsements. It also has a labels"
            text " system to classify modules by intended use."
        p ->
            text "To help with the project or report bugs, use GitHub issues."
            text " To report security issues that should be fixed before they"
            text " are disclosed, please get in touch with "
            a href:  "http://catwell.info", "Pierre Chapuis"
            text "."

    h2 class: "list-header", "Metrics"
    div class: "cell", ->
        h3 "Endorsements per module"
        object type: "image/svg+xml", data: table.concat {
            "#{@chartspree_root}/bar.svg",
            "?series=#{table.concat(@endorsement_data.values, ",")}",
            "&_labels=#{table.concat(@endorsement_data.labels, ",")}",
            "&_show_legend=false",
            "&_height=300px",
        }


plugins/sasl: Add EXTERNAL mechanism

plugins/sasl: Add EXTERNAL mechanism

import to_base64 from require 'basexx'
import IRCClient from require 'irc'

handlers =
	-- TODO abstract
	EXTERNAL: =>
		c = coroutine.create (auth)=>
			prefix, command, args, trail = coroutine.yield "AUTHENTICATE EXTERNAL"
			assert args[#args] == "+", "Unable to continue: #{trail}"
			prefix, command, args, trail = coroutine.yield "AUTHENTICATE +"
			assert command == "903" or command == "900", trail

			@fire_hook 'CAP_ACK'
			@sasl_handler = nil
	PLAIN: =>
		c = coroutine.create (auth)=>
			prefix, command, args, trail = coroutine.yield "AUTHENTICATE PLAIN"
			assert args[#args] == "+", "Unable to continue: #{trail}"
			payload = ""
			payload ..= "#{auth.identity or auth.username}\000"
			payload ..= "#{auth.username}\000#{auth.password}"
			payload = "AUTHENTICATE #{to_base64 payload}"
			prefix, command, args, trail = coroutine.yield payload
			assert command == "903" or command == "900", trail

			-- destroy self on finish
			@fire_hook 'CAP_ACK' -- finished SASL, release lock on CAP END
			@sasl_handler = nil

		@send_raw select(2, assert(coroutine.resume c, self, @config.auth))
		return c

IRCClient\add_hook 'CAP_ACK.sasl', (values = 'PLAIN')=>
	return unless @config.auth
	methods = [w for w in values\gmatch "[^,]+"]
	for method in *methods
		if handlers[method] and @config.auth.mechanism == method
			-- found usable method
			@set_caps += 1
			@sasl_handler = handlers[method] self, @config.auth

for command in *{"AUTHENTICATE", "900", "901", "903", "904", "905", "906"}
	IRCClient\add_handler command, (prefix, args, trail)=>
		return if not @sasl_handler
		ok, result = coroutine.resume @sasl_handler, prefix, command, args, trail
		if not ok
			@sasl_handler = nil
			@fire_hook 'CAP_ACK' -- failed SASL, release lock on CAP END
			error "Error in SASL mechanism: #{result}"
		else
			@send_raw result

import to_base64 from require 'basexx'
import IRCClient from require 'irc'

handlers =
	PLAIN: =>
		c = coroutine.create (auth)=>
			prefix, command, args, trail = coroutine.yield "AUTHENTICATE PLAIN"
			assert args[#args] == "+", "Unable to continue: #{table.concat(args)}"
			payload = ""
			payload ..= "#{auth.identity or auth.username}\000"
			payload ..= "#{auth.username}\000#{auth.password}"
			payload = "AUTHENTICATE #{to_base64 payload}"
			prefix, command, args, trail = coroutine.yield payload
			assert command == "903" or command == "900", trail

			-- destroy self on finish
			@fire_hook 'CAP_ACK' -- finished SASL, release lock on CAP END
			@sasl_handler = nil

		@send_raw select(2, assert(coroutine.resume c, self, @config.auth))
		return c

IRCClient\add_hook 'CAP_ACK.sasl', (values = 'PLAIN')=>
	return unless @config.auth
	methods = [w for w in values\gmatch "[^,]+"]
	for method in *methods
		if handlers[method] and @config.auth.mechanism == method
			-- found usable method
			@set_caps += 1
			@sasl_handler = handlers[method] self, @config.auth

for command in *{"AUTHENTICATE", "900", "901", "903", "904", "905", "906"}
	IRCClient\add_handler command, (prefix, args, trail)=>
		return if not @sasl_handler
		ok, result = coroutine.resume @sasl_handler, prefix, command, args, trail
		if not ok
			@sasl_handler = nil
			@fire_hook 'CAP_ACK' -- failed SASL, release lock on CAP END
			error "Error in SASL mechanism: #{result}"
		else
			@send_raw result


i messed up, but strangely broke nothing

import run_with_scope, defaultbl from require "moon"
import insert from require "table"

local parse_to_table

args = (first, ...)->
  if #{...}>0
    {first, ...}
  else
    first

call = (name, first, ...)=>
  if type(first) == "function"
    insert @_buffer, {:name, value: parse_to_table first, args ...}
  else
    insert @_buffer, {:name, value: args first, ...}

index = (name)=>
  (...) -> call @, name, ...

parse_to_table=(fn)->
  buf={}
  run_with_scope fn, defaultbl({_buffer:buf},index)
  buf

parse_to_table

import run_with_scope, defaultbl from require "moon"
import insert from require "table"

local parse_to_table

args = (first, ...)->
  if #{...}>0
    {first, ...}
  else
    first

call = (name, first, ...)=>
  if type(first) == "function"
    insert @_buffer, {:name, value: parse_to_table first, args ...}
  else
    insert @_buffer, {:name, value: args first, ...}

index = (name)=>
  (...) -> call @, name, ...

parse_to_table=(fn, ...)->
  buf={}
  run_with_scope fn, defaultbl({_buffer:buf},index), @, ...
  buf

parse_to_table


i don't remember why i did it this way

i don't remember why i did it this way

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

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

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

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

import FrameNumberL, RealTimeL, PPM2 from _G
import GetPonyData, IsDormant, PPMBonesModifier, IsPony from FindMetaTable('Entity')

RENDER_HORN_GLOW = CreateConVar('ppm2_horn_glow', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn glow when player uses physgun')
HORN_PARTICLES = CreateConVar('ppm2_horn_particles', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn particles when player uses physgun')
HORN_FP = CreateConVar('ppm2_horn_firstperson', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn effetcs in first person')
HORN_HIDE_BEAM = CreateConVar('ppm2_horn_nobeam', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Hide physgun beam')
DRAW_LEGS_DEPTH = CreateConVar('ppm2_render_legsdepth', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Render legs in depth pass. Useful with Boken DoF enabled')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
PPM2.ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_phys_ragdolls', '0', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable physics ragdolls (Pre March 2020 gmod update workaround)')
ENABLE_NEW_RAGDOLLS = PPM2.ENABLE_NEW_RAGDOLLS
SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_cl_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')
SV_SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_sv_draw_hands', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Should draw hooves as viewmodel')

VM_MAGIC = CreateConVar('ppm2_cl_vm_magic', '0', {FCVAR_ARCHIVE, FCVAR_USERINFO}, 'Modify viewmodel when pony has horn for more immersion. Has no effect when ppm2_cl_vm_magic_hands is on')
VM_MAGIC_HANDS = CreateConVar('ppm2_cl_vm_magic_hands', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_USERINFO}, 'Use magic hands when pony has horn. Due to gmod behavior, sometimes this does not work')

PPM2.VM_MAGIC = VM_MAGIC
PPM2.VM_MAGIC_HANDS = VM_MAGIC_HANDS

validHands = (hands) -> hands == 'models/ppm/c_arms_pony.mdl'

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS or not IsValid(arms) or not ply.__cachedIsPony
	observer = ply\GetObserverTarget()
	return if IsValid(observer) and not observer.__cachedIsPony
	return true if not SV_SHOULD_DRAW_VIEWMODEL\GetBool() or not SHOULD_DRAW_VIEWMODEL\GetBool()

	if IsValid(observer)
		return if not observer\Alive()
	else
		return if not ply\Alive()

	arms\SetPos(ply\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()

	return true if IsValid(wep) and wep.UseHands == false
	amodel = arms\GetModel()
	return if not validHands(amodel)

	data = ply\GetPonyData()
	return unless data

	return true if data\GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) ~= 0 and VM_MAGIC\GetBool() and not VM_MAGIC_HANDS\GetBool()

	status = data\GetRenderController()\PreDrawArms(arms)
	return status if status ~= nil
	arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return unless IsValid(arms)
	return unless arms.__ppm2_draw
	data = ply\GetPonyData()
	return unless data
	data\GetRenderController()\PostDrawArms(arms)
	arms.__ppm2_draw = false

local lastPos, lastAng

CalcViewModelView = (weapon, vm, oldPos, oldAng, pos, ang) ->
	return if not VM_MAGIC\GetBool() or VM_MAGIC_HANDS\GetBool()
	ply = LocalPlayer()
	return if PPM2.__RENDERING_REFLECTIONS or not IsValid(vm) or not ply.__cachedIsPony
	observer = ply\GetObserverTarget()
	return if IsValid(observer) and not observer.__cachedIsPony
	return if not SV_SHOULD_DRAW_VIEWMODEL\GetBool() or not SHOULD_DRAW_VIEWMODEL\GetBool()

	if IsValid(observer)
		return if not observer\Alive()
	else
		return if not ply\Alive()

	return if ply\GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) == 0
	return if IsValid(weapon) and weapon.UseHands == false
	shouldShift = not (weapon.IsTFA and weapon\IsTFA() and weapon.IronSightsProgress > 0.6)
	-- since pos and ang never get refreshed, i would modify those to get desired results
	-- with other addons installed

	if shouldShift
		fwd = ply\EyeAngles()\Forward()
		right = ply\EyeAngles()\Right()
		pos2 = pos + fwd * math.sin(CurTimeL()) + right * math.cos(CurTimeL() + 1.2) + right * 4 - fwd * 2
		ang2 = Angle(ang)
		pos.z -= 4
		ang2\RotateAroundAxis(ang\Forward(), -30)
		lastPos = pos2
		lastAng = ang2
	else
		lastPos = LerpVector(FrameTime() * 11, lastPos or pos, pos)
		lastAng = LerpAngle(FrameTime() * 11, lastAng or ang, ang)

	pos.x, pos.y, pos.z = lastPos.x, lastPos.y, lastPos.z
	ang.p, ang.y, ang.r = lastAng.p, lastAng.y, lastAng.r

hook.Add 'CalcViewModelView', 'PPM2.ViewModel', CalcViewModelView, -3

-- indraw = false
--
-- magicMat = CreateMaterial('ppm2_magic_material', 'UnlitGeneric', {
--  '$basetexture': 'models/debug/debugwhite'
--  '$ignorez': 1
--  '$vertexcolor': 1
--  '$vertexalpha': 1
--  '$nolod': 1
--  '$color2': '{255 255 255}'
-- })

-- hook.Add 'PostDrawViewModel', 'PPM2.ViewModel', (vm, ply, weapon) ->
--  return if indraw
--  return if not VM_MAGIC\GetBool() or VM_MAGIC_HANDS\GetBool()
--  return if not IsValid(vm) or not IsValid(ply) or not IsValid(weapon)
--  return if PPM2.__RENDERING_REFLECTIONS or not IsValid(vm) or not ply.__cachedIsPony
--  observer = ply\GetObserverTarget()
--  return if IsValid(observer) and not observer.__cachedIsPony
--  return if not SV_SHOULD_DRAW_VIEWMODEL\GetBool() or not SHOULD_DRAW_VIEWMODEL\GetBool()
--  return if IsValid(weapon) and weapon.UseHands == false
--
--  if IsValid(observer)
--      return if not observer\Alive()
--  else
--      return if not ply\Alive()
--
--  return if ply\GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) == 0
--  data = ply\GetPonyData()
--  return if not data
--
--  indraw = true
--  color = data\ComputeMagicColor()
--
--  magicMat\SetVector('$color2', color\ToVector())
--  render.MaterialOverride(magicMat)
--  render.ModelMaterialOverride(magicMat)
--
--  cam.IgnoreZ(true)
--
--  mat = Matrix()
--  mat\Scale(Vector(1.1, 1.1, 1.1))
--  vm\EnableMatrix('RenderMultiply', mat)
--
--  ply\DrawModel()
--  vm\DisableMatrix('RenderMultiply')
--
--  cam.IgnoreZ(false)
--
--  render.MaterialOverride()
--  render.ModelMaterialOverride()
--  indraw = false

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.CheckDXLevel', 180, 0, ->
	if mat_dxlevel\GetInt() > 90
		timer.Remove 'PPM2.CheckDXLevel'
		return

	PPM2.Message('Direct3D Level is LESS THAN 9.1! This will not work!')

IN_DRAW = false

player_GetAll = player.GetAll

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

PPM2.PostDrawTranslucentRenderables = (bDrawingDepth, bDrawingSkybox) ->
	if LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

PPM2.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for _, ply in ipairs player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTimeL() + 2 if not alive
			if ply.__cachedIsPony
				if ply\GetPonyData() and not alive
					data = ply\GetPonyData()
					rag = ply\GetRagdollEntity()
					if IsValid(rag)
						renderController = data\GetRenderController()
						data\DoRagdollMerge()
						if renderController
							renderController\PreDraw(rag)
							IN_DRAW = true
							rag\DrawModel()
							IN_DRAW = false
							renderController\PostDraw(rag)

Think = ->
	for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
		ent = task.ent
		if IsValid(ent) and ent.__cachedIsPony
			if ent.__ppm2_task_hit
				ent.__ppm2_task_hit = false
				ent\SetNoDraw(false)

			if not ent.__ppm2RenderOverride
				ent = ent
				ent.__ppm2_oldRenderOverride = ent.RenderOverride
				ent.__ppm2RenderOverride = ->
					renderController = task\GetRenderController()
					renderController\PreDraw(ent, true)

					if ent.__ppm2_oldRenderOverride
						ent.__ppm2_oldRenderOverride(ent)
					else
						ent\DrawModel()

					renderController\PostDraw(ent, true)
				ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if PPM2.__RENDERING_REFLECTIONS

	with data = GetPonyData(@)
		return if not data
		@__cachedIsPony = IsPony(@)
		return if not @__cachedIsPony
		f = FrameNumberL()
		return if @__ppm2_last_draw == f

		renderController = data\GetRenderController()
		if renderController
			renderController\PreDraw()

		@__ppm2_last_draw = f
		bones = PPMBonesModifier(@)
		if data and bones\CanThink()
			bones\ResetBones()
			hook.Call('PPM2.SetupBones', nil, @, data) if data
			bones\Think()
			@_ppmBonesModified = true

PPM2.PostPlayerDraw = =>
	return if PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data or not @__cachedIsPony
		renderController = data\GetRenderController()
		renderController\PostDraw() if renderController
		return

do
	hornGlowStatus = {}
	smokeMaterial = 'ppm2/hornsmoke'
	fireMat = 'particle/fire'
	hornShift = Vector(1, 0.15, 14.5)

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumberL()
		cTime = (RealTimeL() % 20) * 4
		for ent, status in pairs hornGlowStatus
			if IsValid(ent) and status.frame == frame and IsValid(status.target)
				additional = math.sin(cTime / 2 + status.haloSeed * 3) * 40
				newCol = status.color + Color(additional, additional, additional)
				halo.Add({status.target}, newCol, math.sin(cTime + status.haloSeed) * 4 + 8, math.cos(cTime + status.haloSeed) * 4 + 8, 2)

	hook.Add 'Think', 'PPM2.HornEffects', =>
		frame = FrameNumberL()
		for ent, status in pairs hornGlowStatus
			if not IsValid(ent)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			elseif status.frame ~= frame
				status.data\SetHornGlow(status.prevStatus)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			else
				if not status.prevStatus and RENDER_HORN_GLOW\GetBool() and status.data\GetHornGlow() ~= status.isEnabled
					status.data\SetHornGlow(status.isEnabled)
				if status.attach and IsValid(status.target)
					grabHornPos = Vector(hornShift) * status.data\GetPonySize()
					{:Pos, :Ang} = ent\GetAttachment(status.attach)
					grabHornPos\Rotate(Ang)
					if status.isEnabled and IsValid(status.emmiter) and status.nextSmokeParticle < RealTimeL()
						status.nextSmokeParticle = RealTimeL() + math.Rand(0.1, 0.2)
						for i = 1, math.random(1, 4)
							vec = VectorRand()
							calcPos = Pos + grabHornPos + vec
							with particle = status.emmiter\Add(smokeMaterial, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.6, 0.9)
								\SetStartAlpha(math.random(80, 170))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								size = math.Rand(2, 3)
								\SetEndSize(math.Rand(2, size))
								\SetStartSize(size)
								\SetGravity(Vector())
								\SetAirResistance(10)
								vecRand = VectorRand()
								vecRand.z *= 2
								\SetVelocity(ent\GetVelocity() + vecRand * status.data\GetPonySize() * 2)
								\SetCollide(false)
					if status.isEnabled and IsValid(status.emmiterProp) and status.nextGrabParticle < RealTimeL() and status.mins and status.maxs
						status.nextGrabParticle = RealTimeL() + math.Rand(0.05, 0.3)
						status.emmiterProp\SetPos(status.tpos)
						for i = 1, math.random(2, 6)
							calcPos = Vector(math.Rand(status.mins.x, status.maxs.x), math.Rand(status.mins.y, status.maxs.y), math.Rand(status.mins.z, status.maxs.z))
							with particle = status.emmiterProp\Add(fireMat, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.5, 0.9)
								\SetStartAlpha(math.random(130, 230))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								\SetEndSize(0)
								\SetStartSize(math.Rand(2, 6))
								\SetGravity(Vector())
								\SetAirResistance(15)
								\SetVelocity(VectorRand() * 6)
								\SetCollide(false)

	hook.Add 'DrawPhysgunBeam', 'PPM2.HornEffects', (physgun = NULL, isEnabled = false, target = NULL, bone = 0, hitPos = Vector()) =>
		return if not @IsPony() or not HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if @GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) == 0
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumberL()
				prevStatus: data\GetHornGlow()
				:data, :isEnabled, :hitPos, :target, :bone
				tpos: @GetPos()
				attach: @LookupAttachment('eyes')
				nextSmokeParticle: 0
				nextGrabParticle: 0
			}

			with hornGlowStatus[@]
				if HORN_PARTICLES\GetBool()
					.emmiter = ParticleEmitter(EyePos())
					.emmiterProp = ParticleEmitter(EyePos())

				.color = data\ComputeMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))
		else
			with hornGlowStatus[@]
				.frame = FrameNumberL()
				.isEnabled = isEnabled
				.target = target
				.bone = bone
				.hitPos = hitPos
				if IsValid(target)
					.tpos = target\GetPos() + hitPos
					center = target\WorldSpaceCenter()
					.center = center
					mins, maxs = target\WorldSpaceAABB()
					.mins = center + (mins - center) * 1.2
					.maxs = center + (maxs - center) * 1.2
		return false if HORN_HIDE_BEAM\GetBool() and IsValid(target)

import ScrWL, ScrHL from _G
import HUDCommons from DLib
color_black = Color(color_black)

hook.Add 'HUDPaint', 'PPM2.LoadingDisplay', ->
	lply = LocalPlayer()
	lpos = EyePos()

	for task in *PPM2.NetworkedPonyData.CheckTasks
		if ent = task\GetEntity()
			if IsValid(ent) and ent\IsPony() and not ent\IsRagdoll() and (ent ~= lply or lply\ShouldDrawLocalPlayer()) and (not ent.Alive or ent\Alive())
				if renderer = task\GetRenderController()
					if textures = renderer\GetTextureController()
						if textures\IsBeingProcessed()
							pos = ent\GetPos()
							pos\Add(ent\OBBCenter())
							dist = pos\Distance(lpos)

							if dist < 384
								{:x, :y, :visible} = pos\ToScreen()

								if visible and x > -100 and x < ScrWL() + 100 and y > -100 and y < ScrHL() + 100
									color = task\ComputeMagicColor()
									color.a = 255 - dist\progression(128, 384) * 255
									color_black.a = color.a
									size = (200 * (1 - dist\progression(0, 300))) * task\GetPonySize() * task\GetLegsSize() * task\GetNeckSize() * task\GetBackSize()
									sizeh = size / 2

									HUDCommons.DrawLoading(x + 2 - sizeh, y + 2 - sizeh, size, color_black)
									HUDCommons.DrawLoading(x - sizeh, y - sizeh, size, color)

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, -2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, -2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, -2
hook.Add 'Think', 'PPM2.UpdateRenderTasks', Think, -2
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, -2
hook.Add 'PostDrawTranslucentRenderables', 'PPM2.PostDrawTranslucentRenderables', PPM2.PostDrawTranslucentRenderables, -2

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

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

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

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

import FrameNumberL, RealTimeL, PPM2 from _G
import GetPonyData, IsDormant, PPMBonesModifier, IsPony from FindMetaTable('Entity')

RENDER_HORN_GLOW = CreateConVar('ppm2_horn_glow', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn glow when player uses physgun')
HORN_PARTICLES = CreateConVar('ppm2_horn_particles', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn particles when player uses physgun')
HORN_FP = CreateConVar('ppm2_horn_firstperson', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Visual horn effetcs in first person')
HORN_HIDE_BEAM = CreateConVar('ppm2_horn_nobeam', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Hide physgun beam')
DRAW_LEGS_DEPTH = CreateConVar('ppm2_render_legsdepth', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Render legs in depth pass. Useful with Boken DoF enabled')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
PPM2.ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_phys_ragdolls', '0', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable physics ragdolls (Pre March 2020 gmod update workaround)')
ENABLE_NEW_RAGDOLLS = PPM2.ENABLE_NEW_RAGDOLLS
SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_cl_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')
SV_SHOULD_DRAW_VIEWMODEL = CreateConVar('ppm2_sv_draw_hands', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Should draw hooves as viewmodel')

VM_MAGIC = CreateConVar('ppm2_cl_vm_magic', '0', {FCVAR_ARCHIVE, FCVAR_USERINFO}, 'Modify viewmodel when pony has horn for more immersion. Has no effect when ppm2_cl_vm_magic_hands is on')
VM_MAGIC_HANDS = CreateConVar('ppm2_cl_vm_magic_hands', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_USERINFO}, 'Use magic hands when pony has horn. Due to gmod behavior, sometimes this does not work')

PPM2.VM_MAGIC = VM_MAGIC
PPM2.VM_MAGIC_HANDS = VM_MAGIC_HANDS

validHands = (hands) -> hands == 'models/ppm/c_arms_pony.mdl'

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS or not IsValid(arms) or not ply.__cachedIsPony
	observer = ply\GetObserverTarget()
	return if IsValid(observer) and not observer.__cachedIsPony
	return true if not SV_SHOULD_DRAW_VIEWMODEL\GetBool() or not SHOULD_DRAW_VIEWMODEL\GetBool()

	if IsValid(observer)
		return if not observer\Alive()
	else
		return if not ply\Alive()

	arms\SetPos(ply\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()

	return true if IsValid(wep) and wep.UseHands == false
	amodel = arms\GetModel()
	return if not validHands(amodel)

	data = ply\GetPonyData()
	return unless data

	return true if data\GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) ~= 0 and VM_MAGIC\GetBool() and not VM_MAGIC_HANDS\GetBool()

	status = data\GetRenderController()\PreDrawArms(arms)
	return status if status ~= nil
	arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return unless IsValid(arms)
	return unless arms.__ppm2_draw
	data = ply\GetPonyData()
	return unless data
	data\GetRenderController()\PostDrawArms(arms)
	arms.__ppm2_draw = false

local lastPos, lastAng

CalcViewModelView = (weapon, vm, oldPos, oldAng, pos, ang) ->
	return if not VM_MAGIC\GetBool() or VM_MAGIC_HANDS\GetBool()
	ply = LocalPlayer()
	return if PPM2.__RENDERING_REFLECTIONS or not IsValid(vm) or not ply.__cachedIsPony
	observer = ply\GetObserverTarget()
	return if IsValid(observer) and not observer.__cachedIsPony
	return if not SV_SHOULD_DRAW_VIEWMODEL\GetBool() or not SHOULD_DRAW_VIEWMODEL\GetBool()

	if IsValid(observer)
		return if not observer\Alive()
	else
		return if not ply\Alive()

	return if ply\GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) == 0
	return if IsValid(weapon) and weapon.UseHands == false
	shouldShift = not (weapon.IsTFA and weapon\IsTFA() and weapon.IronSightsProgress > 0.6)
	-- since pos and ang never get refreshed, i would modify those to get desired results
	-- with other addons installed

	if shouldShift
		fwd = ply\EyeAngles()\Forward()
		right = ply\EyeAngles()\Right()
		pos2 = pos + fwd * math.sin(CurTimeL()) + right * math.cos(CurTimeL() + 1.2) + right * 4 - fwd * 2
		ang2 = Angle(ang)
		pos.z -= 4
		ang2\RotateAroundAxis(ang\Forward(), -30)
		lastPos = pos2
		lastAng = ang2
	else
		lastPos = LerpVector(FrameTime() * 11, lastPos or pos, pos)
		lastAng = LerpAngle(FrameTime() * 11, lastAng or ang, ang)

	pos.x, pos.y, pos.z = lastPos.x, lastPos.y, lastPos.z
	ang.p, ang.y, ang.r = lastAng.p, lastAng.y, lastAng.r

hook.Add 'CalcViewModelView', 'PPM2.ViewModel', CalcViewModelView, -3

-- indraw = false
--
-- magicMat = CreateMaterial('ppm2_magic_material', 'UnlitGeneric', {
--  '$basetexture': 'models/debug/debugwhite'
--  '$ignorez': 1
--  '$vertexcolor': 1
--  '$vertexalpha': 1
--  '$nolod': 1
--  '$color2': '{255 255 255}'
-- })

-- hook.Add 'PostDrawViewModel', 'PPM2.ViewModel', (vm, ply, weapon) ->
--  return if indraw
--  return if not VM_MAGIC\GetBool() or VM_MAGIC_HANDS\GetBool()
--  return if not IsValid(vm) or not IsValid(ply) or not IsValid(weapon)
--  return if PPM2.__RENDERING_REFLECTIONS or not IsValid(vm) or not ply.__cachedIsPony
--  observer = ply\GetObserverTarget()
--  return if IsValid(observer) and not observer.__cachedIsPony
--  return if not SV_SHOULD_DRAW_VIEWMODEL\GetBool() or not SHOULD_DRAW_VIEWMODEL\GetBool()
--  return if IsValid(weapon) and weapon.UseHands == false
--
--  if IsValid(observer)
--      return if not observer\Alive()
--  else
--      return if not ply\Alive()
--
--  return if ply\GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) == 0
--  data = ply\GetPonyData()
--  return if not data
--
--  indraw = true
--  color = data\ComputeMagicColor()
--
--  magicMat\SetVector('$color2', color\ToVector())
--  render.MaterialOverride(magicMat)
--  render.ModelMaterialOverride(magicMat)
--
--  cam.IgnoreZ(true)
--
--  mat = Matrix()
--  mat\Scale(Vector(1.1, 1.1, 1.1))
--  vm\EnableMatrix('RenderMultiply', mat)
--
--  ply\DrawModel()
--  vm\DisableMatrix('RenderMultiply')
--
--  cam.IgnoreZ(false)
--
--  render.MaterialOverride()
--  render.ModelMaterialOverride()
--  indraw = false

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.CheckDXLevel', 180, 0, ->
	if mat_dxlevel\GetInt() > 90
		timer.Remove 'PPM2.CheckDXLevel'
		return

	PPM2.Message('Direct3D Level is LESS THAN 9.1! This will not work!')

IN_DRAW = false
MARKED_FOR_DRAW = {}

player_GetAll = player.GetAll

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	MARKED_FOR_DRAW = {}

	for ply in *player_GetAll()
		if not IsDormant(ply)
			p = IsPony(ply)
			ply.__cachedIsPony = p
			if p
				data = GetPonyData(ply)
				if data
					renderController = data\GetRenderController()
					if renderController
						renderController\PreDraw()
						table.insert(MARKED_FOR_DRAW, renderController)

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

PPM2.PostDrawTranslucentRenderables = (bDrawingDepth, bDrawingSkybox) ->
	if not bDrawingDepth and not bDrawingSkybox
		for _, draw in ipairs MARKED_FOR_DRAW
			draw\PostDraw()

	if LEGS_RENDER_TYPE\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegs()
					IN_DRAW = false

PPM2.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
	return if IN_DRAW or PPM2.__RENDERING_REFLECTIONS

	if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
		with LocalPlayer()
			if .__cachedIsPony and \Alive()
				if data = \GetPonyData()
					IN_DRAW = true
					data\GetRenderController()\DrawLegsDepth()
					IN_DRAW = false

	return if bDrawingDepth or bDrawingSkybox

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for _, ply in ipairs player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTimeL() + 2 if not alive
			if ply.__cachedIsPony
				if ply\GetPonyData() and not alive
					data = ply\GetPonyData()
					rag = ply\GetRagdollEntity()
					if IsValid(rag)
						renderController = data\GetRenderController()
						data\DoRagdollMerge()
						if renderController
							renderController\PreDraw(rag)
							IN_DRAW = true
							rag\DrawModel()
							IN_DRAW = false
							renderController\PostDraw(rag)

Think = ->
	for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
		ent = task.ent
		if IsValid(ent) and ent.__cachedIsPony
			if ent.__ppm2_task_hit
				ent.__ppm2_task_hit = false
				ent\SetNoDraw(false)

			if not ent.__ppm2RenderOverride
				ent = ent
				ent.__ppm2_oldRenderOverride = ent.RenderOverride
				ent.__ppm2RenderOverride = ->
					renderController = task\GetRenderController()
					renderController\PreDraw(ent, true)

					if ent.__ppm2_oldRenderOverride
						ent.__ppm2_oldRenderOverride(ent)
					else
						ent\DrawModel()

					renderController\PostDraw(ent, true)
				ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if PPM2.__RENDERING_REFLECTIONS

	with data = GetPonyData(@)
		return if not data
		@__cachedIsPony = IsPony(@)
		return if not @__cachedIsPony
		f = FrameNumberL()
		return if @__ppm2_last_draw == f
		@__ppm2_last_draw = f
		bones = PPMBonesModifier(@)
		if data and bones\CanThink()
			bones\ResetBones()
			hook.Call('PPM2.SetupBones', nil, @, data) if data
			bones\Think()
			@_ppmBonesModified = true

PPM2.PostPlayerDraw = =>
	return if PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data or not @__cachedIsPony
		renderController = data\GetRenderController()
		renderController\PostDraw() if renderController
		return

do
	hornGlowStatus = {}
	smokeMaterial = 'ppm2/hornsmoke'
	fireMat = 'particle/fire'
	hornShift = Vector(1, 0.15, 14.5)

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumberL()
		cTime = (RealTimeL() % 20) * 4
		for ent, status in pairs hornGlowStatus
			if IsValid(ent) and status.frame == frame and IsValid(status.target)
				additional = math.sin(cTime / 2 + status.haloSeed * 3) * 40
				newCol = status.color + Color(additional, additional, additional)
				halo.Add({status.target}, newCol, math.sin(cTime + status.haloSeed) * 4 + 8, math.cos(cTime + status.haloSeed) * 4 + 8, 2)

	hook.Add 'Think', 'PPM2.HornEffects', =>
		frame = FrameNumberL()
		for ent, status in pairs hornGlowStatus
			if not IsValid(ent)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			elseif status.frame ~= frame
				status.data\SetHornGlow(status.prevStatus)
				status.emmiter\Finish() if IsValid(status.emmiter)
				status.emmiterProp\Finish() if IsValid(status.emmiterProp)
				hornGlowStatus[ent] = nil
			else
				if not status.prevStatus and RENDER_HORN_GLOW\GetBool() and status.data\GetHornGlow() ~= status.isEnabled
					status.data\SetHornGlow(status.isEnabled)
				if status.attach and IsValid(status.target)
					grabHornPos = Vector(hornShift) * status.data\GetPonySize()
					{:Pos, :Ang} = ent\GetAttachment(status.attach)
					grabHornPos\Rotate(Ang)
					if status.isEnabled and IsValid(status.emmiter) and status.nextSmokeParticle < RealTimeL()
						status.nextSmokeParticle = RealTimeL() + math.Rand(0.1, 0.2)
						for i = 1, math.random(1, 4)
							vec = VectorRand()
							calcPos = Pos + grabHornPos + vec
							with particle = status.emmiter\Add(smokeMaterial, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.6, 0.9)
								\SetStartAlpha(math.random(80, 170))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								size = math.Rand(2, 3)
								\SetEndSize(math.Rand(2, size))
								\SetStartSize(size)
								\SetGravity(Vector())
								\SetAirResistance(10)
								vecRand = VectorRand()
								vecRand.z *= 2
								\SetVelocity(ent\GetVelocity() + vecRand * status.data\GetPonySize() * 2)
								\SetCollide(false)
					if status.isEnabled and IsValid(status.emmiterProp) and status.nextGrabParticle < RealTimeL() and status.mins and status.maxs
						status.nextGrabParticle = RealTimeL() + math.Rand(0.05, 0.3)
						status.emmiterProp\SetPos(status.tpos)
						for i = 1, math.random(2, 6)
							calcPos = Vector(math.Rand(status.mins.x, status.maxs.x), math.Rand(status.mins.y, status.maxs.y), math.Rand(status.mins.z, status.maxs.z))
							with particle = status.emmiterProp\Add(fireMat, calcPos)
								\SetRollDelta(math.rad(math.random(0, 360)))
								\SetPos(calcPos)
								life = math.Rand(0.5, 0.9)
								\SetStartAlpha(math.random(130, 230))
								\SetDieTime(life)
								\SetColor(status.color.r, status.color.g, status.color.b)
								\SetEndAlpha(0)
								\SetEndSize(0)
								\SetStartSize(math.Rand(2, 6))
								\SetGravity(Vector())
								\SetAirResistance(15)
								\SetVelocity(VectorRand() * 6)
								\SetCollide(false)

	hook.Add 'DrawPhysgunBeam', 'PPM2.HornEffects', (physgun = NULL, isEnabled = false, target = NULL, bone = 0, hitPos = Vector()) =>
		return if not @IsPony() or not HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if @GetPonyRaceFlags()\band(PPM2.RACE_HAS_HORN) == 0
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumberL()
				prevStatus: data\GetHornGlow()
				:data, :isEnabled, :hitPos, :target, :bone
				tpos: @GetPos()
				attach: @LookupAttachment('eyes')
				nextSmokeParticle: 0
				nextGrabParticle: 0
			}

			with hornGlowStatus[@]
				if HORN_PARTICLES\GetBool()
					.emmiter = ParticleEmitter(EyePos())
					.emmiterProp = ParticleEmitter(EyePos())

				.color = data\ComputeMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))
		else
			with hornGlowStatus[@]
				.frame = FrameNumberL()
				.isEnabled = isEnabled
				.target = target
				.bone = bone
				.hitPos = hitPos
				if IsValid(target)
					.tpos = target\GetPos() + hitPos
					center = target\WorldSpaceCenter()
					.center = center
					mins, maxs = target\WorldSpaceAABB()
					.mins = center + (mins - center) * 1.2
					.maxs = center + (maxs - center) * 1.2
		return false if HORN_HIDE_BEAM\GetBool() and IsValid(target)

import ScrWL, ScrHL from _G
import HUDCommons from DLib
color_black = Color(color_black)

hook.Add 'HUDPaint', 'PPM2.LoadingDisplay', ->
	lply = LocalPlayer()
	lpos = EyePos()

	for task in *PPM2.NetworkedPonyData.CheckTasks
		if ent = task\GetEntity()
			if IsValid(ent) and ent\IsPony() and not ent\IsRagdoll() and (ent ~= lply or lply\ShouldDrawLocalPlayer()) and (not ent.Alive or ent\Alive())
				if renderer = task\GetRenderController()
					if textures = renderer\GetTextureController()
						if textures\IsBeingProcessed()
							pos = ent\GetPos()
							pos\Add(ent\OBBCenter())
							dist = pos\Distance(lpos)

							if dist < 384
								{:x, :y, :visible} = pos\ToScreen()

								if visible and x > -100 and x < ScrWL() + 100 and y > -100 and y < ScrHL() + 100
									color = task\ComputeMagicColor()
									color.a = 255 - dist\progression(128, 384) * 255
									color_black.a = color.a
									size = (200 * (1 - dist\progression(0, 300))) * task\GetPonySize() * task\GetLegsSize() * task\GetNeckSize() * task\GetBackSize()
									sizeh = size / 2

									HUDCommons.DrawLoading(x + 2 - sizeh, y + 2 - sizeh, size, color_black)
									HUDCommons.DrawLoading(x - sizeh, y - sizeh, size, color)

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, -2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, -2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, -2
hook.Add 'Think', 'PPM2.UpdateRenderTasks', Think, -2
hook.Add 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, -2
hook.Add 'PostDrawTranslucentRenderables', 'PPM2.PostDrawTranslucentRenderables', PPM2.PostDrawTranslucentRenderables, -2


added profiles.registered_in_project_stage

import Model, enum from require "lapis.db.model"
import create_table, types, add_column, drop_column from require "lapis.db.schema"
Users = require "lazuli.modules.user_management.models.users"
config = (require "lapis.config").get!

class Profiles extends Model
  @getOrCreateByUser: (user)=>
    if type(user)=="number"
      user=Users\find user
    return nil, "user not found" unless user
    (@find{user_id: user.id}) or (@create{user_id: user.id, registered_in_project_stage: config.projectStage})

  @relations: {
    {"user",            belongs_to: "Users"}
    {"acl_gender",      belongs_to: "ACLs"}
    {"acl_birthday_y",  belongs_to: "ACLs"}
    {"acl_birthday_dm", belongs_to: "ACLs"}
    {"acl_about",       belongs_to: "ACLs"}
  }

  @get_relation_model: (name)=> switch name
    when "Users"
      require "lazuli.modules.user_management.models.users"
    when "ACLs"
      require "models.acls"

  @migrations: {
    ->
      create_table "profiles", {
        {"id", types.serial}
        {"user_id", types.integer}
        "PRIMARY KEY (id)"
      }
    ->
      add_column  "profiles", "birthday",             types.date null: true
      add_column  "profiles", "about",                types.text null: true
    ->
      add_column  "profiles", "privacy_birthday_dm",  types.boolean
      add_column  "profiles", "privacy_birthday_y",   types.boolean
      add_column  "profiles", "privacy_birthday_age", types.boolean default: true
    ->
      add_column  "profiles", "gender",               types.varchar null: true
      add_column  "profiles", "privacy_gender",       types.boolean
    ->
      drop_column "profiles", "privacy_gender"
      drop_column "profiles", "privacy_birthday_age"
      drop_column "profiles", "privacy_birthday_y"
      drop_column "profiles", "privacy_birthday_dm"
      add_column  "profiles", "acl_gender_id",        types.integer null: true
      add_column  "profiles", "acl_birthday_dm_id",   types.integer null: true
      add_column  "profiles", "acl_birthday_y_id",    types.integer null: true
    ->
      add_column  "profiles", "acl_about_id",    types.integer null: true
    ->
      add_column  "profiles", "registered_in_project_stage", types.varchar default: "alpha"
  }

import Model, enum from require "lapis.db.model"
import create_table, types, add_column, drop_column from require "lapis.db.schema"
Users = require "lazuli.modules.user_management.models.users"

class Profiles extends Model
  @getOrCreateByUser: (user)=>
    if type(user)=="number"
      user=Users\find user
    return nil, "user not found" unless user
    (@find{user_id: user.id}) or (@create{user_id: user.id})

  @relations: {
    {"user",            belongs_to: "Users"}
    {"acl_gender",      belongs_to: "ACLs"}
    {"acl_birthday_y",  belongs_to: "ACLs"}
    {"acl_birthday_dm", belongs_to: "ACLs"}
    {"acl_about",       belongs_to: "ACLs"}
  }

  @get_relation_model: (name)=> switch name
    when "Users"
      require "lazuli.modules.user_management.models.users"
    when "ACLs"
      require "models.acls"

  @migrations: {
    ->
      create_table "profiles", {
        {"id", types.serial}
        {"user_id", types.integer}
        "PRIMARY KEY (id)"
      }
    ->
      add_column  "profiles", "birthday",             types.date null: true
      add_column  "profiles", "about",                types.text null: true
    ->
      add_column  "profiles", "privacy_birthday_dm",  types.boolean
      add_column  "profiles", "privacy_birthday_y",   types.boolean
      add_column  "profiles", "privacy_birthday_age", types.boolean default: true
    ->
      add_column  "profiles", "gender",               types.varchar null: true
      add_column  "profiles", "privacy_gender",       types.boolean
    ->
      drop_column "profiles", "privacy_gender"
      drop_column "profiles", "privacy_birthday_age"
      drop_column "profiles", "privacy_birthday_y"
      drop_column "profiles", "privacy_birthday_dm"
      add_column  "profiles", "acl_gender_id",        types.integer null: true
      add_column  "profiles", "acl_birthday_dm_id",   types.integer null: true
      add_column  "profiles", "acl_birthday_y_id",    types.integer null: true
    ->
      add_column  "profiles", "acl_about_id",    types.integer null: true
  }


Fixed bad list reconstruction

Fixed bad list reconstruction

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

class BonesSequence extends DLib.SequenceBase
	new: (controller, data) =>
		super(controller, data)

		{
			'bones': @bonesNames
			'numid': @numid
		} = data

		@modifierID = controller\GetModifierID(@name .. '_sequence')
		@bonesFuncsPos = ['SetModifier' .. boneName .. 'Position' for boneName in *@bonesNames]
		@bonesFuncsScale = ['SetModifier' .. boneName .. 'Scale' for boneName in *@bonesNames]
		@bonesFuncsAngles = ['SetModifier' .. boneName .. 'Angles' for boneName in *@bonesNames]
		@ent = controller.ent
		@controller = controller
		@Launch()

	GetController: => @controller
	GetEntity: => @ent

	Think: (delta = 0) =>
		@ent = @controller.ent
		return false if not IsValid(@ent)
		super(delta)

	Stop: =>
		super()
		@controller\ResetModifiers(@name .. '_sequence')

	SetBonePosition: (id = 1, val = Vector(0, 0, 0)) => @controller[@bonesFuncsPos[id]] and @controller[@bonesFuncsPos[id]](@controller, @modifierID, val)
	SetBoneScale: (id = 1, val = 0) => @controller[@bonesFuncsScale[id]] and @controller[@bonesFuncsScale[id]](@controller, @modifierID, val)
	SetBoneAngles: (id = 1, val = Angle(0, 0, 0)) => @controller[@bonesFuncsAngles[id]] and @controller[@bonesFuncsAngles[id]](@controller, @modifierID, val)

PPM2.BonesSequence = BonesSequence

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

RESET_BONE_POS = Vector(0, 0, 0)
RESET_BONE_ANGLES = Angle(0, 0, 0)
RESET_BONE_SCALE = Vector(1, 1, 1)
resetBones = (ent) ->
	for i = 0, ent\GetBoneCount() - 1
		ent\ManipulateBonePosition(i, RESET_BONE_POS)
		ent\ManipulateBoneScale(i, RESET_BONE_SCALE)
		ent\ManipulateBoneAngles(i, RESET_BONE_ANGLES)

class PPM2.EntityBonesModifier extends DLib.SequenceHolder
	@OBJECTS = {}
	@resetBones = resetBones

	@SEQUENCES = {
		{
			'name': 'floppy_ears'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'Ear_L', 'Ear_R'}
			'reset': =>
				@SetBoneAngles(1, Angle(0, -84, -40))
				@SetBoneAngles(2, Angle(0, 84, -40))
			'func': (delta, timeOfAnim) =>
		}

		{
			'name': 'floppy_ears_weak'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'Ear_L', 'Ear_R'}
			'reset': =>
				@SetBoneAngles(1, Angle(0, -20, -20))
				@SetBoneAngles(2, Angle(0, 20, -20))
			'func': (delta, timeOfAnim) =>
		}

		{
			'name': 'forward_ears'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'Ear_L', 'Ear_R'}
			'reset': =>
				@SetBoneAngles(1, Angle(0, -15, -27))
				@SetBoneAngles(2, Angle(0, 15, -27))
			'func': (delta, timeOfAnim) =>
		}

		{
			'name': 'neck_flopping_backward'
			'autostart': false
			'repeat': false
			'time': 3
			'bones': {'LrigNeck3'}
			'reset': =>
			'func': (delta, timeOfAnim) =>
				@SetBoneAngles(1, Angle(0, -12 * timeOfAnim, math.sin(RealTime() * 4) * 20))
		}

		{
			'name': 'neck_backward'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'reset': => @SetBoneAngles(1, Angle(0, -12, 0))
		}

		{
			'name': 'neck_twitch'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'func': (delta, timeOfAnim) =>
				@SetBoneAngles(1, Angle(0, math.cos(RealTime() * 4) * 20, 0))
		}

		{
			'name': 'neck_twitch_fast'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'func': (delta, timeOfAnim) =>
				@SetBoneAngles(1, Angle(0, math.cos(RealTime() * 8) * 20, 0))
		}

		{
			'name': 'neck_left'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'reset': => @SetBoneAngles(1, Angle(14, 0, 12))
		}

		{
			'name': 'neck_right'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'reset': => @SetBoneAngles(1, Angle(-14, 0, -12))
		}

		{
			'name': 'forward_left'
			'autostart': false
			'repeat': false
			'time': 3
			'bones': {'LrigNeck3'}
			'reset': =>
				@SetBoneAngles(1, Angle(10, 12, -9))
		}

		{
			'name': 'forward_right'
			'autostart': false
			'repeat': false
			'time': 3
			'bones': {'LrigNeck3'}
			'reset': =>
				@SetBoneAngles(1, Angle(-10, 12, 9))
		}
	}

	@SequenceObject = BonesSequence

	hook.Add 'PreDrawOpaqueRenderables', 'PPM2.EntityBonesModifier', (a, b) ->
		return if a or b
		frame = FrameNumber()
		rtime = RealTime()
		for obj in *@OBJECTS
			if not obj\IsValid()
				oldObjects = @OBJECTS
				@OBJECTS = {}
				for obj in *oldObjects
					if obj\IsValid()
						table.insert(@OBJECTS, obj)
					else
						lent = obj.ent
						if IsValid(lent)
							lent.__ppmBonesModifiers = nil
				return

			if obj.callFrame ~= frame and (not obj.pac3Last or obj.pac3Last < rtime) and not obj.ent\IsDormant() and not obj.ent\GetNoDraw()
				resetBones(obj.ent)
				data = obj.ent\GetPonyData()
				hook.Call('PPM2.SetupBones', nil, StrongEntity(obj.ent), data) if data
				obj\Think()

	new: (ent = NULL) =>
		super()
		@ent = ent
		@bonesMappingID = {}
		@bonesMappingName = {}
		@bonesMapping = {}
		@bonesMappingForName = {}
		@bonesMappingForID = {}
		@bonesIterable = {}
		@boneCount = 0
		@isValid = false
		table.insert(@@OBJECTS, @)
		@lastCall = RealTime()
		@Setup() if IsValid(ent)

	Setup: (ent = @ent) =>
		return false if not IsValid(ent)
		@lastModel = ent\GetModel()
		@ClearModifiers()
		@isValid = true
		@ent = ent
		@bonesMappingID = {}
		@bonesMappingName = {}
		@boneCount = ent\GetBoneCount()
		@bonesIterable = for i = 0, @boneCount - 1
			name = ent\GetBoneName(i)
			@bonesMappingID[i] = name
			@bonesMapping[i] = name
			@bonesMappingName[name] = i
			@bonesMapping[name] = i
			@bonesMappingForName[name] = name
			@bonesMappingForName[i] = name
			@bonesMappingForID[name] = i
			@bonesMappingForID[i] = i
			@RegisterModifier(name .. 'Position', (-> Vector(0, 0, 0)), (-> Vector(0, 0, 0)))
			@RegisterModifier(name .. 'Scale', (-> Vector(0, 0, 0)), (-> Vector(0, 0, 0)))
			@RegisterModifier(name .. 'Angles', (-> Angle(0, 0, 0)), (-> Angle(0, 0, 0)))
			@SetupLerpTables(name .. 'Position')
			@SetupLerpTables(name .. 'Scale')
			@SetupLerpTables(name .. 'Angles')
			@SetLerpFunc(name .. 'Position', LerpVector)
			@SetLerpFunc(name .. 'Scale', LerpVector)
			@SetLerpFunc(name .. 'Angles', LerpAngle)
			--@RegisterModifier(name .. 'Jiggle', 0)
			{i, name, 'Calculate' .. name .. 'Position', 'Calculate' .. name .. 'Scale', 'Calculate' .. name .. 'Angles'}

	Think: (force = false) =>
		return if not super() or not force and @callFrame == FrameNumber()
		@callFrame = FrameNumber()
		for data in *@bonesIterable
			id = data[1]
			name = data[2]
			calc = data[3]
			calcScale = data[4]
			calcAngles = data[5]
			with @ent
				\ManipulateBonePosition(id, \GetManipulateBonePosition(id) + @[calc](@))
				\ManipulateBoneScale(id, \GetManipulateBoneScale(id) + @[calcScale](@))
				\ManipulateBoneAngles(id, \GetManipulateBoneAngles(id) + @[calcAngles](@))

	ResetBones: =>
		return if @pac3Last and @pac3Last > RealTime()
		resetBones(@ent)

	IsValid: => @isValid and @ent\IsValid()
	GetEntity: => @ent

with FindMetaTable('Entity')
	.PPMBonesModifier = =>
		return @__ppmBonesModifiers if IsValid(@__ppmBonesModifiers)
		@__ppmBonesModifiers = PPM2.EntityBonesModifier(@)
		@__ppmBonesModifiers.ent = @
		@__ppmBonesModifiers\Setup(@) if @__ppmBonesModifiers.lastModel ~= @GetModel()
		return @__ppmBonesModifiers

hook.Add 'PAC3ResetBones', 'PPM2.EntityBonesModifier', =>
	data = @GetPonyData()
	hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
	if @__ppmBonesModifiers
		@__ppmBonesModifiers\Think()
		@__ppmBonesModifiers.pac3Last = RealTime() + 0.2

ent.__ppmBonesModifiers = nil for ent in *ents.GetAll()

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

class BonesSequence extends DLib.SequenceBase
	new: (controller, data) =>
		super(controller, data)

		{
			'bones': @bonesNames
			'numid': @numid
		} = data

		@modifierID = controller\GetModifierID(@name .. '_sequence')
		@bonesFuncsPos = ['SetModifier' .. boneName .. 'Position' for boneName in *@bonesNames]
		@bonesFuncsScale = ['SetModifier' .. boneName .. 'Scale' for boneName in *@bonesNames]
		@bonesFuncsAngles = ['SetModifier' .. boneName .. 'Angles' for boneName in *@bonesNames]
		@ent = controller.ent
		@controller = controller
		@Launch()

	GetController: => @controller
	GetEntity: => @ent

	Think: (delta = 0) =>
		@ent = @controller.ent
		return false if not IsValid(@ent)
		super(delta)

	Stop: =>
		super()
		@controller\ResetModifiers(@name .. '_sequence')

	SetBonePosition: (id = 1, val = Vector(0, 0, 0)) => @controller[@bonesFuncsPos[id]] and @controller[@bonesFuncsPos[id]](@controller, @modifierID, val)
	SetBoneScale: (id = 1, val = 0) => @controller[@bonesFuncsScale[id]] and @controller[@bonesFuncsScale[id]](@controller, @modifierID, val)
	SetBoneAngles: (id = 1, val = Angle(0, 0, 0)) => @controller[@bonesFuncsAngles[id]] and @controller[@bonesFuncsAngles[id]](@controller, @modifierID, val)

PPM2.BonesSequence = BonesSequence

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

RESET_BONE_POS = Vector(0, 0, 0)
RESET_BONE_ANGLES = Angle(0, 0, 0)
RESET_BONE_SCALE = Vector(1, 1, 1)
resetBones = (ent) ->
	for i = 0, ent\GetBoneCount() - 1
		ent\ManipulateBonePosition(i, RESET_BONE_POS)
		ent\ManipulateBoneScale(i, RESET_BONE_SCALE)
		ent\ManipulateBoneAngles(i, RESET_BONE_ANGLES)

class PPM2.EntityBonesModifier extends DLib.SequenceHolder
	@OBJECTS = {}
	@resetBones = resetBones

	@SEQUENCES = {
		{
			'name': 'floppy_ears'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'Ear_L', 'Ear_R'}
			'reset': =>
				@SetBoneAngles(1, Angle(0, -84, -40))
				@SetBoneAngles(2, Angle(0, 84, -40))
			'func': (delta, timeOfAnim) =>
		}

		{
			'name': 'floppy_ears_weak'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'Ear_L', 'Ear_R'}
			'reset': =>
				@SetBoneAngles(1, Angle(0, -20, -20))
				@SetBoneAngles(2, Angle(0, 20, -20))
			'func': (delta, timeOfAnim) =>
		}

		{
			'name': 'forward_ears'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'Ear_L', 'Ear_R'}
			'reset': =>
				@SetBoneAngles(1, Angle(0, -15, -27))
				@SetBoneAngles(2, Angle(0, 15, -27))
			'func': (delta, timeOfAnim) =>
		}

		{
			'name': 'neck_flopping_backward'
			'autostart': false
			'repeat': false
			'time': 3
			'bones': {'LrigNeck3'}
			'reset': =>
			'func': (delta, timeOfAnim) =>
				@SetBoneAngles(1, Angle(0, -12 * timeOfAnim, math.sin(RealTime() * 4) * 20))
		}

		{
			'name': 'neck_backward'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'reset': => @SetBoneAngles(1, Angle(0, -12, 0))
		}

		{
			'name': 'neck_twitch'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'func': (delta, timeOfAnim) =>
				@SetBoneAngles(1, Angle(0, math.cos(RealTime() * 4) * 20, 0))
		}

		{
			'name': 'neck_twitch_fast'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'func': (delta, timeOfAnim) =>
				@SetBoneAngles(1, Angle(0, math.cos(RealTime() * 8) * 20, 0))
		}

		{
			'name': 'neck_left'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'reset': => @SetBoneAngles(1, Angle(14, 0, 12))
		}

		{
			'name': 'neck_right'
			'autostart': false
			'repeat': false
			'time': 5
			'bones': {'LrigNeck3'}
			'reset': => @SetBoneAngles(1, Angle(-14, 0, -12))
		}

		{
			'name': 'forward_left'
			'autostart': false
			'repeat': false
			'time': 3
			'bones': {'LrigNeck3'}
			'reset': =>
				@SetBoneAngles(1, Angle(10, 12, -9))
		}

		{
			'name': 'forward_right'
			'autostart': false
			'repeat': false
			'time': 3
			'bones': {'LrigNeck3'}
			'reset': =>
				@SetBoneAngles(1, Angle(-10, 12, 9))
		}
	}

	@SequenceObject = BonesSequence

	hook.Add 'PreDrawOpaqueRenderables', 'PPM2.EntityBonesModifier', (a, b) ->
		return if a or b
		frame = FrameNumber()
		rtime = RealTime()
		for obj in *@OBJECTS
			if not obj\IsValid()
				@OBJECTS = [obj for obj in *@OBJECTS when obj\IsValid()]
				return
			if obj.callFrame ~= frame and (not obj.pac3Last or obj.pac3Last < rtime) and not obj.ent\IsDormant() and not obj.ent\GetNoDraw()
				resetBones(obj.ent)
				data = obj.ent\GetPonyData()
				hook.Call('PPM2.SetupBones', nil, StrongEntity(obj.ent), data) if data
				obj\Think()

	new: (ent = NULL) =>
		super()
		@ent = ent
		@bonesMappingID = {}
		@bonesMappingName = {}
		@bonesMapping = {}
		@bonesMappingForName = {}
		@bonesMappingForID = {}
		@bonesIterable = {}
		@boneCount = 0
		@isValid = false
		table.insert(@@OBJECTS, @)
		@lastCall = RealTime()
		@Setup() if IsValid(ent)

	Setup: (ent = @ent) =>
		return false if not IsValid(ent)
		@lastModel = ent\GetModel()
		@ClearModifiers()
		@isValid = true
		@ent = ent
		@bonesMappingID = {}
		@bonesMappingName = {}
		@boneCount = ent\GetBoneCount()
		@bonesIterable = for i = 0, @boneCount - 1
			name = ent\GetBoneName(i)
			@bonesMappingID[i] = name
			@bonesMapping[i] = name
			@bonesMappingName[name] = i
			@bonesMapping[name] = i
			@bonesMappingForName[name] = name
			@bonesMappingForName[i] = name
			@bonesMappingForID[name] = i
			@bonesMappingForID[i] = i
			@RegisterModifier(name .. 'Position', (-> Vector(0, 0, 0)), (-> Vector(0, 0, 0)))
			@RegisterModifier(name .. 'Scale', (-> Vector(0, 0, 0)), (-> Vector(0, 0, 0)))
			@RegisterModifier(name .. 'Angles', (-> Angle(0, 0, 0)), (-> Angle(0, 0, 0)))
			@SetupLerpTables(name .. 'Position')
			@SetupLerpTables(name .. 'Scale')
			@SetupLerpTables(name .. 'Angles')
			@SetLerpFunc(name .. 'Position', LerpVector)
			@SetLerpFunc(name .. 'Scale', LerpVector)
			@SetLerpFunc(name .. 'Angles', LerpAngle)
			--@RegisterModifier(name .. 'Jiggle', 0)
			{i, name, 'Calculate' .. name .. 'Position', 'Calculate' .. name .. 'Scale', 'Calculate' .. name .. 'Angles'}

	Think: (force = false) =>
		return if not super() or not force and @callFrame == FrameNumber()
		@callFrame = FrameNumber()
		for data in *@bonesIterable
			id = data[1]
			name = data[2]
			calc = data[3]
			calcScale = data[4]
			calcAngles = data[5]
			with @ent
				\ManipulateBonePosition(id, \GetManipulateBonePosition(id) + @[calc](@))
				\ManipulateBoneScale(id, \GetManipulateBoneScale(id) + @[calcScale](@))
				\ManipulateBoneAngles(id, \GetManipulateBoneAngles(id) + @[calcAngles](@))

	ResetBones: =>
		return if @pac3Last and @pac3Last > RealTime()
		resetBones(@ent)

	IsValid: => @isValid and @ent\IsValid()
	GetEntity: => @ent

with FindMetaTable('Entity')
	.PPMBonesModifier = =>
		return @__ppmBonesModifiers if IsValid(@__ppmBonesModifiers)
		@__ppmBonesModifiers = PPM2.EntityBonesModifier(@)
		@__ppmBonesModifiers.ent = @
		@__ppmBonesModifiers\Setup(@) if @__ppmBonesModifiers.lastModel ~= @GetModel()
		return @__ppmBonesModifiers

hook.Add 'PAC3ResetBones', 'PPM2.EntityBonesModifier', =>
	data = @GetPonyData()
	hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
	if @__ppmBonesModifiers
		@__ppmBonesModifiers\Think()
		@__ppmBonesModifiers.pac3Last = RealTime() + 0.2

ent.__ppmBonesModifiers = nil for ent in *ents.GetAll()


logger: fix ACTION bug

logger: fix ACTION bug

Former-commit-id: 87e3060be1e5613b1a98e2cec99bd0a4176f657a [formerly c0e744082405aede9f130272916eb2785b92e959]
Former-commit-id: e1c23033b17ebab36fd57de841d430b2f9099522

dbi = require 'DBI'
require'logging.console'
log = logging.console()

conn = false

schemas = {[[
  CREATE TABLE IF NOT EXISTS log (
    time timestamp DEFAULT now(),
    nick text,
    channel text,
    message text,
    type text
  );]],
  [[CREATE INDEX idx_time ON log(time);]],
  [[CREATE INDEX idx_channel ON log(channel);]]
}

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

  --for s in *schemas
  --  a,b = DBI.Do(conn, s)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

dblog = (type, source, destination, arg) =>
  nick = source.nick

  if type == 'PRIVMSG'
    -- action
    if arg\sub(1,1) == '\001' and arg\sub(-1) == '\001'
      arg = arg\sub(9, -2)
      type = 'ACTION'

  unless arg
    arg = ''

  insert = dbh!\prepare('INSERT INTO log(nick,channel,message,type) values(?,?,?,?)')
  stmt, err = insert\execute(nick, destination, arg, type)
  unless stmt
    log\error err

history = (source, destination, nr) ->
  nr = tonumber(nr) or 1
  stmt = dbh!\prepare [[
    SELECT
      *
      FROM log
      WHERE channel=?
      AND (type = 'PRIVMSG' OR type = 'ACTION')
      ORDER BY time
      DESC LIMIT ?
    ]]
  stmt\execute destination, nr
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

lastlog = (source, destination, arg) ->
  nr = tonumber(nr) or 1
  arg = '%'..arg..'%'
  stmt = dbh!\prepare [[
    SELECT
      *
      FROM log
      WHERE channel=?
      AND (type = 'PRIVMSG' OR type = 'ACTION')
      AND message LIKE ?
      ORDER BY time DESC
      LIMIT 20
    ]]
  stmt\execute destination, arg
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

return {
  PRIVMSG: {
    (s, d, a) =>
      dblog @, 'PRIVMSG', s, d, a
    '%plast$': (source, destination) =>
      say history(source,destination,1)
    '%plastlog (.+)$': (source, destination, arg) =>
      out = {}
      for k,v in *lastlog(source, destination, arg)
        table.insert out, string.format('<%s> %s', k.nick, k.message)
      say table.concat(out, ' ')
  }
  NOTICE: {
    (s, d, a) =>
      dblog @, 'NOTICE', s, d, a
  }
  JOIN: {
    (s, d, a) =>
      dblog @, 'JOIN', s, d, a
  }
  PART: {
    (s, d, a) =>
      dblog @, 'PART', s, d, a
  }
  KICK: {
    (s, d, a) =>
      dblog @, 'KICK', s, d, a
  }
  MODE: {
    (s, d, a) =>
      dblog @, 'MODE', s, d, a
  }
  TOPIC: {
    (s, d, a) =>
      dblog @, 'TOPIC', s, d, a
  }
  NICK: {
    (s, d, a) =>
      dblog @, 'NICK', s, d, a
  }
  QUIT: {
    (s, d, a) =>
      dblog @, 'QUIT', s, d, a
  }
}

dbi = require 'DBI'
require'logging.console'
log = logging.console()

conn = false

schemas = {[[
  CREATE TABLE IF NOT EXISTS log (
    time timestamp DEFAULT now(),
    nick text,
    channel text,
    message text,
    type text
  );]],
  [[CREATE INDEX idx_time ON log(time);]],
  [[CREATE INDEX idx_channel ON log(channel);]]
}

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

  --for s in *schemas
  --  a,b = DBI.Do(conn, s)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

dblog = (type, source, destination, arg) =>
  nick = source.nick

  if type == 'PRIVMSG'
    -- action
    if arg\sub(1,1) == '\001' and arg\sub(-1) == '\001'
      arg = arg\sub(9, -2)
    type = 'ACTION'

  unless arg
    arg = ''

  insert = dbh!\prepare('INSERT INTO log(nick,channel,message,type) values(?,?,?,?)')
  stmt, err = insert\execute(nick, destination, arg, type)
  unless stmt
    log\error err

history = (source, destination, nr) ->
  nr = tonumber(nr) or 1
  stmt = dbh!\prepare [[SELECT * FROM log WHERE channel=? AND type = 'PRIVMSG' ORDER BY time DESC LIMIT ?]]
  stmt\execute destination, nr
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

lastlog = (source, destination, arg) ->
  nr = tonumber(nr) or 1
  arg = '%'..arg..'%'
  stmt = dbh!\prepare [[SELECT * FROM log WHERE channel=? AND type = 'PRIVMSG' AND message LIKE ? ORDER BY time DESC LIMIT 20]]
  stmt\execute destination, arg
  out = {}
  for row in stmt\rows(true) -- true for column names
    out[#out+1] = row

  if #out == 1
    return out[1].message
  return out

return {
  PRIVMSG: {
    (s, d, a) =>
      dblog @, 'PRIVMSG', s, d, a
    '%plast$': (source, destination) =>
      say history(source,destination,1)
    '%plastlog (.+)$': (source, destination, arg) =>
      out = {}
      for k,v in *lastlog(source, destination, arg)
        table.insert out, string.format('<%s> %s', k.nick, k.message)
      say table.concat(out, ' ')
  }
  NOTICE: {
    (s, d, a) =>
      dblog @, 'NOTICE', s, d, a
  }
  JOIN: {
    (s, d, a) =>
      dblog @, 'JOIN', s, d, a
  }
  PART: {
    (s, d, a) =>
      dblog @, 'PART', s, d, a
  }
  KICK: {
    (s, d, a) =>
      dblog @, 'KICK', s, d, a
  }
  MODE: {
    (s, d, a) =>
      dblog @, 'MODE', s, d, a
  }
  TOPIC: {
    (s, d, a) =>
      dblog @, 'TOPIC', s, d, a
  }
  NICK: {
    (s, d, a) =>
      dblog @, 'NICK', s, d, a
  }
  QUIT: {
    (s, d, a) =>
      dblog @, 'QUIT', s, d, a
  }
}