In draw check

In draw check

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

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_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')
SHOULD_DRAW_VIEWMODEL = CreateConVar('cl_ppm2_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    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 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

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    return if IN_DRAW
    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.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    return if IN_DRAW
    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

    for task in *PPM2.NetworkedPonyData.RenderTasks
        ent = task.ent
        if IsValid(ent)
            if ent.__cachedIsPony
                if not TASK_RENDER_TYPE\GetBool()
                    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)
                    renderController = task\GetRenderController()
                    renderController\PreDraw(ent)
                    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

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController

PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

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 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, 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.
--

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_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')
SHOULD_DRAW_VIEWMODEL = CreateConVar('cl_ppm2_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    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 unless IsValid(arms)
    return unless arms.__ppm2_draw
    data = ply\GetPonyData()
    return unless data
    data\GetRenderController()\PostDrawArms(arms)
    arms.__ppm2_draw = false

PPM2.PreDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    data\GetRenderController()\DrawLegsDepth()

    return if bDrawingDepth or bDrawingSkybox

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

PPM2.PostDrawOpaqueRenderables = (bDrawingDepth, bDrawingSkybox) ->
    if bDrawingDepth and DRAW_LEGS_DEPTH\GetBool()
        with LocalPlayer()
            if .__cachedIsPony and \Alive()
                if data = \GetPonyData()
                    data\GetRenderController()\DrawLegsDepth()

    return if bDrawingDepth or bDrawingSkybox

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

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

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController
PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

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 'PreDrawOpaqueRenderables', 'PPM2.PreDrawOpaqueRenderables', PPM2.PreDrawOpaqueRenderables, 2


Stringify array contents.

Stringify array contents.

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

glurg = require "glurg"
Promise = require "glurg.common.Promise"

local Value

class BitmaskSignature
	new: (signature) =>
		Promise.keep("signature", Promise.IsUserdata(signature))

		@_signature = signature
		@id = signature.id

	match: (value, mask) =>
		Promise.keep("value", Promise.IsNumber(value))
		Promise.keep("mask", Promise.IsString(value))

		return @_signature\match(value, mask)

class Bitmask
	new: (bitmask) =>
		Promise.keep("bitmask", Promise.IsUserdata(bitmask))

		@_bitmask = bitmask
		@bitmask = BitmaskSignature(bitmask.signature)
		@value = bitmask.value

class Blob
	new: (blob) =>
		Promise.keep("blob", Promise.IsUserdata(blob))

		@_blob = blob
		@data = blob.data
		@length = blob.length

class EnumerationSignature
	new: (signature) =>
		Promise.keep("signature", Promise.IsUserdata(signature))

		@_signature = signature
		@id = signature.id

	get_value_by_name: (name) =>
		Promise.keep("name", Promise.IsString(name))

		return Value(@_signature\get_value_by_name(name))

	has_value: (value) =>
		Promise.keep("value", Promise.IsClass(value, Value))

		return @_signature\has_value(value._value)

	get_name_by_value: (value) =>
		Promise.keep("value", Promise.IsClass(value, Value))

		return @_signature\get_name_by_value(value._value)

class Enumeration
	new: (enumeration) =>
		Promise.keep("enumeration", Promise.IsUserdata(enumeration))

		@_enumeration = enumeration
		@signature = EnumerationSignature(enumeration.signature)
		@value = Value(enumeration.value)
		if @signature\has_value(@value)
			@value_name = @signature\get_name_by_value(@value)

class Array
	new: (array) =>
		Promise.keep("array", Promise.IsUserdata(array))

		@_array = array
		for i = 1, array.size
			@[i] = Value(array\get_value_at(i - 1))

class Value
	new: (value) =>
		Promise.keep("value", Promise.IsUserdata(value))

		@_value = value
		@type = switch value.type
			when glurg.trace.values.false_boolean then 'boolean'
			when glurg.trace.values.true_boolean then 'boolean'
			when glurg.trace.values.negative_integer then 'integer'
			when glurg.trace.values.positive_integer then 'integer'
			when glurg.trace.values.float then 'float'
			when glurg.trace.values.double then 'float'
			when glurg.trace.values.string then 'string'
			when glurg.trace.values.null_pointer then 'nil'
			when glurg.trace.values.blob then 'blob'
			when glurg.trace.values.enumeration then 'enumeration'
			when glurg.trace.values.bitmask then 'bitmask'
			when glurg.trace.values.array then 'array'
			when glurg.trace.values.structure then 'structure'
			when glurg.trace.values.handle then 'handle'
			else error "unknown value type"

	query: =>
		return switch @type
			when 'boolean' then @_value\to_boolean!
			when 'integer' then @_value\to_integer!
			when 'float' then @_value\to_float!
			when 'string' then @_value\to_string!
			when 'nil' then nil
			when 'blob' then Blob(@_value\to_blob!)
			when 'enumeration' then Enumeration(@_value\to_enumeration!)
			when 'bitmask' then Bitmask(@_value\to_bitmask!)
			when 'array' then Array(@_value\to_array!)
			when 'structure' then error "not yet implemented"
			when 'handle' then @_value\to_handle!
			else assert(false, "value type is invalid")

	to_string: =>
		query = @\query!
		result = switch @type
			when 'nil' then "NULL"
			when 'blob' then "ubyte[#{query.length}]"
			when 'enumeration'
				if not query.value_name then
					tostring(query.value\query!)
				else
					query.value_name
			when 'bitmask' then string.format("0x%08x", query.value)
			when 'array'
				"[%s]"\format(table.concat([i\to_string! for i in *query], ", "))
			when 'structure' then 'struct {}'
			else tostring(query)
		return result

	clone: =>
		return Value(@_value\clone!)

return Value

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

glurg = require "glurg"
Promise = require "glurg.common.Promise"

local Value

class BitmaskSignature
	new: (signature) =>
		Promise.keep("signature", Promise.IsUserdata(signature))

		@_signature = signature
		@id = signature.id

	match: (value, mask) =>
		Promise.keep("value", Promise.IsNumber(value))
		Promise.keep("mask", Promise.IsString(value))

		return @_signature\match(value, mask)

class Bitmask
	new: (bitmask) =>
		Promise.keep("bitmask", Promise.IsUserdata(bitmask))

		@_bitmask = bitmask
		@bitmask = BitmaskSignature(bitmask.signature)
		@value = bitmask.value

class Blob
	new: (blob) =>
		Promise.keep("blob", Promise.IsUserdata(blob))

		@_blob = blob
		@data = blob.data
		@length = blob.length

class EnumerationSignature
	new: (signature) =>
		Promise.keep("signature", Promise.IsUserdata(signature))

		@_signature = signature
		@id = signature.id

	get_value_by_name: (name) =>
		Promise.keep("name", Promise.IsString(name))

		return Value(@_signature\get_value_by_name(name))

	has_value: (value) =>
		Promise.keep("value", Promise.IsClass(value, Value))

		return @_signature\has_value(value._value)

	get_name_by_value: (value) =>
		Promise.keep("value", Promise.IsClass(value, Value))

		return @_signature\get_name_by_value(value._value)

class Enumeration
	new: (enumeration) =>
		Promise.keep("enumeration", Promise.IsUserdata(enumeration))

		@_enumeration = enumeration
		@signature = EnumerationSignature(enumeration.signature)
		@value = Value(enumeration.value)
		if @signature\has_value(@value)
			@value_name = @signature\get_name_by_value(@value)

class Array
	new: (array) =>
		Promise.keep("array", Promise.IsUserdata(array))

		@_array = array
		for i = 1, array.size
			@[i] = Value(array)

class Value
	new: (value) =>
		Promise.keep("value", Promise.IsUserdata(value))

		@_value = value
		@type = switch value.type
			when glurg.trace.values.false_boolean then 'boolean'
			when glurg.trace.values.true_boolean then 'boolean'
			when glurg.trace.values.negative_integer then 'integer'
			when glurg.trace.values.positive_integer then 'integer'
			when glurg.trace.values.float then 'float'
			when glurg.trace.values.double then 'float'
			when glurg.trace.values.string then 'string'
			when glurg.trace.values.null_pointer then 'nil'
			when glurg.trace.values.blob then 'blob'
			when glurg.trace.values.enumeration then 'enumeration'
			when glurg.trace.values.bitmask then 'bitmask'
			when glurg.trace.values.array then 'array'
			when glurg.trace.values.structure then 'structure'
			when glurg.trace.values.handle then 'handle'
			else error "unknown value type"

	query: =>
		return switch @type
			when 'boolean' then @_value\to_boolean!
			when 'integer' then @_value\to_integer!
			when 'float' then @_value\to_float!
			when 'string' then @_value\to_string!
			when 'nil' then nil
			when 'blob' then Blob(@_value\to_blob!)
			when 'enumeration' then Enumeration(@_value\to_enumeration!)
			when 'bitmask' then Bitmask(@_value\to_bitmask!)
			when 'array' then Array(@_value\to_array!)
			when 'structure' then error "not yet implemented"
			when 'handle' then @_value\to_handle!
			else assert(false, "value type is invalid")

	to_string: =>
		query = @\query!
		result = switch @type
			when 'nil' then "NULL"
			when 'blob' then "ubyte[#{query.length}]"
			when 'enumeration'
				if not query.value_name then
					tostring(query.value\query!)
				else
					query.value_name
			when 'bitmask' then string.format("0x%08x", query.value)
			when 'array' then string.format('object[%d]', #query)
			when 'structure' then 'struct {}'
			else tostring(query)
		return result

	clone: =>
		return Value(@_value\clone!)

return Value


fixing turtle.suck

fixing turtle.suck

fill_inv = ->
  for i=1,16
    turtle.select i
    turtle.suck!

dump_inv = ->
  for i=1,16
    turtle.select i
    turtle.dropDown!

running = true

while running
  if turtle.detect!
    dump_inv!
    fill_inv!
    turtle.turnLeft!
    turtle.turnLeft!
  while not turtle.detect!
    turtle.forward()

fill_inv = ->
  for i=1,16
    turtle.select i
    turtle.suck

dump_inv = ->
  for i=1,16
    turtle.select i
    turtle.dropDown!

running = true

while running
  if turtle.detect!
    dump_inv!
    fill_inv!
    turtle.turnLeft!
    turtle.turnLeft!
  while not turtle.detect!
    turtle.forward()


utf8: fix syntax

utf8: fix syntax

Former-commit-id: 46d93078ca96554f75bfc31417f6e6b79b7fc7d0 [formerly 66fa087d5e1266177ba0d1a71a8c05df43378821]
Former-commit-id: 4696e6a3030d899a035540e4a1ba4451391eac12

html2unicode = require'html'

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

    table.concat(t, "")

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0x1D4A3, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0x246F, arg)

html2unicode = require'html'

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

    table.concat(t, "")

PRIVMSG:
  '^%pwide (.+)$': wide = (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, replace(0x1D4A3, arg)
  '^%pcircled (.+)$': (destination, source, arg) =>
    @Msg 'privmsg', destination, source, replace(0x246F, arg)


fix pathfinding

fix pathfinding

while ... and ... INSTEAD OF while ... OR ...

export ^

require "world/tile"
require "world/rooms/rock"
require "world/rooms/corridor"
require "world/rooms/reactor"
require "world/rooms/extractor"
shapes = require "world/rooms/shape"

lume = require "lib/lume/lume"

class Spacebase
    -- Encapsulate the world grid and the Room structure
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        if kDEBUG -- show grid
            for i=1, @kBASE_SIZE + 1
                love.graphics.setColor(255, 255, 255, 150)
                love.graphics.setLineWidth(1)
                i -= 1 -- begins at 0
                love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
                love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        if kDEBUG
            -- display neighbours
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileToWorld(i, j)
                            {x2, y2} = @tileToWorld(neigh.row, neigh.col)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    drawPath: (path) =>
        size = #path
        for i = 1, size - 1
            {row, col} = path[i]
            {row2, col2} = path[i + 1]
            {x, y} = @tileToWorld(row, col)
            {x2, y2} = @tileToWorld(row2, col2)
            love.graphics.setColor(0, 0, 0)
            love.graphics.line(x, y, x2, y2)

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: () =>
        items = {}
        for room in *@rooms
            for item in *room\getItems()
                table.insert(items, item)
        return items

    updateNeighbors: =>
        -- TODO: refactor by iterating over @rooms (more efficient probably)
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]

    pathFinding: (row1, col1, row2, col2, acceptSameRoom=false) =>
        -- Return a path (as a table of {row, col} coordinate) between two tiles
        -- Currently use Breadth-First Search (TODO: improve efficiency?)
        -- @param row1, col1: start tile of path
        -- @param row2, col2: end tile of path
        -- @param acceptSameRoom: if true, may return a path not ending at the end tile, but in the same room
        -- TODO: unit testing
        assert @tileGrid[row1][col1] ~= nil and @tileGrid[row2][col2] ~= nil, "pathFinding: start or end tile is nil"
        -- print "PathFinding", row1, col1, row2, col2
        endRoom = @tileGrid[row2][col2].room
        row, col = row1, col1
        tile_queue = {}
        marked = {} -- the mark is the previous coordinate in the path
        in_marked = (r, c) -> return marked[r] ~= nil and marked[r][c] ~= nil
        mark_tile = (r, c, prevCoor) ->
            if marked[r] == nil then marked[r] = {}
            marked[r][c] = prevCoor
        while row ~= row2 or col ~= col2
            -- print "current", row, col, "tilequeue is ", lume.serialize(tile_queue)
            -- print "tile_queue", lume.serialize(tile_queue)
            assert @tileGrid[row][col] ~= nil, "pathfinding: invalid state (current tile is nil)"
            tile = @tileGrid[row][col]
            -- special condition for acceptSameRoom
            if acceptSameRoom and tile.room == endRoom
                break
            for neigh in *tile.neighbors
                if neigh and not in_marked(neigh.row, neigh.col)
                    -- print "exploring", neigh.row, neigh.col, "from", row, col
                    mark_tile(neigh.row, neigh.col, {tile.row, tile.col})
                    table.insert(tile_queue, {neigh.row, neigh.col})
            if #tile_queue == 0
                return nil -- no path found
            nextTile = table.remove(tile_queue, 1)
            row, col = nextTile[1], nextTile[2]
        if acceptSameRoom
            assert @tileGrid[row][col].room == endRoom, "pathfinding: search returned a wrong tile. Searched: (#{row}, #{col}), got: (#{row2}, #{col2})}\n#{lume.serialize(marked)}"
        else
            assert row == row2 and col == col2, "pathfinding: search returned a wrong tile. Searched: (#{row}, #{col}), got: (#{row2}, #{col2})"
        -- retrace path back using marks
        path = {}
        table.insert(path, {row, col})
        loopCounter = 0
        while row ~= row1 or col ~= col1
            loopCounter += 1
            assert loopCounter <= @kBASE_SIZE * @kBASE_SIZE, "Pathfinding: infinite cycle in marks"
            assert @tileGrid[row][col] ~= nil
            {row, col} = marked[row][col]
            table.insert(path, {row, col})
        table.insert(path, {row1, col1})
        --print "return path", lume.serialize(path)
        return path

export ^

require "world/tile"
require "world/rooms/rock"
require "world/rooms/corridor"
require "world/rooms/reactor"
require "world/rooms/extractor"
shapes = require "world/rooms/shape"

class Spacebase
    -- Encapsulate the world grid and the Room structure
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 3}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        if kDEBUG -- show grid
            for i=1, @kBASE_SIZE + 1
                love.graphics.setColor(255, 255, 255, 150)
                love.graphics.setLineWidth(1)
                i -= 1 -- begins at 0
                love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
                love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        if kDEBUG
            -- display neighbours
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileToWorld(i, j)
                            {x2, y2} = @tileToWorld(neigh.row, neigh.col)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    drawPath: (path) =>
        size = #path
        for i = 1, size - 1
            {row, col} = path[i]
            {row2, col2} = path[i + 1]
            {x, y} = @tileToWorld(row, col)
            {x2, y2} = @tileToWorld(row2, col2)
            love.graphics.setColor(0, 0, 0)
            love.graphics.line(x, y, x2, y2)

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: () =>
        items = {}
        for room in *@rooms
            for item in *room\getItems()
                table.insert(items, item)
        return items

    updateNeighbors: =>
        -- TODO: refactor by iterating over @rooms (more efficient probably)
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]

    pathFinding: (row1, col1, row2, col2, acceptSameRoom=false) =>
        -- Return a path (as a table of {row, col} coordinate) between two tiles
        -- Currently use Breadth-First Search (TODO: improve efficiency?)
        -- @param row1, col1: start tile of path
        -- @param row2, col2: end tile of path
        -- @param acceptSameRoom: if true, may return a path not ending at the end tile, but in the same room
        -- TODO: unit testing
        assert @tileGrid[row1][col1] ~= nil and @tileGrid[row2][col2] ~= nil, "pathFinding: start or end tile is nil"
        endRoom = @tileGrid[row2][col2].room
        row, col = row1, col1
        tile_queue = {}
        marked = {} -- the mark is the previous coordinate in the path
        in_marked = (r, c) -> return marked[r] ~= nil and marked[r][c] ~= nil
        mark_tile = (r, c, prevCoor) ->
            if marked[r] == nil then marked[r] = {}
            marked[r][c] = prevCoor
        while row ~= row2 or col ~= col2
            assert @tileGrid[row][col] ~= nil, "pathfinding: invalid state (current tile is nil)"
            tile = @tileGrid[row][col]
            -- special condition for acceptSameRoom
            if acceptSameRoom and tile.room == endRoom
                break
            for neigh in *tile.neighbors
                if neigh and not in_marked(neigh.row, neigh.col)
                    mark_tile(neigh.row, neigh.col, {tile.row, tile.col})
                    table.insert(tile_queue, neigh)
            if #tile_queue == 0
                return nil -- no path found
            nextTile = table.remove(tile_queue, 1)
            row, col = nextTile.row, nextTile.col
        if acceptSameRoom
            assert @tileGrid[row][col].room == endRoom, "pathfinding: search returned a wrong tile. Searched: (#{row}, #{col}), got: (#{row2}, #{col2})"
        else
            assert row == row2 and col == col2, "pathfinding: search returned a wrong tile. Searched: (#{row}, #{col}), got: (#{row2}, #{col2})"
        -- retrace path back using marks
        path = {}
        table.insert(path, {row, col})
        loopCounter = 0
        while row ~= row1 and col ~= col1
            loopCounter += 1
            assert loopCounter <= @kBASE_SIZE * @kBASE_SIZE, "Pathfinding: infinite cycle in marks"
            assert @tileGrid[row][col] ~= nil
            {row, col} = marked[row][col]
            table.insert(path, {row, col})
        table.insert(path, {row1, col1})
        return path


wow error

wow error

ln = require'linenoise'
import remove, insert, concat from table
import printerr, to_lua, evalprint from require'moor.utils'

prompt =
	p: ">"
	deepen: => @p = "? "
	reset: => @p = ">"

---- tab completion
cndgen = (env) ->  (line) ->
	if i1 = line\find '[.\\%w_]+$' -- if completable
		with res = {}
			front = line\sub(1, i1 - 1)
			partial = line\sub i1
			prefix, last = partial\match '(.-)([^.\\]*)$'
			t, all = env

			if #prefix > 0 -- tbl.ky or not
				P = prefix\sub(1, -2)
				all = last == ''

				for w in P\gmatch '[^.\\]+'
					t = t[w]

					return unless t

			prefix = front .. prefix

			append_candidates = (t) ->
				if type(t) == 'table'
					table.insert(res, prefix .. k) for k in pairs t when all or k\sub(1, #last) == last

			append_candidates t

			if mt = getmetatable t then append_candidates mt.__index

compgen = (env) ->
	candidates = cndgen env

	(c, s) -> if cc = candidates s
		ln.addcompletion c, name for name in *cc

string.match_if_fncls = =>
	@\match"[-=]>$" or
	@\match"class%s*$" or
	@\match"class%s+%w+$" or
	@\match"class%s+extends%s+%w+%s*$" or
	@\match"class%s+%w+%s+extends%s+%w+%s*$"

-- for busted unit test, repl is sepalated with `get_line` and `replgen`
-- and using the former for the test, the latter is only needed by `repl`.

get_line = ->
	with line = ln.linenoise prompt.p .. " "
		if line and line\match '%S' then ln.historyadd line

replgen = (get_line) -> (env = {}, _ENV = _ENV) ->
	block = {}

	ln.setcompletion compgen _ENV

	while true
		line = get_line!

		unless line then break
		elseif #line < 1 then continue

		-- if line\match"^:"
			-- (require'moor.replcmd') line
			-- continue

		is_fncls, lua_code, err =  if line\match_if_fncls!
			true
		else
			false, to_lua line

		if lua_code and not err
			evalprint env, lua_code
		elseif is_fncls or err\match "^Failed to parse"
			insert block, line

			prompt.reset with prompt
				\deepen!
				while line and #line > 0
					line = get_line!
					insert block, " #{line}"

			lua_code, err = to_lua concat block, "\n"

			if lua_code then evalprint env, lua_code

			block = {}

		if err
			printerr err

	env

-- this is main repl
repl = replgen get_line

setmetatable {:replgen, :repl, :printerr}, __call: (env, _ENV) => repl env, _ENV

ln = require'linenoise'
import remove, insert, concat from table
import printerr, to_lua, evalprint from require'moor.utils'

prompt =
	p: ">"
	deepen: => @p = "? "
	reset: => @p = ">"

---- tab completion
cndgen = (env) ->  (line) ->
	if i1 = line\find '[.\\%w_]+$' -- if completable
		with res = {}
			front = line\sub(1, i1 - 1)
			partial = line\sub i1
			prefix, last = partial\match '(.-)([^.\\]*)$'
			t, all = env

			if #prefix > 0 -- tbl.ky or not
				P = prefix\sub(1, -2)
				all = last == ''

				for w in P\gmatch '[^.\\]+'
					t = t[w]

					return unless t

			prefix = front .. prefix

			append_candidates = (t) ->
				if type(t) == 'table'
					table.insert(res, prefix .. k) for k in pairs t when all or k\sub(1, #last) == last

			append_candidates t

			if mt = getmetatable t then append_candidates mt.__index

compgen = (env) ->
	candidates = cndgen env

	(c, s) -> if cc = candidates s
		ln.addcompletion c, name for name in *cc

string.match_if_fncls = =>
	@\match"[-=]>$" or
	@\match"class%s*$" or
	@\match"class%s+%w+$" or
	@\match"class%s+extends%s+%w+%s*$" or
	@\match"class%s+%w+%s+extends%s+%w+%s*$"

-- for busted unit test, repl is sepalated with `get_line` and `replgen`
-- and using the former for the test, the latter is only needed by `repl`.

get_line = ->
	with line = ln.linenoise prompt.p .. " "
		if line and line\match '%S' then ln.historyadd line

replgen = (get_line) -> (env = {}, _ENV = _ENV) ->
	block = {}

	ln.setcompletion compgen _ENV

	while true
		line = get_line!

		unless line then break
		elseif #line < 1 then continue

		-- if line\match"^:"
			-- (require'moor.replcmd') line
			-- continue

		is_fncls, lua_code, err =  if line\match_if_fncls!
			true
		else
			false, to_lua line

		if lua_code and not err
			evalprint env, lua_code
		elseif is_fncls or err\match "^Failed to parse"
			insert block, line

			prompt.reset with prompt
				\deepen!
				while line and #line > 0
					line = get_line!
					insert block, " #{line}"

			lua_code, err = to_lua concat block, "\n"

			if lua_code then evalprint env, lua_code

			block = {}

		if err
			printerr err

	env

-- this is main repl
repl = replgen get_line

setmetatable {:replgen, :repl}, __call: (env, _ENV) => repl env, _ENV


Change magic hands color only when actually changing related variables

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

import PPM2 from _G

vector_one = Vector(1, 1, 1)

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')
SLOW_STARE_UPDATE = CreateConVar('ppm2_render_stare_slow', '0', {FCVAR_ARCHIVE}, 'Lazy stare update to save a bit frames')

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

	CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
	new: (controller) =>
		super(controller)
		@hideModels = false
		@modelCached = controller\GetModel()
		@IGNORE_DRAW = false
		@CompileTextures() if @GetEntity()\IsValid()
		@CreateLegs() if @GetEntity() == LocalPlayer()
		@socksModel = controller\GetSocksModel()
		@socksModel\SetNoDraw(false) if IsValid(@socksModel)
		@newSocksModel = controller\GetNewSocksModel()
		@newSocksModel\SetNoDraw(false) if IsValid(@newSocksModel)
		@lastStareUpdate = 0
		@staringAt = NULL
		@staringAtDirectly = NULL
		@staringAtDirectlyLast = 0
		@staringAtDirectlyTr = false
		@rotatedHeadTarget = false
		@idleEyes = true
		@idleEyesActive = false
		@nextRollEyes = 0
		@rollEyesDelta = CurTimeL()
		if @GetEntity()\IsValid()
			@CreateFlexController()
			@CreateEmotesController()

	GetModel: => @controller\GetModel()

	GetLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		@CreateLegs() if not IsValid()
		return @legsModel

	CreateLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		for _, ent in ipairs ents.GetAll()
			if ent.isPonyLegsModel
				ent\Remove()

		with @legsModel = ClientsideModel(@modelCached)
			.isPonyLegsModel = true
			.lastRedrawFix = 0
			\SetNoDraw(true)
			.__PPM2_PonyData = @GetData()
			--\PPMBonesModifier()

		@GetData()\GetWeightController()\UpdateWeight(@legsModel)

		@lastLegUpdate = CurTimeL()
		@legClipPlanePos = Vector(0, 0, 0)
		@legBGSetup = CurTimeL()
		@legUpdateFrame = 0
		@legClipDot = 0
		@duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
		@legsClipPlane = @@LEG_CLIP_VECTOR
		return @legsModel

	@LEG_SHIFT_CONST = 24
	@LEG_SHIFT_CONST_VEHICLE = 14
	@LEG_Z_CONST = 0
	@LEG_Z_CONST_VEHICLE = 20
	@LEG_ANIM_SPEED_CONST = 1
	@LEG_CLIP_OFFSET_STAND = 28
	@LEG_CLIP_OFFSET_DUCK = 12
	@LEG_CLIP_OFFSET_VEHICLE = 11

	UpdateLegs: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return unless IsValid(@legsModel)
		return if @legUpdateFrame == FrameNumberL()
		@legUpdateFrame = FrameNumberL()
		ctime = CurTimeL()
		ply = @GetEntity()
		seq = ply\GetSequence()
		legsModel = @legsModel

		with @legsModel
			PPM2.EntityBonesModifier.ThinkObject(ply.__ppmBonesModifiers) if ply.__ppmBonesModifiers

			for boneid = 0, ply\GetBoneCount() - 1
				\ManipulateBonePosition(0, ply\GetManipulateBonePosition(0))
				\ManipulateBoneAngles(0, ply\GetManipulateBoneAngles(0))
				\ManipulateBoneScale(0, ply\GetManipulateBoneScale(0))

			if seq ~= @legSeq
				@legSeq = seq
				\ResetSequence(seq)

			if @legBGSetup < ctime
				@legBGSetup = ctime + 1
				for _, group in ipairs ply\GetBodyGroups()
					\SetBodygroup(group.id, ply\GetBodygroup(group.id))

			\FrameAdvance(ctime - @lastLegUpdate)
			\SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
			@lastLegUpdate = ctime
			\SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
			\SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
			\SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
			\SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
			\SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

		if ply\InVehicle()
			local bonePos

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					bonePos = boneData

			veh = ply\GetVehicle()
			vehAng = veh\GetAngles()
			eyepos = EyePos()
			vehAng\RotateAroundAxis(vehAng\Up(), 90)

			clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
			clipAng\RotateAroundAxis(clipAng\Right(), -90)

			@legsClipPlane = clipAng\Forward()
			@legsModel\SetRenderAngles(vehAng)

			drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
			drawPos\Rotate(vehAng)
			@legsModel\SetPos(eyepos - drawPos)
			@legsModel\SetRenderOrigin(eyepos - drawPos)

			if not bonePos
				legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
				legClipPlanePos\Rotate(vehAng)
				@legClipPlanePos = eyepos - legClipPlanePos
			else
				@legClipPlanePos = bonePos

		else
			@legsClipPlane = @@LEG_CLIP_VECTOR
			eangles = EyeAngles()
			yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
			newAng = Angle(0, yaw, 0)
			rad = math.rad(yaw)
			sin, cos = math.sin(rad), math.cos(rad)
			pos = ply\GetPos()
			{:x, :y, :z} = pos
			newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
			if ply\Crouching()
				@duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
			else
				@duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

			@legsModel\SetRenderAngles(newAng)
			@legsModel\SetAngles(newAng)
			@legsModel\SetRenderOrigin(newPos)
			@legsModel\SetPos(newPos)

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					@legClipPlanePos = boneData
				else
					@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
			else
				@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE

		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)

		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		if LEGS_RENDER_TYPE\GetBool() and ENABLE_FLASHLIGHT_PASS\GetBool()
			render.PushFlashlightMode(true)
			@GetTextureController()\PreDrawLegs(@legsModel)
			if sizes = @GetData()\GetSizeController()
				sizes\ModifyNeck(@legsModel)
				sizes\ModifyLegs(@legsModel)
				sizes\ModifyScale(@legsModel)
			@legsModel\DrawModel()
			@GetTextureController()\PostDrawLegs(@legsModel)
			render.PopFlashlightMode()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		if sizes = @GetData()\GetSizeController()
			sizes\ModifyNeck(@legsModel)
			sizes\ModifyLegs(@legsModel)
			sizes\ModifyScale(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

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

	Reset: =>
		@flexes\Reset() if @flexes and @flexes.Reset
		@emotes\Reset() if @emotes and @emotes.Reset
		@GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
		@GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()

	Remove: =>
		@flexes\Remove() if @flexes
		@emotes\Remove() if @emotes
		@GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
		@isValid = false

	PlayerDeath: =>
		return if not @isValid
		if @emotes
			@emotes\Remove()
			@emotes = nil
		@HideModels(true) if PPM2.ENABLE_NEW_RAGDOLLS\GetBool()
		@GetTextureController()\ResetTextures() if @GetTextureController() and @GetEntity()\IsPony()

	PlayerRespawn: =>
		return if not @isValid
		@GetEmotesController()
		@HideModels(false) if @GetEntity()\IsPony()
		@flexes\PlayerRespawn() if @flexes
		@GetTextureController()\ResetTextures() if @GetTextureController()

	DrawModels: =>
		@socksModel\DrawModel() if IsValid(@socksModel)
		@newSocksModel\DrawModel() if IsValid(@newSocksModel)

	ShouldHideModels: => @hideModels or @GetEntity()\GetNoDraw()

	DoHideModels: (status) =>
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)

	HideModels: (status = true) =>
		return if @hideModels == status
		@DoHideModels(status)
		@hideModels = status

	CheckTarget: (epos, pos) =>
		return not util.TraceLine({
			start: epos,
			endpos: pos,
			filter: @GetEntity(),
			mask: MASK_BLOCKLOS
		}).Hit

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime and SLOW_STARE_UPDATE\GetBool()

		if (not @idleEyes or not ENABLE_STARE\GetBool()) and @idleEyesActive
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(vector_origin)
			@idleEyesActive = false
			return

		return if not @idleEyes or not ENABLE_STARE\GetBool()
		@idleEyesActive = true
		@lastStareUpdate = ctime + 0.2
		lpos = @GetEntity()\EyePos()
		lang = @GetEntity()\EyeAnglesFixed()

		@staringAt = NULL if IsValid(@staringAt) and @staringAt\IsPlayer() and not @staringAt\Alive()

		trNew = util.TraceLine({
			start: lpos,
			endpos: lpos + lang\Forward() * 270,
			filter: @GetEntity(),
		})

		if IsValid(trNew.Entity)
			mins, maxs = trNew.Entity\OBBMins(), trNew.Entity\OBBMaxs()
			size = mins\Distance(maxs)

			if size < 140
				@staringAtDirectly = trNew.Entity
				@staringAtDirectlyLast = CurTimeL() + 2.5
				@staringAtDirectlyTr = trNew

		if IsValid(@staringAtDirectly) and (not @staringAtDirectly\IsPlayer() and not @staringAtDirectly\IsNPC())
			if @staringAtDirectlyLast > CurTimeL()
				pos = @staringAtDirectlyTr.HitPos

				if pos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAtDirectly) and @CheckTarget(lpos, pos)
					@GetEntity()\SetEyeTarget(pos)
					_lpos, _lang = WorldToLocal(pos, angle_zero, lpos, lang)
					@prevRollTargetPos = _lpos
					return

				@staringAtDirectly = NULL
				@staringAtDirectlyLast = 0
				@GetEntity()\SetEyeTarget(vector_origin)
			else
				@staringAtDirectly = NULL

		if trNew.Entity\IsValid() and (trNew.Entity\IsPlayer() or trNew.Entity\IsNPC())
			@staringAt = trNew.Entity

		if IsValid(@staringAt)
			epos = @staringAt\EyePos()

			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAt) and @CheckTarget(lpos, epos)
				@GetEntity()\SetEyeTarget(epos)
				@prevRollTargetPos = epos
				return

			@staringAt = NULL
			@GetEntity()\SetEyeTarget(vector_origin)

		if player.GetCount() ~= 1
			local last
			max = 300
			local lastpos
			for _, ply in ipairs player.GetAll()
				if @GetEntity() ~= ply and ply\Alive()
					epos = ply\EyePos()
					dist = epos\Distance(lpos)
					if dist < max and DLib.combat.inPVS(@GetEntity(), ply) and @CheckTarget(lpos, epos)
						max = dist
						last = ply
						lastpos = epos
			if last
				@GetEntity()\SetEyeTarget(lastpos)
				@staringAt = last
				return

		return if @nextRollEyes > ctime
		@nextRollEyes = ctime + math.random(4, 8) / 6
		@eyeRollTargetPos = Vector(math.random(200, 400), math.random(-80, 80), math.random(-20, 20))
		@prevRollTargetPos = @prevRollTargetPos or @eyeRollTargetPos
		-- @GetEntity()\SetEyeTarget(@prevRollTargetPos)

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt) or IsValid(@staringAtDirectly)
		ctime = CurTimeL()
		delta = ctime - @rollEyesDelta
		@rollEyesDelta = ctime
		@prevRollTargetPos = LerpVector(delta * 25, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@GetEntity()\EyeAnglesFixed())
		@GetEntity()\SetEyeTarget(@GetEntity()\EyePos() + roll)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)

		if drawingNewTask
			with bones = ent\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, ent, @controller)
				\Think(true)
				ent.__ppmBonesModified = true

		@flexes\Think(ent) if @flexes
		@emotes\Think(ent) if @emotes
		if @GetEntity()\IsPlayer()
			@UpdateStare()
			@UpdateEyeRoll()

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes') and @GrabData('HideManesSocks')
			@socksModel\SetNoDraw(true) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(true) if IsValid(@newSocksModel)
		else
			@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)

	PostDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid
		@GetTextureController()\PostDraw(ent, drawingNewTask)

	@HANDS_MATERIAL_INDEX = 1
	@MAGIC_ARMS_MATERIAL_INDEX = 0

	PreDrawArms: (ent) =>
		return if not @isValid
		hooves = @GrabData('PonyRaceFlags')\band(PPM2.RACE_HAS_HORN) == 0

		if ent and hooves
			weight = 0.7 + (@GetData()\GetWeight() - 1) * 0.4
			vec = Vector(weight, weight, weight)
			ent\ManipulateBoneScale(i, vec) for i = 1, 43
		elseif ent
			ent\ManipulateBoneScale(i, vector_one) for i = 1, 13

		if hooves
			ent\SetSubMaterial(@@HANDS_MATERIAL_INDEX, @GetTextureController()\GetBodyName())

	PostDrawArms: (ent) =>
		return if not @isValid
		hooves = @GrabData('PonyRaceFlags')\band(PPM2.RACE_HAS_HORN) == 0
		ent\SetSubMaterial(@@HANDS_MATERIAL_INDEX, '') if hooves

	DataChanges: (state) =>
		return if not @isValid
		return if not @GetEntity()
		@GetTextureController()\DataChanges(state)
		@flexes\DataChanges(state) if @flexes
		@emotes\DataChanges(state) if @emotes

		switch state\GetKey()
			when 'Weight'
				@armsWeightSetup = false
				@GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
			when 'SocksModel'
				@socksModel = @GetData()\GetSocksModel()
				@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
				@GetTextureController()\UpdateSocks(@GetEntity(), @socksModel) if @GetTextureController() and IsValid(@socksModel)
			when 'NewSocksModel'
				@newSocksModel = @GetData()\GetNewSocksModel()
				@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)
				@GetTextureController()\UpdateNewSocks(@GetEntity(), @newSocksModel) if @GetTextureController() and IsValid(@newSocksModel)
			when 'NoFlex'
				if state\GetValue()
					@flexes\ResetSequences() if @flexes
					@flexes = nil
				else
					@CreateFlexController()
			when 'EyeIrisBottom', 'EyeIrisBottomLeft', 'EyeIrisBottomRight', 'SeparateEyes', 'HornMagicColor', 'EyeIrisTop', 'EyeIrisTopLeft', 'EyeIrisTopRight'
				if @GetEntity() == LocalPlayer()
					PPM2.MaterialsRegistry.MAGIC_HANDS_MATERIAL\SetVector('$colortint_base', @GetData()\ComputeMagicColor()\ToVector())
	GetTextureController: =>
		return @renderController if not @isValid
		if not @renderController
			cls = PPM2.GetTextureController(@modelCached)
			@renderController = cls(@)
		@renderController.ent = @GetEntity()
		return @renderController

	CreateFlexController: =>
		return @flexes if not @isValid
		return if @GetData()\GetNoFlex()
		if not @flexes
			cls = PPM2.GetFlexController(@modelCached)
			return if not cls
			@flexes = cls(@)
		@flexes.ent = @GetEntity()
		return @flexes

	CreateEmotesController: =>
		return @emotes if not @isValid
		if not @emotes or not @emotes\IsValid()
			cls = PPM2.GetPonyExpressionsController(@modelCached)
			return if not cls
			@emotes = cls(@)
		@emotes.ent = @GetEntity()
		return @emotes

	GetFlexController: => @flexes
	GetEmotesController: => @emotes

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

	new: (data) =>
		super(data)
		@upperManeModel = data\GetUpperManeModel()
		@lowerManeModel = data\GetLowerManeModel()
		@tailModel = data\GetTailModel()
		@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
	__tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

	DataChanges: (state) =>
		return if not @GetEntity()
		return if not @isValid
		switch state\GetKey()
			when 'UpperManeModel'
				@upperManeModel = @GetData()\GetUpperManeModel()
				@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
				@GetTextureController()\UpdateUpperMane(@GetEntity(), @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
			when 'LowerManeModel'
				@lowerManeModel = @GetData()\GetLowerManeModel()
				@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
				@GetTextureController()\UpdateLowerMane(@GetEntity(), @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
			when 'TailModel'
				@tailModel = @GetData()\GetTailModel()
				@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
				@GetTextureController()\UpdateTail(@GetEntity(), @tailModel) if @GetTextureController() and IsValid(@tailModel)
		super(state)

	DrawModels: =>
		@upperManeModel\DrawModel() if IsValid(@upperManeModel)
		@lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
		@tailModel\DrawModel() if IsValid(@tailModel)
		super()

	DoHideModels: (status) =>
		super(status)
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		super(ent, drawingNewTask)

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes')
			@upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel) and @GrabData('HideManesMane')
			@lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel) and @GrabData('HideManesMane')
			@tailModel\SetNoDraw(true) if IsValid(@tailModel) and @GrabData('HideManesTail')
		else
			@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
			@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
			@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)

hook.Add 'NotifyShouldTransmit', 'PPM2.RenderController', (should) =>
	if data = @GetPonyData()
		if renderer = data\GetRenderController()
			renderer\HideModels(not should)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController

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

import PPM2 from _G

vector_one = Vector(1, 1, 1)

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')
SLOW_STARE_UPDATE = CreateConVar('ppm2_render_stare_slow', '0', {FCVAR_ARCHIVE}, 'Lazy stare update to save a bit frames')

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

	CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
	new: (controller) =>
		super(controller)
		@hideModels = false
		@modelCached = controller\GetModel()
		@IGNORE_DRAW = false
		@CompileTextures() if @GetEntity()\IsValid()
		@CreateLegs() if @GetEntity() == LocalPlayer()
		@socksModel = controller\GetSocksModel()
		@socksModel\SetNoDraw(false) if IsValid(@socksModel)
		@newSocksModel = controller\GetNewSocksModel()
		@newSocksModel\SetNoDraw(false) if IsValid(@newSocksModel)
		@lastStareUpdate = 0
		@staringAt = NULL
		@staringAtDirectly = NULL
		@staringAtDirectlyLast = 0
		@staringAtDirectlyTr = false
		@rotatedHeadTarget = false
		@idleEyes = true
		@idleEyesActive = false
		@nextRollEyes = 0
		@rollEyesDelta = CurTimeL()
		if @GetEntity()\IsValid()
			@CreateFlexController()
			@CreateEmotesController()

	GetModel: => @controller\GetModel()

	GetLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		@CreateLegs() if not IsValid()
		return @legsModel

	CreateLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		for _, ent in ipairs ents.GetAll()
			if ent.isPonyLegsModel
				ent\Remove()

		with @legsModel = ClientsideModel(@modelCached)
			.isPonyLegsModel = true
			.lastRedrawFix = 0
			\SetNoDraw(true)
			.__PPM2_PonyData = @GetData()
			--\PPMBonesModifier()

		@GetData()\GetWeightController()\UpdateWeight(@legsModel)

		@lastLegUpdate = CurTimeL()
		@legClipPlanePos = Vector(0, 0, 0)
		@legBGSetup = CurTimeL()
		@legUpdateFrame = 0
		@legClipDot = 0
		@duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
		@legsClipPlane = @@LEG_CLIP_VECTOR
		return @legsModel

	@LEG_SHIFT_CONST = 24
	@LEG_SHIFT_CONST_VEHICLE = 14
	@LEG_Z_CONST = 0
	@LEG_Z_CONST_VEHICLE = 20
	@LEG_ANIM_SPEED_CONST = 1
	@LEG_CLIP_OFFSET_STAND = 28
	@LEG_CLIP_OFFSET_DUCK = 12
	@LEG_CLIP_OFFSET_VEHICLE = 11

	UpdateLegs: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return unless IsValid(@legsModel)
		return if @legUpdateFrame == FrameNumberL()
		@legUpdateFrame = FrameNumberL()
		ctime = CurTimeL()
		ply = @GetEntity()
		seq = ply\GetSequence()
		legsModel = @legsModel

		with @legsModel
			PPM2.EntityBonesModifier.ThinkObject(ply.__ppmBonesModifiers) if ply.__ppmBonesModifiers

			for boneid = 0, ply\GetBoneCount() - 1
				\ManipulateBonePosition(0, ply\GetManipulateBonePosition(0))
				\ManipulateBoneAngles(0, ply\GetManipulateBoneAngles(0))
				\ManipulateBoneScale(0, ply\GetManipulateBoneScale(0))

			if seq ~= @legSeq
				@legSeq = seq
				\ResetSequence(seq)

			if @legBGSetup < ctime
				@legBGSetup = ctime + 1
				for _, group in ipairs ply\GetBodyGroups()
					\SetBodygroup(group.id, ply\GetBodygroup(group.id))

			\FrameAdvance(ctime - @lastLegUpdate)
			\SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
			@lastLegUpdate = ctime
			\SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
			\SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
			\SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
			\SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
			\SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

		if ply\InVehicle()
			local bonePos

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					bonePos = boneData

			veh = ply\GetVehicle()
			vehAng = veh\GetAngles()
			eyepos = EyePos()
			vehAng\RotateAroundAxis(vehAng\Up(), 90)

			clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
			clipAng\RotateAroundAxis(clipAng\Right(), -90)

			@legsClipPlane = clipAng\Forward()
			@legsModel\SetRenderAngles(vehAng)

			drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
			drawPos\Rotate(vehAng)
			@legsModel\SetPos(eyepos - drawPos)
			@legsModel\SetRenderOrigin(eyepos - drawPos)

			if not bonePos
				legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
				legClipPlanePos\Rotate(vehAng)
				@legClipPlanePos = eyepos - legClipPlanePos
			else
				@legClipPlanePos = bonePos

		else
			@legsClipPlane = @@LEG_CLIP_VECTOR
			eangles = EyeAngles()
			yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
			newAng = Angle(0, yaw, 0)
			rad = math.rad(yaw)
			sin, cos = math.sin(rad), math.cos(rad)
			pos = ply\GetPos()
			{:x, :y, :z} = pos
			newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
			if ply\Crouching()
				@duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
			else
				@duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

			@legsModel\SetRenderAngles(newAng)
			@legsModel\SetAngles(newAng)
			@legsModel\SetRenderOrigin(newPos)
			@legsModel\SetPos(newPos)

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					@legClipPlanePos = boneData
				else
					@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
			else
				@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE

		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)

		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		if LEGS_RENDER_TYPE\GetBool() and ENABLE_FLASHLIGHT_PASS\GetBool()
			render.PushFlashlightMode(true)
			@GetTextureController()\PreDrawLegs(@legsModel)
			if sizes = @GetData()\GetSizeController()
				sizes\ModifyNeck(@legsModel)
				sizes\ModifyLegs(@legsModel)
				sizes\ModifyScale(@legsModel)
			@legsModel\DrawModel()
			@GetTextureController()\PostDrawLegs(@legsModel)
			render.PopFlashlightMode()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		if sizes = @GetData()\GetSizeController()
			sizes\ModifyNeck(@legsModel)
			sizes\ModifyLegs(@legsModel)
			sizes\ModifyScale(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

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

	Reset: =>
		@flexes\Reset() if @flexes and @flexes.Reset
		@emotes\Reset() if @emotes and @emotes.Reset
		@GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
		@GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()

	Remove: =>
		@flexes\Remove() if @flexes
		@emotes\Remove() if @emotes
		@GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
		@isValid = false

	PlayerDeath: =>
		return if not @isValid
		if @emotes
			@emotes\Remove()
			@emotes = nil
		@HideModels(true) if PPM2.ENABLE_NEW_RAGDOLLS\GetBool()
		@GetTextureController()\ResetTextures() if @GetTextureController() and @GetEntity()\IsPony()

	PlayerRespawn: =>
		return if not @isValid
		@GetEmotesController()
		@HideModels(false) if @GetEntity()\IsPony()
		@flexes\PlayerRespawn() if @flexes
		@GetTextureController()\ResetTextures() if @GetTextureController()

	DrawModels: =>
		@socksModel\DrawModel() if IsValid(@socksModel)
		@newSocksModel\DrawModel() if IsValid(@newSocksModel)

	ShouldHideModels: => @hideModels or @GetEntity()\GetNoDraw()

	DoHideModels: (status) =>
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)

	HideModels: (status = true) =>
		return if @hideModels == status
		@DoHideModels(status)
		@hideModels = status

	CheckTarget: (epos, pos) =>
		return not util.TraceLine({
			start: epos,
			endpos: pos,
			filter: @GetEntity(),
			mask: MASK_BLOCKLOS
		}).Hit

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime and SLOW_STARE_UPDATE\GetBool()

		if (not @idleEyes or not ENABLE_STARE\GetBool()) and @idleEyesActive
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(vector_origin)
			@idleEyesActive = false
			return

		return if not @idleEyes or not ENABLE_STARE\GetBool()
		@idleEyesActive = true
		@lastStareUpdate = ctime + 0.2
		lpos = @GetEntity()\EyePos()
		lang = @GetEntity()\EyeAnglesFixed()

		@staringAt = NULL if IsValid(@staringAt) and @staringAt\IsPlayer() and not @staringAt\Alive()

		trNew = util.TraceLine({
			start: lpos,
			endpos: lpos + lang\Forward() * 270,
			filter: @GetEntity(),
		})

		if IsValid(trNew.Entity)
			mins, maxs = trNew.Entity\OBBMins(), trNew.Entity\OBBMaxs()
			size = mins\Distance(maxs)

			if size < 140
				@staringAtDirectly = trNew.Entity
				@staringAtDirectlyLast = CurTimeL() + 2.5
				@staringAtDirectlyTr = trNew

		if IsValid(@staringAtDirectly) and (not @staringAtDirectly\IsPlayer() and not @staringAtDirectly\IsNPC())
			if @staringAtDirectlyLast > CurTimeL()
				pos = @staringAtDirectlyTr.HitPos

				if pos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAtDirectly) and @CheckTarget(lpos, pos)
					@GetEntity()\SetEyeTarget(pos)
					_lpos, _lang = WorldToLocal(pos, angle_zero, lpos, lang)
					@prevRollTargetPos = _lpos
					return

				@staringAtDirectly = NULL
				@staringAtDirectlyLast = 0
				@GetEntity()\SetEyeTarget(vector_origin)
			else
				@staringAtDirectly = NULL

		if trNew.Entity\IsValid() and (trNew.Entity\IsPlayer() or trNew.Entity\IsNPC())
			@staringAt = trNew.Entity

		if IsValid(@staringAt)
			epos = @staringAt\EyePos()

			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAt) and @CheckTarget(lpos, epos)
				@GetEntity()\SetEyeTarget(epos)
				@prevRollTargetPos = epos
				return

			@staringAt = NULL
			@GetEntity()\SetEyeTarget(vector_origin)

		if player.GetCount() ~= 1
			local last
			max = 300
			local lastpos
			for _, ply in ipairs player.GetAll()
				if @GetEntity() ~= ply and ply\Alive()
					epos = ply\EyePos()
					dist = epos\Distance(lpos)
					if dist < max and DLib.combat.inPVS(@GetEntity(), ply) and @CheckTarget(lpos, epos)
						max = dist
						last = ply
						lastpos = epos
			if last
				@GetEntity()\SetEyeTarget(lastpos)
				@staringAt = last
				return

		return if @nextRollEyes > ctime
		@nextRollEyes = ctime + math.random(4, 8) / 6
		@eyeRollTargetPos = Vector(math.random(200, 400), math.random(-80, 80), math.random(-20, 20))
		@prevRollTargetPos = @prevRollTargetPos or @eyeRollTargetPos
		-- @GetEntity()\SetEyeTarget(@prevRollTargetPos)

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt) or IsValid(@staringAtDirectly)
		ctime = CurTimeL()
		delta = ctime - @rollEyesDelta
		@rollEyesDelta = ctime
		@prevRollTargetPos = LerpVector(delta * 25, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@GetEntity()\EyeAnglesFixed())
		@GetEntity()\SetEyeTarget(@GetEntity()\EyePos() + roll)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)

		if drawingNewTask
			with bones = ent\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, ent, @controller)
				\Think(true)
				ent.__ppmBonesModified = true

		@flexes\Think(ent) if @flexes
		@emotes\Think(ent) if @emotes
		if @GetEntity()\IsPlayer()
			@UpdateStare()
			@UpdateEyeRoll()

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes') and @GrabData('HideManesSocks')
			@socksModel\SetNoDraw(true) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(true) if IsValid(@newSocksModel)
		else
			@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)

	PostDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid
		@GetTextureController()\PostDraw(ent, drawingNewTask)

	@HANDS_MATERIAL_INDEX = 1
	@MAGIC_ARMS_MATERIAL_INDEX = 0

	PreDrawArms: (ent) =>
		return if not @isValid
		hooves = @GrabData('PonyRaceFlags')\band(PPM2.RACE_HAS_HORN) == 0

		if ent and hooves
			weight = 0.7 + (@GetData()\GetWeight() - 1) * 0.4
			vec = Vector(weight, weight, weight)
			ent\ManipulateBoneScale(i, vec) for i = 1, 43
		elseif ent
			ent\ManipulateBoneScale(i, vector_one) for i = 1, 13

		if hooves
			ent\SetSubMaterial(@@HANDS_MATERIAL_INDEX, @GetTextureController()\GetBodyName())

	PostDrawArms: (ent) =>
		return if not @isValid
		hooves = @GrabData('PonyRaceFlags')\band(PPM2.RACE_HAS_HORN) == 0
		ent\SetSubMaterial(@@HANDS_MATERIAL_INDEX, '') if hooves

	DataChanges: (state) =>
		return if not @isValid
		return if not @GetEntity()
		@GetTextureController()\DataChanges(state)
		@flexes\DataChanges(state) if @flexes
		@emotes\DataChanges(state) if @emotes
		if @GetEntity() == LocalPlayer()
			PPM2.MaterialsRegistry.MAGIC_HANDS_MATERIAL\SetVector('$colortint_base', @GetData()\ComputeMagicColor()\ToVector())
		switch state\GetKey()
			when 'Weight'
				@armsWeightSetup = false
				@GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
			when 'SocksModel'
				@socksModel = @GetData()\GetSocksModel()
				@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
				@GetTextureController()\UpdateSocks(@GetEntity(), @socksModel) if @GetTextureController() and IsValid(@socksModel)
			when 'NewSocksModel'
				@newSocksModel = @GetData()\GetNewSocksModel()
				@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)
				@GetTextureController()\UpdateNewSocks(@GetEntity(), @newSocksModel) if @GetTextureController() and IsValid(@newSocksModel)
			when 'NoFlex'
				if state\GetValue()
					@flexes\ResetSequences() if @flexes
					@flexes = nil
				else
					@CreateFlexController()
	GetTextureController: =>
		return @renderController if not @isValid
		if not @renderController
			cls = PPM2.GetTextureController(@modelCached)
			@renderController = cls(@)
		@renderController.ent = @GetEntity()
		return @renderController

	CreateFlexController: =>
		return @flexes if not @isValid
		return if @GetData()\GetNoFlex()
		if not @flexes
			cls = PPM2.GetFlexController(@modelCached)
			return if not cls
			@flexes = cls(@)
		@flexes.ent = @GetEntity()
		return @flexes

	CreateEmotesController: =>
		return @emotes if not @isValid
		if not @emotes or not @emotes\IsValid()
			cls = PPM2.GetPonyExpressionsController(@modelCached)
			return if not cls
			@emotes = cls(@)
		@emotes.ent = @GetEntity()
		return @emotes

	GetFlexController: => @flexes
	GetEmotesController: => @emotes

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

	new: (data) =>
		super(data)
		@upperManeModel = data\GetUpperManeModel()
		@lowerManeModel = data\GetLowerManeModel()
		@tailModel = data\GetTailModel()
		@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
	__tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

	DataChanges: (state) =>
		return if not @GetEntity()
		return if not @isValid
		switch state\GetKey()
			when 'UpperManeModel'
				@upperManeModel = @GetData()\GetUpperManeModel()
				@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
				@GetTextureController()\UpdateUpperMane(@GetEntity(), @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
			when 'LowerManeModel'
				@lowerManeModel = @GetData()\GetLowerManeModel()
				@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
				@GetTextureController()\UpdateLowerMane(@GetEntity(), @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
			when 'TailModel'
				@tailModel = @GetData()\GetTailModel()
				@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
				@GetTextureController()\UpdateTail(@GetEntity(), @tailModel) if @GetTextureController() and IsValid(@tailModel)
		super(state)

	DrawModels: =>
		@upperManeModel\DrawModel() if IsValid(@upperManeModel)
		@lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
		@tailModel\DrawModel() if IsValid(@tailModel)
		super()

	DoHideModels: (status) =>
		super(status)
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		super(ent, drawingNewTask)

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes')
			@upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel) and @GrabData('HideManesMane')
			@lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel) and @GrabData('HideManesMane')
			@tailModel\SetNoDraw(true) if IsValid(@tailModel) and @GrabData('HideManesTail')
		else
			@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
			@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
			@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)

hook.Add 'NotifyShouldTransmit', 'PPM2.RenderController', (should) =>
	if data = @GetPonyData()
		if renderer = data\GetRenderController()
			renderer\HideModels(not should)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController