rss: cleanup for cqueues usage

rss: cleanup for cqueues usage

Former-commit-id: d2cb3add9978e61e9bff6b1bae19500e67c54e8c
Former-commit-id: 136399647845156ec5a5dce9221c3c72ea3e597b

-- Module for subscribing to RSS and announce to channels
feedparser = require 'feedparser' -- http://feedparser.luaforge.net/
{:simplehttp, :json, :urlEncode, :bold} = require'util'
html2unicode = require 'html'
sql = require'lsqlite3'

moduleName = 'rss'

-- share a db reference
dbref = false

get_db = ->
  unless dbref
    dbref = sql.open 'cache/rss.sql'
    code = dbref\exec [[
      CREATE TABLE IF NOT EXISTS feed (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        rssurl VARCHAR(1024) NOT NULL,
        url VARCHAR(1024),
        title VARCHAR(1024),
        author VARCHAR(1024),
        etag TEXT,
        lastmodified TEXT
      );

      CREATE TABLE IF NOT EXISTS item (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        feed_id INTEGER NOT NULL,
        guid VARCHAR(64),
        title VARCHAR(1024),
        author VARCHAR(1024),
        url VARCHAR(1024),
        feedurl VARCHAR(1024),
        pubDate TEXT,
        summary TEXT,
        content TEXT,
        FOREIGN KEY(feed_id) REFERENCES feed(id),
        UNIQUE (guid, feed_id)
      );

      CREATE TABLE IF NOT EXISTS subscription (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        destination varchar(100),
        feed_id INTEGER,
        last INTEGER DEFAULT 0,
        created_at timestamp DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY(feed_id) REFERENCES feed(id)
      );
      ]]
    if code != sql.OK
      ivar2\Log 'error', "Error during opendb: #{dbref\errmsg!}"
      return nil

  unless dbref\isopen!
    dbref = sql.open 'cache/rss.sql'

  return dbref

prepare = (db, stmt, ...) ->
  code = db\prepare stmt
  unless code
    ivar2\Log 'error', "Error with statement prepare: #{db\errmsg!}"
    return nil
  code\bind_values ...
  return code

-- Function that add functionality to extract specific information for specific sites
feedSpecific = (feedurl, link, title, summary, content) ->
  if feedurl\match 'https://comics.io/my/'

    title = summary\match '
(.-)
' or ''
    img, alt = summary\match '
'
    return "#{html2unicode title} #{img} #{html2unicode alt}"

  return "#{title} - #{link}"

announce = (db, id) ->
  stmt = prepare db, "SELECT DISTINCT feed.id, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id WHERE feed.id = ?", id
  for row in stmt\rows!
    destination = row[2]

    highest = 0
    -- Check last value
    stmt = prepare db, "SELECT last FROM subscription WHERE feed_id = ? and destination = ?", id, destination
    stmt\step!
    value = stmt\get_values()[1]
    if value and value ~= '' and tonumber(value) and tonumber(value) > 0
      highest = value

    ivar2\Log 'debug', 'Got highest %s, %s, %s', id, destination, highest

    stmt = prepare db, "SELECT feed.id AS feed_id, item.id AS item_id, rssurl, pubDate, name, etag, item.url AS link, item.title, content, summary FROM feed JOIN item on item.feed_id = feed.id WHERE feed.id = ? AND item.id > ? ORDER BY item.id DESC LIMIT 100", id, highest
    out = {}
    for item in stmt\nrows!
      with item
        if .item_id > highest
          highest = .item_id
        table.insert out, "[#{bold .name}]: #{feedSpecific(.rssurl, .link, .title, .summary, .content)}"
    if #out > 0
      ivar2\Msg 'privmsg', destination, ivar2.nick, "RSS "..table.concat(out, ', ')
    -- Update last read
    ins = prepare db, 'UPDATE subscription SET last=? WHERE feed_id = ? and destination = ?', highest, id, destination
    code, err = ins\step!
    code, err = ins\finalize!

poll = ->
  db = get_db!
  channels = {}
  for c,_ in pairs(ivar2.channels)
    channels[c] = true
  for row in db\rows "SELECT DISTINCT feed.id, rssurl, lastmodified, name, etag, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id"
    -- Ignore feeds this particular instance of the bot does not subscribe to
    if not channels[row[6]]
      continue
    id = row[1]
    rssurl = row[2]
    lm = row[3]
    name = row[4]
    etag = row[5]
    headers = {}
    if lm and lm != '' then
      headers['If-Modified-Since'] = lm
    if etag and etag != '' then
      headers['If-None-Match'] = etag

    data, url, response = simplehttp {url:rssurl,headers:headers}
      -- Reopen DB because of async
      --sdb = get_db!
    sdb = db

    lastModified = response.headers['Last-Modified']
    if not lastModified
      lastModified = response.headers['Date']

    -- Unmodified content
    if response.status_code == 304 or not data
      -- return from the simplehttp callback
      return

    ok, feed, err = pcall -> feedparser.parse data
    if not ok
      ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{feed}> data for feed: <#{name}> with URL <#{url}>"
      return
    if err
      ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{err}> data for feed: <#{name}> with URL <#{url}>"
      return
    else
      title = feed.title
      author = feed.author
      url = feed.link

      -- Update feed with new values
      ins = sdb\prepare 'UPDATE feed SET title=?, author=?, url=?, lastmodified=? WHERE id=?'
      code, err = ins\bind_values title, author, url, lastModified, id
      code, err = ins\step!
      code, err = ins\finalize!

      out = {}
      for i, e in ipairs(feed.entries)
        -- Attempt to get a unique entry ID
        guid = e.guid
        if not guid then guid = e.id
        if not guid then guid = e.link
        if not guid
          ivar2\Log 'error', "#{moduleName}: No GUID when parsing entry: <#{e}> data for feed: <#{name}> with URL <#{url}>"
          break

        ins = sdb\prepare [[
          INSERT OR ABORT INTO
            item(feed_id, guid, title, author, url, pubDate, content, summary)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ]]
        if not ins then
          ivar2\Log 'error', "#{moduleName}: Error during inserting entry: <#{err}> feed: <#{name}> with URL <#{url}> and position: #{i}"
        else
          ivar2\Log 'debug', "#{moduleName}: Inserting entry: #{e.link}, #{e.title}"
          code = ins\bind_values id, guid, e.title, e.author, e.link, e.updated, e.content, e.summary
          code = ins\step!
          code = ins\finalize!
          if code == sql.CONSTRAINT -- duplicate value
            ivar2\Log 'debug', "Reached duplicate link, breaking"
            break
    --sdb\close!
    announce(db, id)
  db\close!

subscribe = (source, destination, name, url) =>
  db = get_db!
  ins = db\prepare "INSERT INTO feed (name, rssurl) VALUES(?, ?)"
  code = ins\bind_values name, url
  code = ins\step!
  code = ins\finalize!
  ins = db\prepare "INSERT INTO subscription (feed_id, destination) VALUES(?, ?)"
  code = ins\bind_values db\last_insert_rowid!, destination
  code = ins\step!
  code = ins\finalize!
  db\close!
  reply "Ok. Subscribed to #{bold name}"
  poll!

unsubscribe = (source, destination, name) =>
  db = get_db!
  ins = db\prepare "DELETE FROM subscription WHERE feed_id IN (select id from feed where name = ?) and destination = ?"
  code = ins\bind_values name, destination
  code = ins\step!
  code = ins\finalize!
  total_changes = db\total_changes!
  db\close!
  if total_changes < 1
    reply "Wasn't subscribed. But, sure."
  else
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  out = {}
  db = get_db!
  stmt = db\prepare("SELECT name FROM feed JOIN subscription ON feed.id = subscription.feed_id WHERE destination = ?")
  stmt\bind_values destination
  for row in stmt\rows!
    out[#out+1] = row[1]
  if #out > 0 then
    reply "Subscribed to: #{table.concat(out, ', ')}"
  else
    reply "Not subscribed to any feeds."

  db\close!

-- Start the subscribe poller
interval = 60*60
ivar2\Timer(moduleName, interval, interval, poll)

PRIVMSG:
  '^%prss latest (.*)': getLatest
  '^%prss subscribe (.-) (.*)': subscribe
  '^%prss unsubscribe (.*)': unsubscribe
  '^%prss list': list
  -- for debugging
  '^%prss poll': poll

-- Module for subscribing to RSS and announce to channels
feedparser = require 'feedparser' -- http://feedparser.luaforge.net/
{:simplehttp, :json, :urlEncode, :bold} = require'util'
html2unicode = require 'html'
sql = require'lsqlite3'

moduleName = 'rss'

get_db = ->
  db = sql.open 'cache/rss.sql'

  code = db\exec [[
    CREATE TABLE IF NOT EXISTS feed (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT,
      rssurl VARCHAR(1024) NOT NULL,
      url VARCHAR(1024),
      title VARCHAR(1024),
      author VARCHAR(1024),
      etag TEXT,
      lastmodified TEXT
    );

    CREATE TABLE IF NOT EXISTS item (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      feed_id INTEGER NOT NULL,
      guid VARCHAR(64),
      title VARCHAR(1024),
      author VARCHAR(1024),
      url VARCHAR(1024),
      feedurl VARCHAR(1024),
      pubDate TEXT,
      summary TEXT,
      content TEXT,
      FOREIGN KEY(feed_id) REFERENCES feed(id),
      UNIQUE (guid, feed_id)
    );

    CREATE TABLE IF NOT EXISTS subscription (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      destination varchar(100),
      feed_id INTEGER,
      last INTEGER DEFAULT 0,
      created_at timestamp DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY(feed_id) REFERENCES feed(id)
    );
    ]]

  if code != sql.OK
    ivar2\Log 'error', "Error during opendb: #{db\errmsg!}"
    return nil

  return db

prepare = (db, stmt, ...) ->
  code = db\prepare stmt
  unless code
    ivar2\Log 'error', "Error with statement prepare: #{db\errmsg!}"
    return nil
  code\bind_values ...
  return code

-- Function that add functionality to extract specific information for specific sites
feedSpecific = (feedurl, link, title, summary, content) ->
  if feedurl\match 'https://comics.io/my/'

    title = summary\match '
(.-)
' or ''
    img, alt = summary\match '
'
    return "#{html2unicode title} #{img} #{html2unicode alt}"

  return "#{title} - #{link}"

announce = (id) ->
  db = get_db!
  stmt = prepare db, "SELECT DISTINCT feed.id, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id WHERE feed.id = ?", id
  for row in stmt\rows!
    destination = row[2]

    highest = 0
    -- Check last value
    stmt = prepare db, "SELECT last FROM subscription WHERE feed_id = ? and destination = ?", id, destination
    stmt\step!
    value = stmt\get_values()[1]
    if value and value ~= '' and tonumber(value) and tonumber(value) > 0
      highest = value

    ivar2\Log 'debug', 'Got highest %s, %s, %s', id, destination, highest

    stmt = prepare db, "SELECT feed.id AS feed_id, item.id AS item_id, rssurl, pubDate, name, etag, item.url AS link, item.title, content, summary FROM feed JOIN item on item.feed_id = feed.id WHERE feed.id = ? AND item.id > ? ORDER BY item.id DESC LIMIT 100", id, highest
    out = {}
    for item in stmt\nrows!
      with item
        if .item_id > highest
          highest = .item_id
        table.insert out, "[#{bold .name}]: #{feedSpecific(.rssurl, .link, .title, .summary, .content)}"
    if #out > 0
      ivar2\Msg 'privmsg', destination, ivar2.nick, "RSS "..table.concat(out, ', ')
    -- Update last red
    ins = prepare db, 'UPDATE subscription SET last=? WHERE feed_id = ? and destination = ?', highest, id, destination
    code, err = ins\step!
    code, err = ins\finalize!
  db\close!

poll = ->
  db = get_db!
  channels = {}
  for c,_ in pairs(ivar2.channels)
    channels[c] = true
  for row in db\rows "SELECT DISTINCT feed.id, rssurl, lastmodified, name, etag, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id"
    -- Ignore feeds this particular instance of the bot does not subscribe to
    if not channels[row[6]]
      continue
    id = row[1]
    rssurl = row[2]
    lm = row[3]
    name = row[4]
    etag = row[5]
    headers = {}
    if lm and lm != '' then
      headers['If-Modified-Since'] = lm
    if etag and etag != '' then
      headers['If-None-Match'] = etag

    simplehttp {url:rssurl,headers:headers}, (data, url, response) ->
      -- Reopen DB because of async
      sdb = get_db!

      lastModified = response.headers['Last-Modified']
      if not lastModified
        lastModified = response.headers['Date']

      -- Unmodified content
      if response.status_code == 304 or not data
        -- return from the simplehttp callback
        return

      ok, feed, err = pcall -> feedparser.parse data
      if not ok
        ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{feed}> data for feed: <#{name}> with URL <#{url}>"
        return
      if err
        ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{err}> data for feed: <#{name}> with URL <#{url}>"
        return
      else
        title = feed.title
        author = feed.author
        url = feed.link

        -- Update feed with new values
        ins = sdb\prepare 'UPDATE feed SET title=?, author=?, url=?, lastmodified=? WHERE id=?'
        code, err = ins\bind_values title, author, url, lastModified, id
        code, err = ins\step!
        code, err = ins\finalize!

        out = {}
        for i, e in ipairs(feed.entries)
          -- Attempt to get a unique entry ID
          guid = e.guid
          if not guid then guid = e.id
          if not guid then guid = e.link
          if not guid
            ivar2\Log 'error', "#{moduleName}: No GUID when parsing entry: <#{e}> data for feed: <#{name}> with URL <#{url}>"
            break

          ins = sdb\prepare [[
            INSERT OR ABORT INTO
              item(feed_id, guid, title, author, url, pubDate, content, summary)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?)
          ]]
          if not ins then
            ivar2\Log 'error', "#{moduleName}: Error during inserting entry: <#{err}> feed: <#{name}> with URL <#{url}> and position: #{i}"
          else
            ivar2\Log 'debug', "#{moduleName}: Inserting entry: #{e.link}, #{e.title}"
            code = ins\bind_values id, guid, e.title, e.author, e.link, e.updated, e.content, e.summary
            code = ins\step!
            code = ins\finalize!
            if code == sql.CONSTRAINT -- duplicate value
              ivar2\Log 'debug', "Reached duplicate link, breaking"
              break
      sdb\close!
      announce(id)
  db\close!

subscribe = (source, destination, name, url) =>
  db = get_db!
  ins = db\prepare "INSERT INTO feed (name, rssurl) VALUES(?, ?)"
  code = ins\bind_values name, url
  code = ins\step!
  code = ins\finalize!
  ins = db\prepare "INSERT INTO subscription (feed_id, destination) VALUES(?, ?)"
  code = ins\bind_values db\last_insert_rowid!, destination
  code = ins\step!
  code = ins\finalize!
  db\close!
  reply "Ok. Subscribed to #{bold name}"
  poll!

unsubscribe = (source, destination, name) =>
  db = get_db!
  ins = db\prepare "DELETE FROM subscription WHERE feed_id IN (select id from feed where name = ?) and destination = ?"
  code = ins\bind_values name, destination
  code = ins\step!
  code = ins\finalize!
  total_changes = db\total_changes!
  db\close!
  if total_changes < 1
    reply "Wasn't subscribed. But, sure."
  else
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  out = {}
  db = get_db!
  stmt = db\prepare("SELECT name FROM feed JOIN subscription ON feed.id = subscription.feed_id WHERE destination = ?")
  stmt\bind_values destination
  for row in stmt\rows!
    out[#out+1] = row[1]
  if #out > 0 then
    reply "Subscribed to: #{table.concat(out, ', ')}"
  else
    reply "Not subscribed to any feeds."

  db\close!

-- Start the subscribe poller
interval = 60*60
ivar2\Timer(moduleName, interval, interval, poll)

PRIVMSG:
  '^%prss latest (.*)': getLatest
  '^%prss subscribe (.-) (.*)': subscribe
  '^%prss unsubscribe (.*)': unsubscribe
  '^%prss list': list
  -- for debugging
  '^%prss poll': poll


Maybe always attempt to update textures and update them earlier

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

import FrameNumber, RealTime, StrongEntity, PPM2 from _G
import ALTERNATIVE_RENDER from PPM2
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')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
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')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, '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')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
	return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
	return unless IsValid(arms)
	return unless ply.__cachedIsPony
	return unless ply\Alive()
	arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()
	if IsValid(wep) and wep.UseHands == false
		return true -- Dafuck?
	return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
	data = ply\GetPonyData()
	return unless data
	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

IN_DRAW = false
MARKED_FOR_DRAW = {}

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

	MARKED_FOR_DRAW = {}

	if not ALTERNATIVE_RENDER\GetBool()
		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 ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		for draw in *MARKED_FOR_DRAW
			draw\PostDraw()

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 TASK_RENDER_TYPE\GetBool()
		for task in *PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent)
				if ent.__cachedIsPony
					ent\SetNoDraw(true)
					ent.__ppm2_task_hit = true
					renderController = task\GetRenderController()
					renderController\PreDraw(ent)
					IN_DRAW = true
					ent\DrawModel()
					IN_DRAW = false
					renderController\PostDraw(ent)
				else
					if ent.__ppm2_task_hit
						ent.__ppm2_task_hit = false
						ent\SetNoDraw(false)
						task\Reset()

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for ply in *player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTime() + 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)

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

Think = ->
	if TASK_RENDER_TYPE\GetBool()
		for task in *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\GetEntity()
					ent.__ppm2_oldRenderOverride = ent.RenderOverride
					ent.__ppm2RenderOverride = ->
						renderController = task\GetRenderController()
						renderController\PreDraw(ent, true)
						ent\DrawModel()
						renderController\PostDraw(ent, true)
						ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
					ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if not ALTERNATIVE_RENDER\GetBool() or PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data
		@__cachedIsPony = IsPony(@)
		return if not @__cachedIsPony
		f = FrameNumber()
		return if @__ppm2_last_draw == f
		@__ppm2_last_draw = f
		return if IsDormant(@)
		@__ppm2_last_dead = @__ppm2_last_dead or 0
		return if @__ppm2_last_dead > RealTime()
		bones = PPMBonesModifier(@)
		if data and bones.callFrame ~= FrameNumber() and (not bones.pac3Last or bones.pac3Last < RealTime())
			bones\ResetBones()
			hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
			bones\Think()
		renderController = data\GetRenderController()
		status = renderController\PreDraw() if renderController

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

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

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumber()
		cTime = (RealTime() % 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 = DLib.AddColor(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 = FrameNumber()
		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 < RealTime()
						status.nextSmokeParticle = RealTime() + 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 < RealTime() and status.mins and status.maxs
						status.nextGrabParticle = RealTime() + 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 HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumber()
				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\GetHornMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))

				if not data\GetSeparateMagicColor()
					if not data\GetSeparateEyes()
						.color = DLib.LerpColor(0.5, data\GetEyeIrisTop(), data\GetEyeIrisBottom())
					else
						lerpLeft = DLib.LerpColor(0.5, data\GetEyeIrisTopLeft(), data\GetEyeIrisBottomLeft())
						lerpRight = DLib.LerpColor(0.5, data\GetEyeIrisTopRight(), data\GetEyeIrisBottomRight())
						.color = DLib.LerpColor(0.5, lerpLeft, lerpRight)
		else
			with hornGlowStatus[@]
				.frame = FrameNumber()
				.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)

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

import FrameNumber, RealTime, StrongEntity, PPM2 from _G
import ALTERNATIVE_RENDER from PPM2
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')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
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')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, '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')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
	return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
	return unless IsValid(arms)
	return unless ply.__cachedIsPony
	return unless ply\Alive()
	arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()
	if IsValid(wep) and wep.UseHands == false
		return true -- Dafuck?
	return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
	data = ply\GetPonyData()
	return unless data
	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

IN_DRAW = false
MARKED_FOR_DRAW = {}

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

	MARKED_FOR_DRAW = {}

	if not ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		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 ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		for draw in *MARKED_FOR_DRAW
			draw\PostDraw()

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 TASK_RENDER_TYPE\GetBool()
		for task in *PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent)
				if ent.__cachedIsPony
					ent\SetNoDraw(true)
					ent.__ppm2_task_hit = true
					renderController = task\GetRenderController()
					renderController\PreDraw(ent)
					IN_DRAW = true
					ent\DrawModel()
					IN_DRAW = false
					renderController\PostDraw(ent)
				else
					if ent.__ppm2_task_hit
						ent.__ppm2_task_hit = false
						ent\SetNoDraw(false)
						task\Reset()

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for ply in *player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTime() + 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)

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

Think = ->
	if TASK_RENDER_TYPE\GetBool()
		for task in *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\GetEntity()
					ent.__ppm2_oldRenderOverride = ent.RenderOverride
					ent.__ppm2RenderOverride = ->
						renderController = task\GetRenderController()
						renderController\PreDraw(ent, true)
						ent\DrawModel()
						renderController\PostDraw(ent, true)
						ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
					ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if not ALTERNATIVE_RENDER\GetBool() or PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data
		@__cachedIsPony = IsPony(@)
		return if not @__cachedIsPony
		f = FrameNumber()
		return if @__ppm2_last_draw == f
		@__ppm2_last_draw = f
		return if IsDormant(@)
		@__ppm2_last_dead = @__ppm2_last_dead or 0
		return if @__ppm2_last_dead > RealTime()
		bones = PPMBonesModifier(@)
		if data and bones.callFrame ~= FrameNumber() and (not bones.pac3Last or bones.pac3Last < RealTime())
			bones\ResetBones()
			hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
			bones\Think()
		renderController = data\GetRenderController()
		status = renderController\PreDraw() if renderController

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

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

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumber()
		cTime = (RealTime() % 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 = DLib.AddColor(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 = FrameNumber()
		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 < RealTime()
						status.nextSmokeParticle = RealTime() + 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 < RealTime() and status.mins and status.maxs
						status.nextGrabParticle = RealTime() + 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 HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumber()
				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\GetHornMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))

				if not data\GetSeparateMagicColor()
					if not data\GetSeparateEyes()
						.color = DLib.LerpColor(0.5, data\GetEyeIrisTop(), data\GetEyeIrisBottom())
					else
						lerpLeft = DLib.LerpColor(0.5, data\GetEyeIrisTopLeft(), data\GetEyeIrisBottomLeft())
						lerpRight = DLib.LerpColor(0.5, data\GetEyeIrisTopRight(), data\GetEyeIrisBottomRight())
						.color = DLib.LerpColor(0.5, lerpLeft, lerpRight)
		else
			with hornGlowStatus[@]
				.frame = FrameNumber()
				.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)

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


whoopsie

whoopsie

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

import FrameNumber, RealTime, StrongEntity, PPM2 from _G
import ALTERNATIVE_RENDER from PPM2
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')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
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')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, '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')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
	return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
	return unless IsValid(arms)
	return unless ply.__cachedIsPony
	return unless ply\Alive()
	arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()
	if IsValid(wep) and wep.UseHands == false
		return true -- Dafuck?
	return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
	data = ply\GetPonyData()
	return unless data
	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

IN_DRAW = false
MARKED_FOR_DRAW = {}

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

	MARKED_FOR_DRAW = {}

	if not ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		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 ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		for draw in *MARKED_FOR_DRAW
			draw\PostDraw()

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 TASK_RENDER_TYPE\GetBool()
		for task in *PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent)
				if ent.__cachedIsPony
					ent\SetNoDraw(true)
					ent.__ppm2_task_hit = true
					renderController = task\GetRenderController()
					renderController\PreDraw(ent)
					IN_DRAW = true
					ent\DrawModel()
					IN_DRAW = false
					renderController\PostDraw(ent)
				else
					if ent.__ppm2_task_hit
						ent.__ppm2_task_hit = false
						ent\SetNoDraw(false)
						task\Reset()

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for ply in *player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTime() + 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)

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

Think = ->
	if TASK_RENDER_TYPE\GetBool()
		for task in *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\GetEntity()
					ent.__ppm2_oldRenderOverride = ent.RenderOverride
					ent.__ppm2RenderOverride = ->
						renderController = task\GetRenderController()
						renderController\PreDraw(ent, true)
						ent\DrawModel()
						renderController\PostDraw(ent, true)
						ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
					ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if not ALTERNATIVE_RENDER\GetBool() or PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data
		@__cachedIsPony = IsPony(@)
		return if not @__cachedIsPony
		f = FrameNumber()
		return if @__ppm2_last_draw == f
		@__ppm2_last_draw = f
		return if IsDormant(@)
		@__ppm2_last_dead = @__ppm2_last_dead or 0
		return if @__ppm2_last_dead > RealTime()
		bones = PPMBonesModifier(@)
		if data and bones.callFrame ~= FrameNumber() and (not bones.pac3Last or bones.pac3Last < RealTime())
			bones\ResetBones()
			hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
			bones\Think()
		renderController = data\GetRenderController()
		status = renderController\PreDraw() if renderController

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

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

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumber()
		cTime = (RealTime() % 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 = DLib.AddColor(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 = FrameNumber()
		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 < RealTime()
						status.nextSmokeParticle = RealTime() + 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 < RealTime() and status.mins and status.maxs
						status.nextGrabParticle = RealTime() + 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 HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumber()
				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\GetHornMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))

				if not data\GetSeparateMagicColor()
					if not data\GetSeparateEyes()
						.color = DLib.LerpColor(0.5, data\GetEyeIrisTop(), data\GetEyeIrisBottom())
					else
						lerpLeft = DLib.LerpColor(0.5, data\GetEyeIrisTopLeft(), data\GetEyeIrisBottomLeft())
						lerpRight = DLib.LerpColor(0.5, data\GetEyeIrisTopRight(), data\GetEyeIrisBottomRight())
						.color = DLib.LerpColor(0.5, lerpLeft, lerpRight)
		else
			with hornGlowStatus[@]
				.frame = FrameNumber()
				.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)

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

import FrameNumber, RealTime, StrongEntity, PPM2 from _G
import ALTERNATIVE_RENDER from PPM2
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')
TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')
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')
ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, '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')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
	return if PPM2.__RENDERING_REFLECTIONS
	return true unless SV_SHOULD_DRAW_VIEWMODEL\GetBool()
	return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
	return unless IsValid(arms)
	return unless ply.__cachedIsPony
	return unless ply\Alive()
	arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
	wep = ply\GetActiveWeapon()
	if IsValid(wep) and wep.UseHands == false
		return true -- Dafuck?
	return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
	data = ply\GetPonyData()
	return unless data
	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

IN_DRAW = false
MARKED_FOR_DRAW = {}

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

	MARKED_FOR_DRAW = {}

	if not ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		for ply in *player.GetAll()
			if not IsDormant(ply)
				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 ALTERNATIVE_RENDER\GetBool() and not bDrawingDepth and not bDrawingSkybox
		for draw in *MARKED_FOR_DRAW
			draw\PostDraw()

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 TASK_RENDER_TYPE\GetBool()
		for task in *PPM2.NetworkedPonyData.RenderTasks
			ent = task.ent
			if IsValid(ent)
				if ent.__cachedIsPony
					ent\SetNoDraw(true)
					ent.__ppm2_task_hit = true
					renderController = task\GetRenderController()
					renderController\PreDraw(ent)
					IN_DRAW = true
					ent\DrawModel()
					IN_DRAW = false
					renderController\PostDraw(ent)
				else
					if ent.__ppm2_task_hit
						ent.__ppm2_task_hit = false
						ent\SetNoDraw(false)
						task\Reset()

	if not ENABLE_NEW_RAGDOLLS\GetBool()
		for ply in *player.GetAll()
			alive = ply\Alive()
			ply.__ppm2_last_dead = RealTime() + 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)

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

Think = ->
	if TASK_RENDER_TYPE\GetBool()
		for task in *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\GetEntity()
					ent.__ppm2_oldRenderOverride = ent.RenderOverride
					ent.__ppm2RenderOverride = ->
						renderController = task\GetRenderController()
						renderController\PreDraw(ent, true)
						ent\DrawModel()
						renderController\PostDraw(ent, true)
						ent.__ppm2_oldRenderOverride(ent) if ent.__ppm2_oldRenderOverride
					ent.RenderOverride = ent.__ppm2RenderOverride

PPM2.PrePlayerDraw = =>
	return if not ALTERNATIVE_RENDER\GetBool() or PPM2.__RENDERING_REFLECTIONS
	with data = GetPonyData(@)
		return if not data
		@__cachedIsPony = IsPony(@)
		return if not @__cachedIsPony
		f = FrameNumber()
		return if @__ppm2_last_draw == f
		@__ppm2_last_draw = f
		return if IsDormant(@)
		@__ppm2_last_dead = @__ppm2_last_dead or 0
		return if @__ppm2_last_dead > RealTime()
		bones = PPMBonesModifier(@)
		if data and bones.callFrame ~= FrameNumber() and (not bones.pac3Last or bones.pac3Last < RealTime())
			bones\ResetBones()
			hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
			bones\Think()
		renderController = data\GetRenderController()
		status = renderController\PreDraw() if renderController

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

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

	hook.Add 'PreDrawHalos', 'PPM2.HornEffects', =>
		return if not HORN_HIDE_BEAM\GetBool()
		frame = FrameNumber()
		cTime = (RealTime() % 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 = DLib.AddColor(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 = FrameNumber()
		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 < RealTime()
						status.nextSmokeParticle = RealTime() + 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 < RealTime() and status.mins and status.maxs
						status.nextGrabParticle = RealTime() + 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 HORN_FP\GetBool() and @ == LocalPlayer() and not @ShouldDrawLocalPlayer()
		data = @GetPonyData()
		return if not data
		return if data\GetRace() ~= PPM2.RACE_UNICORN and data\GetRace() ~= PPM2.RACE_ALICORN
		if not hornGlowStatus[@]
			hornGlowStatus[@] = {
				frame: FrameNumber()
				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\GetHornMagicColor()
				.haloSeed = math.rad(math.random(-1000, 1000))

				if not data\GetSeparateMagicColor()
					if not data\GetSeparateEyes()
						.color = DLib.LerpColor(0.5, data\GetEyeIrisTop(), data\GetEyeIrisBottom())
					else
						lerpLeft = DLib.LerpColor(0.5, data\GetEyeIrisTopLeft(), data\GetEyeIrisBottomLeft())
						lerpRight = DLib.LerpColor(0.5, data\GetEyeIrisTopRight(), data\GetEyeIrisBottomRight())
						.color = DLib.LerpColor(0.5, lerpLeft, lerpRight)
		else
			with hornGlowStatus[@]
				.frame = FrameNumber()
				.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)

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


Fix recreated cutscenes model again

Fix recreated cutscenes model again

--
-- Copyright (C) 2017-2019 DBot

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

DLib.RegisterAddonName('PPM/2')

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.Unsupported', 600, 4, ->
	if mat_dxlevel\GetInt() >= 90
		timer.Remove 'PPM2.Unsupported'
		return

	Derma_Message('gui.ppm2.dxlevel.not_supported', 'gui.ppm2.dxlevel.toolow')

timer.Create 'PPM2.ModelChecks', 1, 0, ->
	for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
		ent = task.ent
		if IsValid(ent)
			ent.__cachedIsPony = ent\IsPony()

	for _, ply in ipairs player.GetAll()
		if not ply\IsDormant()
			ply.__cachedIsPony = ply\IsPony()
			ponydata = ply\GetPonyData()

			if ply.__cachedIsPony
				if (not ponydata or ponydata\GetHideWeapons()) and not hook.Run('SuppressPonyWeaponsHide', ply) and not ply.RenderOverride
					for _, wep in ipairs ply\GetWeapons()
						if wep
							if not hook.Run('ShouldDrawPonyWeapon', ply, wep) and (not wep.ShouldPonyDraw or not wep\ShouldPonyDraw(ply))
								wep\SetNoDraw(true)
								wep.__ppm2_weapon_hit = true
							elseif wep.__ppm2_weapon_hit
								wep\SetNoDraw(false)
								ply.__ppm2_weapon_hit = false
				else
					for _, wep in ipairs ply\GetWeapons()
						if wep and wep.__ppm2_weapon_hit
							wep\SetNoDraw(false)
							ply.__ppm2_weapon_hit = false

PlayerRespawn = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
	net.Start('PPM2.Require')
	net.SendToServer()
	PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
	return if lastDataSend > RealTimeL()
	lastDataSend = RealTimeL() + 10
	instance = PPM2.GetMainData()
	newData = instance\CreateNetworkObject()
	newData\Create()
	instance\SetNetworkData(newData)
	PPM2.Message 'Sending pony data to server...'

if not IsValid(LocalPlayer())
	times = 0
	hook.Add 'Think', 'PPM2.RequireData', ->
		ply = LocalPlayer()
		return if not IsValid(ply)
		times += 1 if ply\GetVelocity()\Length() > 5

		return if times < 200
		hook.Remove 'Think', 'PPM2.RequireData'
		hook.Add 'KeyPress', 'PPM2.RequireData', ->
			hook.Remove 'KeyPress', 'PPM2.RequireData'
			RunConsoleCommand('ppm2_reload')
			timer.Simple 3, -> RunConsoleCommand('ppm2_require')
else
	timer.Simple 0, ->
		RunConsoleCommand('ppm2_reload')
		timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

net.receive 'PPM2.EditorCamPos', ->
	ply = net.ReadPlayer()
	return if not ply\IsValid()
	pVector, pAngle = net.ReadVector(), net.ReadAngle()

	if not IsValid(ply.__ppm2_cam)
		ply.__ppm2_cam = ClientsideModel('models/tools/camera/camera.mdl', RENDERGROUP_BOTH)
		ply.__ppm2_cam\SetModelScale(0.4)
		ply.__ppm2_cam.RenderOverride = =>
			return if not ply.__ppm2_campos_lerp
			render.DrawLine(ply.__ppm2_campos_lerp, ply\EyePos(), color_white, true)
			@DrawModel()

		hook.Add 'Think', ply.__ppm2_cam, =>
			return @Remove() if not IsValid(ply) or not ply\GetNWBool('PPM2.InEditor')
			findPos, findAng = LocalToWorld(pVector, pAngle, ply\GetPos(), ply\EyeAngles())
			ply.__ppm2_campos_lerp = Lerp(RealFrameTime() * 22, ply.__ppm2_campos_lerp or findPos, findPos)
			@SetPos(ply.__ppm2_campos_lerp)
			@SetAngles(findAng)

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

	editortext = DLib.i18n.localize('tip.ppm2.in_editor')

	for ply in *player.GetAll()
		if ply ~= lply and ply\GetNWBool('PPM2.InEditor')
			pos = ply\EyePos()
			dist = pos\Distance(lpos)

			if dist < 250
				pos.z += 10
				alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
				{:x, :y} = pos\ToScreen()
				draw.DrawText(editortext, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
				draw.DrawText(editortext, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

			if ply.__ppm2_campos
				pos = Vector(ply.__ppm2_campos)
				dist = pos\Distance(lpos)

				if dist < 250
					pos.z += 9
					alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
					{:x, :y} = pos\ToScreen()
					text = DLib.i18n.localize('tip.ppm2.camera', ply\Nick())
					draw.DrawText(text, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
					draw.DrawText(text, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

hook.Add 'PACChooseDeathRagdoll', 'PPM2.DeathRagdoll', (ragdoll) => @GetNWEntity('PPM2.DeathRagdoll') if IsValid(@GetNWEntity('PPM2.DeathRagdoll'))

concommand.Add 'ppm2_cleanup', ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()
	PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()

cvars.AddChangeCallback('mat_picmip', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_generic', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_body', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

-- Jazztronauts cutscenes support
timer.Simple 0, ->
	if dialog and dialog.CreatePlayerProxy
		if info = debug.getinfo(dialog.CreatePlayerProxy)
			if info.short_src and (info.short_src\find('jazztronauts') or info.short_src\find('ppm2'))
				dialog._PPM2_CreatePlayerProxy = dialog._PPM2_CreatePlayerProxy or dialog.CreatePlayerProxy
				dialog.CreatePlayerProxy = (...) ->
					ent = dialog._PPM2_CreatePlayerProxy(...)

					if IsValid(ent)
						if data = LocalPonyData()
							ment = ent.Get and ent\Get() or ent
							newdata = PPM2.NetworkedPonyData(nil, ment) if not ment\GetPonyData()
							newdata = ment\GetPonyData() if ment\GetPonyData()
							ment.__ppm2RenderOverride = nil
							ment.__ppm2_oldRenderOverride = nil
							data\ApplyDataToObject(newdata)
							newdata\SetHideManes(false)
							newdata\SetHideManesSocks(false)

					return ent

return

--
-- Copyright (C) 2017-2019 DBot

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

DLib.RegisterAddonName('PPM/2')

mat_dxlevel = GetConVar('mat_dxlevel')

timer.Create 'PPM2.Unsupported', 600, 4, ->
	if mat_dxlevel\GetInt() >= 90
		timer.Remove 'PPM2.Unsupported'
		return

	Derma_Message('gui.ppm2.dxlevel.not_supported', 'gui.ppm2.dxlevel.toolow')

timer.Create 'PPM2.ModelChecks', 1, 0, ->
	for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
		ent = task.ent
		if IsValid(ent)
			ent.__cachedIsPony = ent\IsPony()

	for _, ply in ipairs player.GetAll()
		if not ply\IsDormant()
			ply.__cachedIsPony = ply\IsPony()
			ponydata = ply\GetPonyData()

			if ply.__cachedIsPony
				if (not ponydata or ponydata\GetHideWeapons()) and not hook.Run('SuppressPonyWeaponsHide', ply) and not ply.RenderOverride
					for _, wep in ipairs ply\GetWeapons()
						if wep
							if not hook.Run('ShouldDrawPonyWeapon', ply, wep) and (not wep.ShouldPonyDraw or not wep\ShouldPonyDraw(ply))
								wep\SetNoDraw(true)
								wep.__ppm2_weapon_hit = true
							elseif wep.__ppm2_weapon_hit
								wep\SetNoDraw(false)
								ply.__ppm2_weapon_hit = false
				else
					for _, wep in ipairs ply\GetWeapons()
						if wep and wep.__ppm2_weapon_hit
							wep\SetNoDraw(false)
							ply.__ppm2_weapon_hit = false

PlayerRespawn = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
	ent = net.ReadEntity()
	return if not IsValid(ent)
	return if not ent\GetPonyData()
	ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
	net.Start('PPM2.Require')
	net.SendToServer()
	PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
	return if lastDataSend > RealTimeL()
	lastDataSend = RealTimeL() + 10
	instance = PPM2.GetMainData()
	newData = instance\CreateNetworkObject()
	newData\Create()
	instance\SetNetworkData(newData)
	PPM2.Message 'Sending pony data to server...'

if not IsValid(LocalPlayer())
	times = 0
	hook.Add 'Think', 'PPM2.RequireData', ->
		ply = LocalPlayer()
		return if not IsValid(ply)
		times += 1 if ply\GetVelocity()\Length() > 5

		return if times < 200
		hook.Remove 'Think', 'PPM2.RequireData'
		hook.Add 'KeyPress', 'PPM2.RequireData', ->
			hook.Remove 'KeyPress', 'PPM2.RequireData'
			RunConsoleCommand('ppm2_reload')
			timer.Simple 3, -> RunConsoleCommand('ppm2_require')
else
	timer.Simple 0, ->
		RunConsoleCommand('ppm2_reload')
		timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

net.receive 'PPM2.EditorCamPos', ->
	ply = net.ReadPlayer()
	return if not ply\IsValid()
	pVector, pAngle = net.ReadVector(), net.ReadAngle()

	if not IsValid(ply.__ppm2_cam)
		ply.__ppm2_cam = ClientsideModel('models/tools/camera/camera.mdl', RENDERGROUP_BOTH)
		ply.__ppm2_cam\SetModelScale(0.4)
		ply.__ppm2_cam.RenderOverride = =>
			return if not ply.__ppm2_campos_lerp
			render.DrawLine(ply.__ppm2_campos_lerp, ply\EyePos(), color_white, true)
			@DrawModel()

		hook.Add 'Think', ply.__ppm2_cam, =>
			return @Remove() if not IsValid(ply) or not ply\GetNWBool('PPM2.InEditor')
			findPos, findAng = LocalToWorld(pVector, pAngle, ply\GetPos(), ply\EyeAngles())
			ply.__ppm2_campos_lerp = Lerp(RealFrameTime() * 22, ply.__ppm2_campos_lerp or findPos, findPos)
			@SetPos(ply.__ppm2_campos_lerp)
			@SetAngles(findAng)

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

	editortext = DLib.i18n.localize('tip.ppm2.in_editor')

	for ply in *player.GetAll()
		if ply ~= lply and ply\GetNWBool('PPM2.InEditor')
			pos = ply\EyePos()
			dist = pos\Distance(lpos)

			if dist < 250
				pos.z += 10
				alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
				{:x, :y} = pos\ToScreen()
				draw.DrawText(editortext, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
				draw.DrawText(editortext, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

			if ply.__ppm2_campos
				pos = Vector(ply.__ppm2_campos)
				dist = pos\Distance(lpos)

				if dist < 250
					pos.z += 9
					alpha = (1 - dist\progression(0, 250))\max(0.1) * 255
					{:x, :y} = pos\ToScreen()
					text = DLib.i18n.localize('tip.ppm2.camera', ply\Nick())
					draw.DrawText(text, 'HudHintTextLarge', x, y, Color(0, 0, 0, alpha), TEXT_ALIGN_CENTER)
					draw.DrawText(text, 'HudHintTextLarge', x + 1, y + 1, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER)

hook.Add 'PACChooseDeathRagdoll', 'PPM2.DeathRagdoll', (ragdoll) => @GetNWEntity('PPM2.DeathRagdoll') if IsValid(@GetNWEntity('PPM2.DeathRagdoll'))

concommand.Add 'ppm2_cleanup', ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()
	PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyPropModel and not IsValid(ent.manePlayer)
			ent\Remove()

cvars.AddChangeCallback('mat_picmip', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_generic', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

cvars.AddChangeCallback('ppm2_cl_hires_body', (->
	timer.Simple 0, (->
		RunConsoleCommand('ppm2_require')
		RunConsoleCommand('ppm2_reload')
	)
), 'ppm2')

-- Jazztronauts cutscenes support
timer.Simple 0, ->
	if dialog and dialog.CreatePlayerProxy
		if info = debug.getinfo(dialog.CreatePlayerProxy)
			if info.short_src and (info.short_src\find('jazztronauts') or info.short_src\find('ppm2'))
				dialog._PPM2_CreatePlayerProxy = dialog._PPM2_CreatePlayerProxy or dialog.CreatePlayerProxy
				dialog.CreatePlayerProxy = (...) ->
					ent = dialog._PPM2_CreatePlayerProxy(...)

					if IsValid(ent)
						if data = LocalPonyData()
							newdata = PPM2.NetworkedPonyData(nil, ent.Get and ent\Get() or ent)
							data\ApplyDataToObject(newdata)
							newdata\SetHideManes(false)
							newdata\SetHideManesSocks(false)

					return ent

return


Set __call directly in class definition

Set __call directly in class definition

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

-- 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', @

    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'

__call: (tbl, ...) ->
    tbl\get ...

getmetatable(database).__call = __call
getmetatable(database_char).__call = __call

T.database = database
T.database_char = database_char