Example: work properly with multiple selected lines.

-- This example is unlicensed under CC0

export script_name        = "ASSInspector Example"
export script_description = "Calculates bounding rectangles on the start and end times of selected events."
export script_author      = "torque"
export script_version     = 0x000003

ffi  = require( 'ffi' )
util = require( 'aegisub.util' )
log  = require( 'a-mo.log' )

ffi.cdef( [[
typedef struct {
	int32_t x, y;
	uint32_t w, h;
} ASSI_Rect;

uint32_t    assi_getVersion( void );
const char* assi_getErrorString( void* );
void*       assi_init( int, int, const char*, uint32_t, const char* );
void        assi_setFontDir( void*, const char* );
int         assi_setScript( void*, const char*, uint32_t, const char *, const uint32_t );
int         assi_calculateBounds( void*, ASSI_Rect*, const int32_t*, const uint32_t );
void        assi_cleanup( void* );
]] )

-- Figure out a better way to load this?
extension = ('OSX' == ffi.os and '.dylib' or 'Windows' == ffi.os and '.dll' or '.so')
libraryPath = "?user/automation/include/ASSInspector"
success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
if not success
	libraryPath = "?data/automation/include/ASSInspector"
	success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
	if not success
		error( "Could not load required libASSInspector library." )

-- Script-wide local variables are stored until the script is reloaded,
-- at which point they are garbage collected. We can abuse this system
-- to create a persistent ASSInspector state that only gets deallocated
-- when the automation script is reloaded or Aegisub is quit.

-- NOTE: this can be somewhat risky. For example, loading a new subtitle
-- file in Aegisub won't reset the automation scripts, and thus the old
-- header information will continue being used. It would be better to
-- make some uniqueness check on the loaded script. The full path to the
-- script would be an option, except that doesn't work for scripts that
-- have yet to be saved to disk. Appending some sort of UUID to the
-- script header and checking that each run would probably be more
-- foolproof. This implementation detail is left as an exercise to the
-- user.
inspector = nil
initializeInspector = ( resX, resY, minimalHeader ) ->
	if nil == inspector
		inspector = ffi.gc( ASSInspector.assi_init( resX, resY, minimalHeader, #minimalHeader, aegisub.decode_path( libraryPath .. "/fonts.conf" ) ), ASSInspector.assi_cleanup )
		if nil == inspector
			log.windowError( "ASSInspector library init failed." )

		-- Tell ASSInspector to tell fontconfig to search the fonts directory
		-- in the script directory for fonts to load.
		if '?script' != aegisub.decode_path( '?script' )
			ASSInspector.assi_setFontDir( inspector, aegisub.decode_path( '?script/fonts' ) )

dumpRect = ( event, rect ) ->
	bounds = {
		x:  tonumber( rect.x )
		y:  tonumber( rect.y )
		x2: tonumber( rect.w + rect.x )
		y2: tonumber( rect.h + rect.y )
	}

	newEvent = util.deep_copy( event )
	with bounds
		newEvent.text = ([[{\an7\pos(0,0)\bord0\shad0\c&H0000FF&\1a&H00&\p1}m %d %d l %d %d %d %d %d %d]])\format( .x, .y, .x2, .y, .x2, .y2, .x, .y2 )

	event.layer += 1
	return newEvent

mainFunction = ( subtitle, selectedLines, activeLine ) ->
	if ASSInspector.assi_getVersion( ) < script_version
		log.windowError( "Installed ASSInspector library is outdated. Please update it." )
		return selectedLines
	elseif ASSInspector.assi_getVersion( ) > script_version
		log.windowError( "Installed OptimASS is outdated. Please update it." )
		return selectedLines

	scriptHeader = {
		"[Script Info]"
	}

	-- These are the only header fields that actually affect the way ASS
	-- is rendered. I don't actually know if ScriptType makes a matters.
	infoFields = {
		PlayResX:   true
		PlayResY:   true
		WrapStyle:  true
		ScriptType: true
		ScaledBorderAndShadow: true
	}

	styles = { }

	-- If there is no video (or, at minimum, timecodes) loaded, this will
	-- return nil.
	hasTimecodes = aegisub.frame_from_ms( 0 )

	-- Copy these functions into local variables to shorten them.
	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame

	subtitleLen = #subtitle
	seenStyles = false
	resX = 640
	resY = 480

	-- This loop assumes the subtitle script is nicely organized in the
	-- order [Script Info] -> [V4+ Styles] -> [Events]. This isn't
	-- guaranteed for scripts floating around in the wild, but fortunately
	-- Aegisub 3.2 seems to do a very good job of ensuring this order when
	-- a script is loaded. In other words, this loop is probably only safe
	-- to do from within automation.
	for index = 1, subtitleLen
		with line = subtitle[index]
			if "info" == .class
				if infoFields[.key]
					table.insert( scriptHeader, .raw )

				if "PlayResX" == .key
					resX = tonumber( .value )
				elseif "PlayResY" == .key
					resY = tonumber( .value )

			elseif "style" == .class
				unless seenStyles
					table.insert( scriptHeader, "[V4+ Styles]\n" )
					seenStyles = true

				styles[.name] = .raw

			elseif "dialogue" == .class
				break

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution. Not convinced this is a great idea because people may
	-- use smaller workraws or something like that. Well, hopefully they
	-- don't do that for typesetting.
	vidResX, vidResY = aegisub.video_size( )
	resX = vidResX or resX
	resY = vidResY or resY

	minimalHeader = table.concat( scriptHeader, '\n' )

	initializeInspector( resX, resY, minimalHeader )

	insertIndices = { }
	insertEvents  = { }

	for eventIndex in *selectedLines
		with event = subtitle[eventIndex]
			-- This does not account for \r[name] being in the line.
			styleText = (styles[.style] or "") .. "\n[Events]\n"
			eventText = .raw

			-- It occurs to me that perhaps having the user just pass in one
			-- string that contains both styles and events may be easier.
			if 0 < ASSInspector.assi_setScript( inspector, styleText, #styleText, eventText, #eventText )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				ASSInspector.assi_cleanup( inspector )
				aegisub.cancel( )

			-- We're going to render a line twice, once at its start and once
			-- at its end.
			renderCount = 2

			renderTimes = { }

			-- Cheat a bit when generating the times to render. This obviously
			-- will not scale with a renderCount > 2. Use 0-index arrays to
			-- match up with C indices in a later loop.
			if hasTimecodes
				-- Using the aegisub.frame_from_ms function pointers we copied
				-- earlier.
				startFrame = ffms( .start_time )
				endFrame   = ffms( .end_time ) - 1
				renderTimes[0] = math.floor( 0.5*( msff( startFrame ) + msff( startFrame + 1 ) ) )
				renderTimes[1] = math.floor( 0.5*( msff( endFrame ) + msff( endFrame + 1 ) ) )
			else
				renderTimes[0] = .start_time
				-- Trying to render exactly at end_time will result in a
				-- completely blank frame.
				renderTimes[1] = .end_time - 1

			-- Because we're rendering the line twice, we need two rects.
			rects = ffi.new( "ASSI_Rect[?]", renderCount )

			-- Allocate the array of times for rendering.
			times = ffi.new( "int32_t[?]", renderCount )

			-- Set up the render times. There's no real reason to do this in a
			-- loop if you know beforehand you're only going to render the
			-- line twice, but loops are more generic.
			for render = 0, renderCount - 1
				times[render] = renderTimes[render]

			-- Send the data in for rendering and bounds calculation.
			if 0 < ASSInspector.assi_calculateBounds( inspector, rects, times, renderCount )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				aegisub.cancel( )

			-- Check out the calculated bounding rects
			for render = 0, renderCount - 1
				if rects[render].w > 0 and rects[render].h > 0
					newEvent = dumpRect( event, rects[render] )
					table.insert( insertIndices, eventIndex )
					table.insert( insertEvents, newEvent)

			subtitle[eventIndex] = event

	for index = #insertIndices, 1, -1
		subtitle.insert( insertIndices[index], insertEvents[index] )

aegisub.register_macro( script_name, script_description, mainFunction )

-- This example is unlicensed under CC0

export script_name        = "ASSInspector Example"
export script_description = "Calculates bounding rectangles on the start and end times of selected events."
export script_author      = "torque"
export script_version     = 0x000003

ffi  = require( 'ffi' )
util = require( 'aegisub.util' )
log  = require( 'a-mo.log' )

ffi.cdef( [[
typedef struct {
	int32_t x, y;
	uint32_t w, h;
} ASSI_Rect;

uint32_t    assi_getVersion( void );
const char* assi_getErrorString( void* );
void*       assi_init( int, int, const char*, uint32_t, const char* );
void        assi_setFontDir( void*, const char* );
int         assi_setScript( void*, const char*, uint32_t, const char *, const uint32_t );
int         assi_calculateBounds( void*, ASSI_Rect*, const int32_t*, const uint32_t );
void        assi_cleanup( void* );
]] )

-- Figure out a better way to load this?
extension = ('OSX' == ffi.os and '.dylib' or 'Windows' == ffi.os and '.dll' or '.so')
libraryPath = "?user/automation/include/ASSInspector"
success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
if not success
	libraryPath = "?data/automation/include/ASSInspector"
	success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
	if not success
		error( "Could not load required libASSInspector library." )

-- Script-wide local variables are stored until the script is reloaded,
-- at which point they are garbage collected. We can abuse this system
-- to create a persistent ASSInspector state that only gets deallocated
-- when the automation script is reloaded or Aegisub is quit.

-- NOTE: this can be somewhat risky. For example, loading a new subtitle
-- file in Aegisub won't reset the automation scripts, and thus the old
-- header information will continue being used. It would be better to
-- make some uniqueness check on the loaded script. The full path to the
-- script would be an option, except that doesn't work for scripts that
-- have yet to be saved to disk. Appending some sort of UUID to the
-- script header and checking that each run would probably be more
-- foolproof. This implementation detail is left as an exercise to the
-- user.
inspector = nil
initializeInspector = ( resX, resY, minimalHeader ) ->
	if nil == inspector
		inspector = ffi.gc( ASSInspector.assi_init( resX, resY, minimalHeader, #minimalHeader, aegisub.decode_path( libraryPath .. "/fonts.conf" ) ), ASSInspector.assi_cleanup )
		if nil == inspector
			log.windowError( "ASSInspector library init failed." )

		-- Tell ASSInspector to tell fontconfig to search the fonts directory
		-- in the script directory for fonts to load.
		if '?script' != aegisub.decode_path( '?script' )
			ASSInspector.assi_setFontDir( inspector, aegisub.decode_path( '?script/fonts' ) )

dumpRect = ( event, rect ) ->
	bounds = {
		x:  tonumber( rect.x )
		y:  tonumber( rect.y )
		x2: tonumber( rect.w + rect.x )
		y2: tonumber( rect.h + rect.y )
	}

	newEvent = util.deep_copy( event )
	with bounds
		newEvent.text = ([[{\an7\pos(0,0)\bord0\shad0\c&H0000FF&\1a&H00&\p1}m %d %d l %d %d %d %d %d %d]])\format( .x, .y, .x2, .y, .x2, .y2, .x, .y2 )

	event.layer += 1
	return newEvent

mainFunction = ( subtitle, selectedLines, activeLine ) ->
	if ASSInspector.assi_getVersion( ) < script_version
		log.windowError( "Installed ASSInspector library is outdated. Please update it." )
		return selectedLines
	elseif ASSInspector.assi_getVersion( ) > script_version
		log.windowError( "Installed OptimASS is outdated. Please update it." )
		return selectedLines

	scriptHeader = {
		"[Script Info]"
	}

	-- These are the only header fields that actually affect the way ASS
	-- is rendered. I don't actually know if ScriptType makes a matters.
	infoFields = {
		PlayResX:   true
		PlayResY:   true
		WrapStyle:  true
		ScriptType: true
		ScaledBorderAndShadow: true
	}

	styles = { }

	-- If there is no video (or, at minimum, timecodes) loaded, this will
	-- return nil.
	hasTimecodes = aegisub.frame_from_ms( 0 )

	-- Copy these functions into local variables to shorten them.
	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame

	subtitleLen = #subtitle
	seenStyles = false
	resX = 640
	resY = 480

	-- This loop assumes the subtitle script is nicely organized in the
	-- order [Script Info] -> [V4+ Styles] -> [Events]. This isn't
	-- guaranteed for scripts floating around in the wild, but fortunately
	-- Aegisub 3.2 seems to do a very good job of ensuring this order when
	-- a script is loaded. In other words, this loop is probably only safe
	-- to do from within automation.
	for index = 1, subtitleLen
		with line = subtitle[index]
			if "info" == .class
				if infoFields[.key]
					table.insert( scriptHeader, .raw )

				if "PlayResX" == .key
					resX = tonumber( .value )
				elseif "PlayResY" == .key
					resY = tonumber( .value )

			elseif "style" == .class
				unless seenStyles
					table.insert( scriptHeader, "[V4+ Styles]\n" )
					seenStyles = true

				styles[.name] = .raw

			elseif "dialogue" == .class
				break

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution. Not convinced this is a great idea because people may
	-- use smaller workraws or something like that. Well, hopefully they
	-- don't do that for typesetting.
	vidResX, vidResY = aegisub.video_size( )
	resX = vidResX or resX
	resY = vidResY or resY

	minimalHeader = table.concat( scriptHeader, '\n' )

	initializeInspector( resX, resY, minimalHeader )

	for eventIndex in *selectedLines
		with event = subtitle[eventIndex]
			-- This does not account for \r[name] being in the line.
			styleText = (styles[.style] or "") .. "\n[Events]\n"
			eventText = .raw

			-- It occurs to me that perhaps having the user just pass in one
			-- string that contains both styles and events may be easier.
			if 0 < ASSInspector.assi_setScript( inspector, styleText, #styleText, eventText, #eventText )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				ASSInspector.assi_cleanup( inspector )
				aegisub.cancel( )

			-- We're going to render a line twice, once at its start and once
			-- at its end.
			renderCount = 2

			renderTimes = { }

			-- Cheat a bit when generating the times to render. This obviously
			-- will not scale with a renderCount > 2. Use 0-index arrays to
			-- match up with C indices in a later loop.
			if hasTimecodes
				-- Using the aegisub.frame_from_ms function pointers we copied
				-- earlier.
				startFrame = ffms( .start_time )
				endFrame   = ffms( .end_time ) - 1
				renderTimes[0] = math.floor( 0.5*( msff( startFrame ) + msff( startFrame + 1 ) ) )
				renderTimes[1] = math.floor( 0.5*( msff( endFrame ) + msff( endFrame + 1 ) ) )
			else
				renderTimes[0] = .start_time
				-- Trying to render exactly at end_time will result in a
				-- completely blank frame.
				renderTimes[1] = .end_time - 1

			-- Because we're rendering the line twice, we need two rects.
			rects = ffi.new( "ASSI_Rect[?]", renderCount )

			-- Allocate the array of times for rendering.
			times = ffi.new( "int32_t[?]", renderCount )

			-- Set up the render times. There's no real reason to do this in a
			-- loop if you know beforehand you're only going to render the
			-- line twice, but loops are more generic.
			for render = 0, renderCount - 1
				times[render] = renderTimes[render]

			-- Send the data in for rendering and bounds calculation.
			if 0 < ASSInspector.assi_calculateBounds( inspector, rects, times, renderCount )
				log.warn( ASSInspector.assi_getErrorString( inspector ) )
				aegisub.cancel( )

			-- Check out the calculated bounding rects
			for render = 0, renderCount - 1
				newEvent = dumpRect( event, rects[render] )
				-- log.dump newEvent
				subtitle.insert( eventIndex + render, newEvent )

			subtitle[eventIndex + renderCount] = event

aegisub.register_macro( script_name, script_description, mainFunction )


Allow to disable new hull

Allow to disable new hull

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

for ply in *player.GetAll()
    ply.__PPM2_PonyData\Remove() if ply.__PPM2_PonyData

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

class NetworkedPonyData extends PPM2.NetworkedObject
    @NW_ClientsideCreation = true
    @RenderTasks = {}

    @Setup()
    @NetworkVar('Entity',           net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), ((newValue) => IsValid(@GetOwner()) and StrongEntity(@GetOwner()\EntIndex()) or newValue))
    @NetworkVar('UpperManeModel',   net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('LowerManeModel',   net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('TailModel',        net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('SocksModel',       net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)

    @NetworkVar('Race',             (-> math.Clamp(net.ReadUInt(4), 0, 3)), ((arg = PPM2.RACE_EARTH) -> net.WriteUInt(arg, 4)), PPM2.RACE_EARTH)
    @NetworkVar('Gender',           (-> math.Clamp(net.ReadUInt(4), 0, 1)), ((arg = PPM2.GENDER_FEMALE) -> net.WriteUInt(arg, 4)), PPM2.GENDER_FEMALE)
    @NetworkVar('Weight',           (-> math.Clamp(net.ReadFloat(), PPM2.MIN_WEIGHT, PPM2.MAX_WEIGHT)), net.WriteFloat, 1)
    @NetworkVar('PonySize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_SCALE, PPM2.MAX_SCALE)),   net.WriteFloat, 1)

    -- Reserved - they can be accessed/used/changed, but they do not do anything
    @NetworkVar('Age',              (-> math.Clamp(net.ReadUInt(4), 0, 2)), ((arg = PPM2.AGE_ADULT) -> net.WriteUInt(arg, 4)), PPM2.AGE_ADULT)

    @NetworkVar('EyelashType',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_EYELASHES, PPM2.MAX_EYELASHES)),           ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('TailType',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_TAILS, PPM2.MAX_TAILS)),                   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeType',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_UPPER_MANES, PPM2.MAX_UPPER_MANES)),       ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeLower',    (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_LOWER_MANES, PPM2.MAX_LOWER_MANES)),       ((arg = 0) -> net.WriteUInt(arg, 8)), 0)

    @NetworkVar('TailTypeNew',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_TAILS_NEW, PPM2.MAX_TAILS_NEW)),               ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeNew',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_UPPER_MANES_NEW, PPM2.MAX_UPPER_MANES_NEW)),   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeLowerNew', (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_LOWER_MANES_NEW, PPM2.MAX_LOWER_MANES_NEW)),   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)

    @NetworkVar('BodyColor',        net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))

    @NetworkVar('SeparateEyes',     net.ReadBool, net.WriteBool,              false)
    for publicName in *{'', 'Left', 'Right'}
        @NetworkVar("EyeType#{publicName}",          (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_EYE_TYPE, PPM2.MAX_EYE_TYPE)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
        @NetworkVar("EyeBackground#{publicName}",    net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeHole#{publicName}",          net.ReadColor, net.WriteColor, 	    Color(0,   0,   0  ))
        @NetworkVar("EyeIrisTop#{publicName}",       net.ReadColor, net.WriteColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisBottom#{publicName}",    net.ReadColor, net.WriteColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisLine1#{publicName}",     net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeIrisLine2#{publicName}",     net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeReflection#{publicName}",    net.ReadColor, net.WriteColor,    Color(255, 255, 255, 127))
        @NetworkVar("EyeEffect#{publicName}",        net.ReadColor, net.WriteColor,         Color(255, 255, 255))
        @NetworkVar("EyeLines#{publicName}",         net.ReadBool, net.WriteBool,                           true)
        @NetworkVar("DerpEyes#{publicName}",         net.ReadBool, net.WriteBool,                          false)
        @NetworkVar("DerpEyesStrength#{publicName}", (-> math.Clamp(net.ReadFloat(), PPM2.MIN_DERP_STRENGTH, PPM2.MAX_DERP_STRENGTH)), net.WriteFloat, 1)
        @NetworkVar("HoleWidth#{publicName}",        (-> math.Clamp(net.ReadFloat(), PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE)), net.WriteFloat,  1)
        @NetworkVar("HoleHeight#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE)), net.WriteFloat,  1)
        @NetworkVar("HoleSize#{publicName}",         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE, PPM2.MAX_HOLE)),             net.WriteFloat, .8)
        @NetworkVar("HoleShiftX#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT)), net.WriteFloat, 0)
        @NetworkVar("HoleShiftY#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT)), net.WriteFloat, 0)
        @NetworkVar("IrisSize#{publicName}",         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, .8)
        @NetworkVar("IrisWidth#{publicName}",        (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, 1)
        @NetworkVar("IrisHeight#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, 1)
        @NetworkVar("EyeRotation#{publicName}",      (-> math.Clamp(net.ReadInt(12), PPM2.MIN_EYE_ROTATION, PPM2.MAX_EYE_ROTATION)), ((arg = 0) -> net.WriteInt(arg, 12)), 1)
        @NetworkVar("EyeURL#{publicName}",           net.ReadString,                                                            net.WriteString, '')

    @NetworkVar('SeparateMane',     net.ReadBool, net.WriteBool,              false)
    for i = 1, 6
        @NetworkVar("ManeColor#{i}",            net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeDetailColor#{i}",      net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeURLColor#{i}",         net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeURL#{i}",              net.ReadString, net.WriteString,   '')

        @NetworkVar("TailColor#{i}",            net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailDetailColor#{i}",      net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailURLColor#{i}",         net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailURL#{i}",              net.ReadString, net.WriteString,   '')

        @NetworkVar("LowerManeColor#{i}",       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeURL#{i}",         net.ReadString, net.WriteString,   '')
        @NetworkVar("LowerManeURLColor#{i}",    net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeDetailColor#{i}", net.ReadColor, net.WriteColor,     Color(255, 255, 255))

        @NetworkVar("UpperManeColor#{i}",       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURL#{i}",         net.ReadString, net.WriteString,   '')
        @NetworkVar("UpperManeDetailColor#{i}", net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURLColor#{i}",    net.ReadColor, net.WriteColor,     Color(255, 255, 255))

    @NetworkVar('CMark',            net.ReadBool, net.WriteBool,              true)
    @NetworkVar('CMarkSize',        (-> math.Clamp(net.ReadFloat(), 0, 1)), net.WriteFloat, 1)
    @NetworkVar('CMarkColor',       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
    @NetworkVar('CMarkURL',         net.ReadString, net.WriteString,            '')
    @NetworkVar('CMarkType',        (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_CMARK, PPM2.MAX_CMARK)),           ((arg = 4) -> net.WriteUInt(arg, 8)), 4)
    @NetworkVar('TailSize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_TAIL_SIZE, PPM2.MAX_TAIL_SIZE)),   net.WriteFloat, 1)

    @NetworkVar('Bodysuit',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_SUIT, PPM2.MAX_SUIT)),             ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('Socks',            net.ReadBool, net.WriteBool,              false)
    @NetworkVar('NoFlex',           net.ReadBool, net.WriteBool,              false)

    @NetworkVar('SocksAsModel',     net.ReadBool, net.WriteBool,              false)
    @NetworkVar('SocksTexture',     (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_SOCKS, PPM2.MAX_SOCKS)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('SocksColor',       net.ReadColor, net.WriteColor,            Color(255, 255, 255))
    @NetworkVar('SocksTextureURL',  net.ReadString, net.WriteString,          '')

    for i = 1, 6
        @NetworkVar('SocksDetailColor' .. i, net.ReadColor, net.WriteColor, Color(255, 255, 255))

    @NetworkVar('BatPonyEars',      net.ReadBool, net.WriteBool,              false)
    @NetworkVar('Fangs',            net.ReadBool, net.WriteBool,              false)
    @NetworkVar('ClawTeeth',        net.ReadBool, net.WriteBool,              false)

    @NetworkVar('TeethColor',       net.ReadColor, net.WriteColor,    Color(255, 255, 255))
    @NetworkVar('MouthColor',       net.ReadColor, net.WriteColor,    Color(219, 65, 155))
    @NetworkVar('TongueColor',      net.ReadColor, net.WriteColor,    Color(235, 131, 59))

    for {:flex, :active} in *PPM2.PonyFlexController.FLEX_LIST
        @NetworkVar("DisableFlex#{flex}", net.ReadBool, net.WriteBool, false) if active

    for i = 1, PPM2.MAX_BODY_DETAILS
        @NetworkVar("BodyDetail#{i}",       (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_DETAIL, PPM2.MAX_DETAIL)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
        @NetworkVar("BodyDetailColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("BodyDetailURL#{i}",    net.ReadString, net.WriteString, '')
        @NetworkVar("BodyDetailURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("HornURL#{i}",       net.ReadString, net.WriteString, '')
        @NetworkVar("WingsURL#{i}",      net.ReadString, net.WriteString, '')
        @NetworkVar("HornURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("WingsURLColor#{i}", net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    @NetworkVar('Fly',                  net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('DisableTask',          net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('UseFlexLerp',          net.ReadBool,   net.WriteBool,                  true)
    @NetworkVar('FlexLerpMultiplier',   net.ReadFloat,  net.WriteFloat,                    1)
    @NetworkVar('NewMuzzle',            net.ReadBool,   net.WriteBool,                  true)
    @NetworkVar('SeparateWings',        net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('SeparateHorn',         net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('WingsColor',           net.ReadColor,  net.WriteColor,  Color(255, 255, 255))
    @NetworkVar('HornColor',            net.ReadColor,  net.WriteColor,  Color(255, 255, 255))

    @NetworkVar('WingsType',            (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_WINGS, PPM2.MAX_WINGS)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('MaleBuff',             (-> math.Clamp(net.ReadFloat(), PPM2.MIN_MALE_BUFF, PPM2.MAX_MALE_BUFF)), net.WriteFloat, PPM2.DEFAULT_MALE_BUFF)

    @NetworkVar('BatWingColor',         net.ReadColor, net.WriteColor,    Color(255, 255, 255))
    @NetworkVar('BatWingSkinColor',     net.ReadColor, net.WriteColor,    Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("BatWingURL#{i}",           net.ReadString, net.WriteString, '')
        @NetworkVar("BatWingSkinURL#{i}",       net.ReadString, net.WriteString, '')
        @NetworkVar("BatWingURLColor#{i}",      net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("BatWingSkinURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    Clone: (target = @ent) =>
        copy = @@(nil, target)
        @ApplyDataToObject(copy)
        return copy

    new: (netID, ent) =>
        @recomputeTextures = true
        @isValid = true
        if ent
            @modelCached = ent\GetModel()
            @SetEntity(ent)
            @SetupEntity(ent)
        super(netID)
    IsValid: => @isValid
    GetModel: => @modelCached
    EntIndex: => @entID
    SetupEntity: (ent) =>
        if ent.__PPM2_PonyData
            return if ent.__PPM2_PonyData\GetOwner() ~= @GetOwner()
            ent.__PPM2_PonyData\Remove() if ent.__PPM2_PonyData.Remove and ent.__PPM2_PonyData ~= @
        ent.__PPM2_PonyData = @
        @ent = ent
        return unless IsValid(ent)
        @modelCached = ent\GetModel()
        @ent = ent
        @flightController = PPM2.PonyflyController(@)
        @entID = ent\EntIndex()
        @ModelChanges(@modelCached, @modelCached)
        @Reset()
        if CLIENT
            timer.Simple 0, ->
                @GetRenderController()\CompileTextures() if @GetRenderController()
        PPM2.DebugPrint('Ponydata ', @, ' was updated to use for ', @ent)
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    ModelChanges: (old = @ent\GetModel(), new = old) =>
        @modelCached = new
        @SetFly(false) if SERVER
        timer.Simple 0.5, ->
            return unless IsValid(@ent)
            @GetBodygroupController()\ApplyBodygroups(CLIENT) if @GetBodygroupController()
            if CLIENT
                @GetRenderController()
                @GetWeightController()
            @Reset()
    GenericDataChange: (state) =>
        if state\GetKey() == 'Entity' and IsValid(@GetEntity())
            @SetupEntity(@GetEntity())

        if state\GetKey() == 'Fly' and @flightController
            @flightController\Switch(state\GetValue())

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

        if state\GetKey() == 'DisableTask'
            @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

        @GetBodygroupController()\DataChanges(state) if @ent and @GetBodygroupController()

        if CLIENT and @ent
            @GetWeightController()\DataChanges(state) if @GetWeightController()
            @GetRenderController()\DataChanges(state) if @GetRenderController()

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

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

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

        if USE_NEW_HULL\GetBool() or @ent.__ppm2_modified_hull
            @ent\ResetHull() if @ent.ResetHull
            @ent\SetStepSize(@@STEP_SIZE) if @ent.SetStepSize
            @ent.__ppm2_modified_hull = false

            if SERVER and @ent.SetJumpPower and @ent.__ppm2_modified_jump
                @ent\SetJumpPower(@ent\GetJumpPower() / PPM2.PONY_JUMP_MODIFIER)
                @ent.__ppm2_modified_jump = false

        @ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL) if @ent.SetViewOffset
        @ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL) if @ent.SetViewOffsetDucked

        if CLIENT
            mat = Matrix()
            mat\Scale(@@DEF_SCALE)
            @ent\EnableMatrix('RenderMultiply', mat)
    ModifyScale: =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        size = @GetPonySize()

        if USE_NEW_HULL\GetBool()
            @ent.__ppm2_modified_hull = true
            @ent\SetHull(@@HULL_MINS * size, @@HULL_MAXS * size) if @ent.SetHull
            @ent\SetHullDuck(@@HULL_MINS * size, @@HULL_MAXS_DUCK * size) if @ent.SetHullDuck
            @ent\SetStepSize(@@STEP_SIZE * size) if @ent.SetStepSize

            if SERVER and @ent.SetJumpPower and not @ent.__ppm2_modified_jump
                @ent\SetJumpPower(@ent\GetJumpPower() * PPM2.PONY_JUMP_MODIFIER)
                @ent.__ppm2_modified_jump = true

        @ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET * size) if @ent.SetViewOffset
        @ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK * size) if @ent.SetViewOffsetDucked

        if CLIENT
            mat = Matrix()
            mat\Scale(@@DEF_SCALE * size)
            @ent\EnableMatrix('RenderMultiply', mat)
    Reset: =>
        @ResetScale()
        @ModifyScale()
        if CLIENT
            @GetWeightController()\Reset() if @GetWeightController().Reset
            @GetRenderController()\Reset() if @GetRenderController().Reset
            @GetBodygroupController()\Reset() if @GetBodygroupController().Reset
    PlayerRespawn: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledRespawn
            @alreadyCalledRespawn = true
            @alreadyCalledDeath = true
        else
            @alreadyCalledRespawn = false
            @alreadyCalledDeath = false
        @ApplyBodygroups(CLIENT)
        @SetFly(false) if SERVER

        @ResetScale()
        @ModifyScale()

        if CLIENT
            @deathRagdollMerged = false
            @GetWeightController()\UpdateWeight() if @GetWeightController()
            @GetRenderController()\PlayerRespawn() if @GetRenderController()
            @GetBodygroupController()\MergeModels(@ent) if IsValid(@ent) and @GetBodygroupController().MergeModels

    PlayerDeath: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledDeath
            @alreadyCalledDeath = true
        else
            @alreadyCalledDeath = false
        @SetFly(false) if SERVER
        @ResetScale()
        if CLIENT
            @DoRagdollMerge()
            @GetRenderController()\PlayerDeath() if @GetRenderController()

    DoRagdollMerge: =>
        return if @deathRagdollMerged
        bgController = @GetBodygroupController()
        rag = @ent\GetRagdollEntity()
        if not bgController.MergeModels
            @deathRagdollMerged = true
        elseif IsValid(rag)
            @deathRagdollMerged = true
            bgController\MergeModels(rag)

    ApplyBodygroups: (updateModels = CLIENT) => @GetBodygroupController()\ApplyBodygroups(updateModels) if @ent
    SetLocalChange: (state) => @GenericDataChange(state)
    NetworkDataChanges: (state) => @GenericDataChange(state)
    SlowUpdate: => @GetBodygroupController()\SlowUpdate() if @GetBodygroupController()

    GetFlightController: => @flightController
    GetRenderController: =>
        return if SERVER
        return @renderController if not @isValid
        if not @renderController or @modelCached ~= @modelRender
            @modelRender = @modelCached
            cls = PPM2.GetRenderController(@modelCached)
            if @renderController and cls == @renderController.__class
                @renderController.ent = @ent
                PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
                return @renderController
            @renderController\Remove() if @renderController
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    GetWeightController: =>
        return if SERVER
        return @weightController if not @isValid
        if not @weightController or @modelCached ~= @modelWeight
            @modelCached = @modelCached or @ent\GetModel()
            @modelWeight = @modelCached
            cls = PPM2.GetPonyWeightController(@modelCached)
            if @weightController and cls == @weightController.__class
                @weightController.ent = @ent
                PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
                return @weightController
            @weightController\Remove() if @weightController
            @weightController = cls(@)
        @weightController.ent = @ent
        return @weightController
    GetBodygroupController: =>
        return @bodygroups if not @isValid
        if not @bodygroups or @modelBodygroups ~= @modelCached
            @modelCached = @modelCached or @ent\GetModel()
            @modelBodygroups = @modelCached
            cls = PPM2.GetBodygroupController(@modelCached)
            if @bodygroups and cls == @bodygroups.__class
                @bodygroups.ent = @ent
                PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
                return @bodygroups
            @bodygroups\Remove() if @bodygroups
            @bodygroups = cls(@)
        @bodygroups.ent = @ent
        return @bodygroups
    Remove: (byClient = false) =>
        @@NW_Objects[@netID] = nil if @NETWORKED
        @isValid = false
        @ent = @GetEntity() if not IsValid(@ent)
        @ent.__PPM2_PonyData = nil if IsValid(@ent) and @ent.__PPM2_PonyData == @
        if CLIENT
            @GetWeightController()\Remove() if @GetWeightController()
            @GetRenderController()\Remove() if @GetRenderController()
            if IsValid(@ent) and @ent.__ppm2_task_hit
                @ent.__ppm2_task_hit = false
                @ent\SetNoDraw(false)
        @ResetScale() if IsValid(@ent)
        @GetBodygroupController()\Remove() if @GetBodygroupController()
        @flightController\Switch(false) if @flightController
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    __tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

PPM2.NetworkedPonyData = NetworkedPonyData

if CLIENT
    net.Receive 'PPM2.NotifyDisconnect', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

    net.Receive 'PPM2.PonyDataRemove', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

	hook.Add 'NetworkEntityCreated', 'PPM2.NetworkedObjectCheck', =>
		return if @GetPonyData()
		--for i, obj in pairs NetworkedPonyData.NW_Objects
        --    obj.ent =

entMeta = FindMetaTable('Entity')
entMeta.GetPonyData = =>
    if @__PPM2_PonyData and StrongEntity(@__PPM2_PonyData\GetEntity()) ~= StrongEntity(@)
        @__PPM2_PonyData\SetEntity(@)
        @__PPM2_PonyData\SetupEntity(@) if CLIENT
    return @__PPM2_PonyData

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

for ply in *player.GetAll()
    ply.__PPM2_PonyData\Remove() if ply.__PPM2_PonyData

class NetworkedPonyData extends PPM2.NetworkedObject
    @NW_ClientsideCreation = true
    @RenderTasks = {}

    @Setup()
    @NetworkVar('Entity',           net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), ((newValue) => IsValid(@GetOwner()) and StrongEntity(@GetOwner()\EntIndex()) or newValue))
    @NetworkVar('UpperManeModel',   net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('LowerManeModel',   net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('TailModel',        net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)
    @NetworkVar('SocksModel',       net.ReadStrongEntity, net.WriteStrongEntity, StrongEntity(-1), nil, false)

    @NetworkVar('Race',             (-> math.Clamp(net.ReadUInt(4), 0, 3)), ((arg = PPM2.RACE_EARTH) -> net.WriteUInt(arg, 4)), PPM2.RACE_EARTH)
    @NetworkVar('Gender',           (-> math.Clamp(net.ReadUInt(4), 0, 1)), ((arg = PPM2.GENDER_FEMALE) -> net.WriteUInt(arg, 4)), PPM2.GENDER_FEMALE)
    @NetworkVar('Weight',           (-> math.Clamp(net.ReadFloat(), PPM2.MIN_WEIGHT, PPM2.MAX_WEIGHT)), net.WriteFloat, 1)
    @NetworkVar('PonySize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_SCALE, PPM2.MAX_SCALE)),   net.WriteFloat, 1)

    -- Reserved - they can be accessed/used/changed, but they do not do anything
    @NetworkVar('Age',              (-> math.Clamp(net.ReadUInt(4), 0, 2)), ((arg = PPM2.AGE_ADULT) -> net.WriteUInt(arg, 4)), PPM2.AGE_ADULT)

    @NetworkVar('EyelashType',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_EYELASHES, PPM2.MAX_EYELASHES)),           ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('TailType',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_TAILS, PPM2.MAX_TAILS)),                   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeType',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_UPPER_MANES, PPM2.MAX_UPPER_MANES)),       ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeLower',    (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_LOWER_MANES, PPM2.MAX_LOWER_MANES)),       ((arg = 0) -> net.WriteUInt(arg, 8)), 0)

    @NetworkVar('TailTypeNew',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_TAILS_NEW, PPM2.MAX_TAILS_NEW)),               ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeNew',      (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_UPPER_MANES_NEW, PPM2.MAX_UPPER_MANES_NEW)),   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('ManeTypeLowerNew', (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_LOWER_MANES_NEW, PPM2.MAX_LOWER_MANES_NEW)),   ((arg = 0) -> net.WriteUInt(arg, 8)), 0)

    @NetworkVar('BodyColor',        net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))

    @NetworkVar('SeparateEyes',     net.ReadBool, net.WriteBool,              false)
    for publicName in *{'', 'Left', 'Right'}
        @NetworkVar("EyeType#{publicName}",          (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_EYE_TYPE, PPM2.MAX_EYE_TYPE)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
        @NetworkVar("EyeBackground#{publicName}",    net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeHole#{publicName}",          net.ReadColor, net.WriteColor, 	    Color(0,   0,   0  ))
        @NetworkVar("EyeIrisTop#{publicName}",       net.ReadColor, net.WriteColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisBottom#{publicName}",    net.ReadColor, net.WriteColor, 	    Color(200, 200, 200))
        @NetworkVar("EyeIrisLine1#{publicName}",     net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeIrisLine2#{publicName}",     net.ReadColor, net.WriteColor, 	    Color(255, 255, 255))
        @NetworkVar("EyeReflection#{publicName}",    net.ReadColor, net.WriteColor,    Color(255, 255, 255, 127))
        @NetworkVar("EyeEffect#{publicName}",        net.ReadColor, net.WriteColor,         Color(255, 255, 255))
        @NetworkVar("EyeLines#{publicName}",         net.ReadBool, net.WriteBool,                           true)
        @NetworkVar("DerpEyes#{publicName}",         net.ReadBool, net.WriteBool,                          false)
        @NetworkVar("DerpEyesStrength#{publicName}", (-> math.Clamp(net.ReadFloat(), PPM2.MIN_DERP_STRENGTH, PPM2.MAX_DERP_STRENGTH)), net.WriteFloat, 1)
        @NetworkVar("HoleWidth#{publicName}",        (-> math.Clamp(net.ReadFloat(), PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE)), net.WriteFloat,  1)
        @NetworkVar("HoleHeight#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_PUPIL_SIZE, PPM2.MAX_PUPIL_SIZE)), net.WriteFloat,  1)
        @NetworkVar("HoleSize#{publicName}",         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE, PPM2.MAX_HOLE)),             net.WriteFloat, .8)
        @NetworkVar("HoleShiftX#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT)), net.WriteFloat, 0)
        @NetworkVar("HoleShiftY#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_HOLE_SHIFT, PPM2.MAX_HOLE_SHIFT)), net.WriteFloat, 0)
        @NetworkVar("IrisSize#{publicName}",         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, .8)
        @NetworkVar("IrisWidth#{publicName}",        (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, 1)
        @NetworkVar("IrisHeight#{publicName}",       (-> math.Clamp(net.ReadFloat(), PPM2.MIN_IRIS, PPM2.MAX_IRIS)),             net.WriteFloat, 1)
        @NetworkVar("EyeRotation#{publicName}",      (-> math.Clamp(net.ReadInt(12), PPM2.MIN_EYE_ROTATION, PPM2.MAX_EYE_ROTATION)), ((arg = 0) -> net.WriteInt(arg, 12)), 1)
        @NetworkVar("EyeURL#{publicName}",           net.ReadString,                                                            net.WriteString, '')

    @NetworkVar('SeparateMane',     net.ReadBool, net.WriteBool,              false)
    for i = 1, 6
        @NetworkVar("ManeColor#{i}",            net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeDetailColor#{i}",      net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeURLColor#{i}",         net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("ManeURL#{i}",              net.ReadString, net.WriteString,   '')

        @NetworkVar("TailColor#{i}",            net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailDetailColor#{i}",      net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailURLColor#{i}",         net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("TailURL#{i}",              net.ReadString, net.WriteString,   '')

        @NetworkVar("LowerManeColor#{i}",       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeURL#{i}",         net.ReadString, net.WriteString,   '')
        @NetworkVar("LowerManeURLColor#{i}",    net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("LowerManeDetailColor#{i}", net.ReadColor, net.WriteColor,     Color(255, 255, 255))

        @NetworkVar("UpperManeColor#{i}",       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURL#{i}",         net.ReadString, net.WriteString,   '')
        @NetworkVar("UpperManeDetailColor#{i}", net.ReadColor, net.WriteColor,     Color(255, 255, 255))
        @NetworkVar("UpperManeURLColor#{i}",    net.ReadColor, net.WriteColor,     Color(255, 255, 255))

    @NetworkVar('CMark',            net.ReadBool, net.WriteBool,              true)
    @NetworkVar('CMarkSize',        (-> math.Clamp(net.ReadFloat(), 0, 1)), net.WriteFloat, 1)
    @NetworkVar('CMarkColor',       net.ReadColor, net.WriteColor,     Color(255, 255, 255))
    @NetworkVar('CMarkURL',         net.ReadString, net.WriteString,            '')
    @NetworkVar('CMarkType',        (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_CMARK, PPM2.MAX_CMARK)),           ((arg = 4) -> net.WriteUInt(arg, 8)), 4)
    @NetworkVar('TailSize',         (-> math.Clamp(net.ReadFloat(), PPM2.MIN_TAIL_SIZE, PPM2.MAX_TAIL_SIZE)),   net.WriteFloat, 1)

    @NetworkVar('Bodysuit',         (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_SUIT, PPM2.MAX_SUIT)),             ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('Socks',            net.ReadBool, net.WriteBool,              false)
    @NetworkVar('NoFlex',           net.ReadBool, net.WriteBool,              false)

    @NetworkVar('SocksAsModel',     net.ReadBool, net.WriteBool,              false)
    @NetworkVar('SocksTexture',     (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_SOCKS, PPM2.MAX_SOCKS)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('SocksColor',       net.ReadColor, net.WriteColor,            Color(255, 255, 255))
    @NetworkVar('SocksTextureURL',  net.ReadString, net.WriteString,          '')

    for i = 1, 6
        @NetworkVar('SocksDetailColor' .. i, net.ReadColor, net.WriteColor, Color(255, 255, 255))

    @NetworkVar('BatPonyEars',      net.ReadBool, net.WriteBool,              false)
    @NetworkVar('Fangs',            net.ReadBool, net.WriteBool,              false)
    @NetworkVar('ClawTeeth',        net.ReadBool, net.WriteBool,              false)

    @NetworkVar('TeethColor',       net.ReadColor, net.WriteColor,    Color(255, 255, 255))
    @NetworkVar('MouthColor',       net.ReadColor, net.WriteColor,    Color(219, 65, 155))
    @NetworkVar('TongueColor',      net.ReadColor, net.WriteColor,    Color(235, 131, 59))

    for {:flex, :active} in *PPM2.PonyFlexController.FLEX_LIST
        @NetworkVar("DisableFlex#{flex}", net.ReadBool, net.WriteBool, false) if active

    for i = 1, PPM2.MAX_BODY_DETAILS
        @NetworkVar("BodyDetail#{i}",       (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_DETAIL, PPM2.MAX_DETAIL)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
        @NetworkVar("BodyDetailColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("BodyDetailURL#{i}",    net.ReadString, net.WriteString, '')
        @NetworkVar("BodyDetailURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("HornURL#{i}",       net.ReadString, net.WriteString, '')
        @NetworkVar("WingsURL#{i}",      net.ReadString, net.WriteString, '')
        @NetworkVar("HornURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("WingsURLColor#{i}", net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    @NetworkVar('Fly',                  net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('DisableTask',          net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('UseFlexLerp',          net.ReadBool,   net.WriteBool,                  true)
    @NetworkVar('FlexLerpMultiplier',   net.ReadFloat,  net.WriteFloat,                    1)
    @NetworkVar('NewMuzzle',            net.ReadBool,   net.WriteBool,                  true)
    @NetworkVar('SeparateWings',        net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('SeparateHorn',         net.ReadBool,   net.WriteBool,                 false)
    @NetworkVar('WingsColor',           net.ReadColor,  net.WriteColor,  Color(255, 255, 255))
    @NetworkVar('HornColor',            net.ReadColor,  net.WriteColor,  Color(255, 255, 255))

    @NetworkVar('WingsType',            (-> math.Clamp(net.ReadUInt(8), PPM2.MIN_WINGS, PPM2.MAX_WINGS)), ((arg = 0) -> net.WriteUInt(arg, 8)), 0)
    @NetworkVar('MaleBuff',             (-> math.Clamp(net.ReadFloat(), PPM2.MIN_MALE_BUFF, PPM2.MAX_MALE_BUFF)), net.WriteFloat, PPM2.DEFAULT_MALE_BUFF)

    @NetworkVar('BatWingColor',         net.ReadColor, net.WriteColor,    Color(255, 255, 255))
    @NetworkVar('BatWingSkinColor',     net.ReadColor, net.WriteColor,    Color(255, 255, 255))

    for i = 1, 3
        @NetworkVar("BatWingURL#{i}",           net.ReadString, net.WriteString, '')
        @NetworkVar("BatWingSkinURL#{i}",       net.ReadString, net.WriteString, '')
        @NetworkVar("BatWingURLColor#{i}",      net.ReadColor,  net.WriteColor, Color(255, 255, 255))
        @NetworkVar("BatWingSkinURLColor#{i}",  net.ReadColor,  net.WriteColor, Color(255, 255, 255))

    Clone: (target = @ent) =>
        copy = @@(nil, target)
        @ApplyDataToObject(copy)
        return copy

    new: (netID, ent) =>
        @recomputeTextures = true
        @isValid = true
        if ent
            @modelCached = ent\GetModel()
            @SetEntity(ent)
            @SetupEntity(ent)
        super(netID)
    IsValid: => @isValid
    GetModel: => @modelCached
    EntIndex: => @entID
    SetupEntity: (ent) =>
        if ent.__PPM2_PonyData
            return if ent.__PPM2_PonyData\GetOwner() ~= @GetOwner()
            ent.__PPM2_PonyData\Remove() if ent.__PPM2_PonyData.Remove and ent.__PPM2_PonyData ~= @
        ent.__PPM2_PonyData = @
        @ent = ent
        return unless IsValid(ent)
        @modelCached = ent\GetModel()
        @ent = ent
        @flightController = PPM2.PonyflyController(@)
        @entID = ent\EntIndex()
        @ModelChanges(@modelCached, @modelCached)
        @Reset()
        if CLIENT
            timer.Simple 0, ->
                @GetRenderController()\CompileTextures() if @GetRenderController()
        PPM2.DebugPrint('Ponydata ', @, ' was updated to use for ', @ent)
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    ModelChanges: (old = @ent\GetModel(), new = old) =>
        @modelCached = new
        @SetFly(false) if SERVER
        timer.Simple 0.5, ->
            return unless IsValid(@ent)
            @GetBodygroupController()\ApplyBodygroups(CLIENT) if @GetBodygroupController()
            if CLIENT
                @GetRenderController()
                @GetWeightController()
            @Reset()
    GenericDataChange: (state) =>
        if state\GetKey() == 'Entity' and IsValid(@GetEntity())
            @SetupEntity(@GetEntity())

        if state\GetKey() == 'Fly' and @flightController
            @flightController\Switch(state\GetValue())

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

        if state\GetKey() == 'DisableTask'
            @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

        @GetBodygroupController()\DataChanges(state) if @ent and @GetBodygroupController()

        if CLIENT and @ent
            @GetWeightController()\DataChanges(state) if @GetWeightController()
            @GetRenderController()\DataChanges(state) if @GetRenderController()

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

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

    ResetScale: =>
        return if not @ent
        @ent\ResetHull() if @ent.ResetHull
        @ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL) if @ent.SetViewOffset
        @ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL) if @ent.SetViewOffsetDucked
        @ent\SetStepSize(@@STEP_SIZE) if @ent.SetStepSize

        if SERVER and @ent.SetJumpPower and @ent.__ppm2_modified_jump
            @ent\SetJumpPower(@ent\GetJumpPower() / PPM2.PONY_JUMP_MODIFIER)
            @ent.__ppm2_modified_jump = false

        if CLIENT
            mat = Matrix()
            mat\Scale(@@DEF_SCALE)
            @ent\EnableMatrix('RenderMultiply', mat)
    ModifyScale: =>
        return if not @ent
        return if not @ent\IsPony()
        size = @GetPonySize()
        @ent\SetHull(@@HULL_MINS * size, @@HULL_MAXS * size) if @ent.SetHull
        @ent\SetHullDuck(@@HULL_MINS * size, @@HULL_MAXS_DUCK * size) if @ent.SetHullDuck
        @ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET * size) if @ent.SetViewOffset
        @ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK * size) if @ent.SetViewOffsetDucked
        @ent\SetStepSize(@@STEP_SIZE * size) if @ent.SetStepSize

        if SERVER and @ent.SetJumpPower and not @ent.__ppm2_modified_jump
            @ent\SetJumpPower(@ent\GetJumpPower() * PPM2.PONY_JUMP_MODIFIER)
            @ent.__ppm2_modified_jump = true

        if CLIENT
            mat = Matrix()
            mat\Scale(@@DEF_SCALE * size)
            @ent\EnableMatrix('RenderMultiply', mat)
    Reset: =>
        @ResetScale()
        @ModifyScale()
        if CLIENT
            @GetWeightController()\Reset() if @GetWeightController().Reset
            @GetRenderController()\Reset() if @GetRenderController().Reset
            @GetBodygroupController()\Reset() if @GetBodygroupController().Reset
    PlayerRespawn: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledRespawn
            @alreadyCalledRespawn = true
            @alreadyCalledDeath = true
        else
            @alreadyCalledRespawn = false
            @alreadyCalledDeath = false
        @ApplyBodygroups(CLIENT)
        @SetFly(false) if SERVER

        @ResetScale()
        @ModifyScale()

        if CLIENT
            @deathRagdollMerged = false
            @GetWeightController()\UpdateWeight() if @GetWeightController()
            @GetRenderController()\PlayerRespawn() if @GetRenderController()
            @GetBodygroupController()\MergeModels(@ent) if IsValid(@ent) and @GetBodygroupController().MergeModels

    PlayerDeath: =>
        return if not IsValid(@ent)
        @ent.__cachedIsPony = @ent\IsPony()
        if not @ent.__cachedIsPony
            return if @alreadyCalledDeath
            @alreadyCalledDeath = true
        else
            @alreadyCalledDeath = false
        @SetFly(false) if SERVER
        @ResetScale()
        if CLIENT
            @DoRagdollMerge()
            @GetRenderController()\PlayerDeath() if @GetRenderController()

    DoRagdollMerge: =>
        return if @deathRagdollMerged
        bgController = @GetBodygroupController()
        rag = @ent\GetRagdollEntity()
        if not bgController.MergeModels
            @deathRagdollMerged = true
        elseif IsValid(rag)
            @deathRagdollMerged = true
            bgController\MergeModels(rag)

    ApplyBodygroups: (updateModels = CLIENT) => @GetBodygroupController()\ApplyBodygroups(updateModels) if @ent
    SetLocalChange: (state) => @GenericDataChange(state)
    NetworkDataChanges: (state) => @GenericDataChange(state)
    SlowUpdate: => @GetBodygroupController()\SlowUpdate() if @GetBodygroupController()

    GetFlightController: => @flightController
    GetRenderController: =>
        return if SERVER
        return @renderController if not @isValid
        if not @renderController or @modelCached ~= @modelRender
            @modelRender = @modelCached
            cls = PPM2.GetRenderController(@modelCached)
            if @renderController and cls == @renderController.__class
                @renderController.ent = @ent
                PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
                return @renderController
            @renderController\Remove() if @renderController
            @renderController = cls(@)
        @renderController.ent = @ent
        return @renderController
    GetWeightController: =>
        return if SERVER
        return @weightController if not @isValid
        if not @weightController or @modelCached ~= @modelWeight
            @modelCached = @modelCached or @ent\GetModel()
            @modelWeight = @modelCached
            cls = PPM2.GetPonyWeightController(@modelCached)
            if @weightController and cls == @weightController.__class
                @weightController.ent = @ent
                PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
                return @weightController
            @weightController\Remove() if @weightController
            @weightController = cls(@)
        @weightController.ent = @ent
        return @weightController
    GetBodygroupController: =>
        return @bodygroups if not @isValid
        if not @bodygroups or @modelBodygroups ~= @modelCached
            @modelCached = @modelCached or @ent\GetModel()
            @modelBodygroups = @modelCached
            cls = PPM2.GetBodygroupController(@modelCached)
            if @bodygroups and cls == @bodygroups.__class
                @bodygroups.ent = @ent
                PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
                return @bodygroups
            @bodygroups\Remove() if @bodygroups
            @bodygroups = cls(@)
        @bodygroups.ent = @ent
        return @bodygroups
    Remove: (byClient = false) =>
        @@NW_Objects[@netID] = nil if @NETWORKED
        @isValid = false
        @ent = @GetEntity() if not IsValid(@ent)
        @ent.__PPM2_PonyData = nil if IsValid(@ent) and @ent.__PPM2_PonyData == @
        if CLIENT
            @GetWeightController()\Remove() if @GetWeightController()
            @GetRenderController()\Remove() if @GetRenderController()
            if IsValid(@ent) and @ent.__ppm2_task_hit
                @ent.__ppm2_task_hit = false
                @ent\SetNoDraw(false)
        @ResetScale() if IsValid(@ent)
        @GetBodygroupController()\Remove() if @GetBodygroupController()
        @flightController\Switch(false) if @flightController
        @@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]
    __tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

PPM2.NetworkedPonyData = NetworkedPonyData

if CLIENT
    net.Receive 'PPM2.NotifyDisconnect', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

    net.Receive 'PPM2.PonyDataRemove', ->
        netID = net.ReadUInt(16)
        data = NetworkedPonyData.NW_Objects[netID]
        return if not data
        data\Remove()

	hook.Add 'NetworkEntityCreated', 'PPM2.NetworkedObjectCheck', =>
		return if @GetPonyData()
		--for i, obj in pairs NetworkedPonyData.NW_Objects
        --    obj.ent =

entMeta = FindMetaTable('Entity')
entMeta.GetPonyData = =>
    if @__PPM2_PonyData and StrongEntity(@__PPM2_PonyData\GetEntity()) ~= StrongEntity(@)
        @__PPM2_PonyData\SetEntity(@)
        @__PPM2_PonyData\SetupEntity(@) if CLIENT
    return @__PPM2_PonyData


Don't slowupdate rendertasks which are dormant

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

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

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

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

DLib.nw.PoolBoolean('PPM2.InEditor', false)

do
	import GetModel, IsDormant, GetPonyData, IsValid from FindMetaTable('Entity')
	callback = ->
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply)
				model = GetModel(ply)
				ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
				if ply.__ppm2_lastmodel ~= model
					data = GetPonyData(ply)
					if data and data.ModelChanges
						oldModel = ply.__ppm2_lastmodel
						ply.__ppm2_lastmodel = model
						data\ModelChanges(oldModel, model)

		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ply = task.ent
			if IsValid(ply) and not IsDormant(ply)
				model = GetModel(ply)
				ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
				if ply.__ppm2_lastmodel ~= model
					data = GetPonyData(ply)
					if data and data.ModelChanges
						oldModel = ply.__ppm2_lastmodel
						ply.__ppm2_lastmodel = model
						data\ModelChanges(oldModel, model)
	timer.Create 'PPM2.ModelWatchdog', 1, 0, ->
		status, err = pcall callback
		print('PPM2 Error: ' .. err) if not status

do
	import GetModel, IsDormant, GetPonyData, IsValid, IsPonyCached from FindMetaTable('Entity')

	hook.Add 'Think', 'PPM2.PonyDataThink', ->
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply) and IsPonyCached(ply)
				data = GetPonyData(ply)
				data\Think() if data and data.Think
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ply = task.ent
			if IsValid(ply) and not IsDormant(ply) and IsPonyCached(ply) and task.Think
				task\Think()

	hook.Add 'RenderScreenspaceEffects', 'PPM2.PonyDataRenderScreenspaceEffects', ->
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply) and IsPonyCached(ply)
				data = GetPonyData(ply)
				data\RenderScreenspaceEffects() if data and data.RenderScreenspaceEffects
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ply = task.ent
			if IsValid(ply) and not IsDormant(ply) and IsPonyCached(ply) and task.RenderScreenspaceEffects
				task\RenderScreenspaceEffects()

do
	catchError = (err) ->
		PPM2.Message 'Slow Update Error: ', err
		PPM2.Message debug.traceback()

	import Alive from FindMetaTable('Player')
	import IsPonyCached, IsDormant, GetPonyData from FindMetaTable('Entity')
	timer.Create 'PPM2.SlowUpdate', CLIENT and 0.5 or 5, 0, ->
		for ply in *player.GetAll()
			if not IsDormant(ply) and Alive(ply) and IsPonyCached(ply) and GetPonyData(ply)
				data = GetPonyData(ply)
				xpcall(data.SlowUpdate, catchError, data, CLIENT) if data.SlowUpdate

		for task in *PPM2.NetworkedPonyData.RenderTasks
			if IsValid(task.ent) and not IsDormant(task.ent) and task.ent\IsPony()
				xpcall(task.SlowUpdate, catchError, task, CLIENT) if task.SlowUpdate

ENABLE_TOOLGUN = CreateConVar('ppm2_sv_ragdoll_toolgun', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow toolgun usage on player death ragdolls')
ENABLE_PHYSGUN = CreateConVar('ppm2_sv_ragdoll_physgun', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow physgun usage on player death ragdolls')

hook.Add 'CanTool', 'PPM2.DeathRagdoll', (ply = NULL, tr = {Entity: NULL}, tool = '') -> false if IsValid(tr.Entity) and tr.Entity\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_TOOLGUN\GetBool() and tool ~= 'rope'
hook.Add 'PhysgunPickup', 'PPM2.DeathRagdoll', (ply = NULL, ent = NULL) -> false if IsValid(ent) and ent\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_PHYSGUN\GetBool()
hook.Add 'CanProperty', 'PPM2.DeathRagdoll', (ply = NULL, mode = '', ent = NULL) -> false if IsValid(ent) and ent\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_TOOLGUN\GetBool()
hook.Add 'CanDrive', 'PPM2.DeathRagdoll', (ply = NULL, ent = NULL) -> false if IsValid(ent) and ent\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_TOOLGUN\GetBool()

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

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

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

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

DLib.nw.PoolBoolean('PPM2.InEditor', false)

do
	import GetModel, IsDormant, GetPonyData, IsValid from FindMetaTable('Entity')
	callback = ->
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply)
				model = GetModel(ply)
				ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
				if ply.__ppm2_lastmodel ~= model
					data = GetPonyData(ply)
					if data and data.ModelChanges
						oldModel = ply.__ppm2_lastmodel
						ply.__ppm2_lastmodel = model
						data\ModelChanges(oldModel, model)

		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ply = task.ent
			if IsValid(ply) and not IsDormant(ply)
				model = GetModel(ply)
				ply.__ppm2_lastmodel = ply.__ppm2_lastmodel or model
				if ply.__ppm2_lastmodel ~= model
					data = GetPonyData(ply)
					if data and data.ModelChanges
						oldModel = ply.__ppm2_lastmodel
						ply.__ppm2_lastmodel = model
						data\ModelChanges(oldModel, model)
	timer.Create 'PPM2.ModelWatchdog', 1, 0, ->
		status, err = pcall callback
		print('PPM2 Error: ' .. err) if not status

do
	import GetModel, IsDormant, GetPonyData, IsValid, IsPonyCached from FindMetaTable('Entity')

	hook.Add 'Think', 'PPM2.PonyDataThink', ->
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply) and IsPonyCached(ply)
				data = GetPonyData(ply)
				data\Think() if data and data.Think
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ply = task.ent
			if IsValid(ply) and not IsDormant(ply) and IsPonyCached(ply) and task.Think
				task\Think()

	hook.Add 'RenderScreenspaceEffects', 'PPM2.PonyDataRenderScreenspaceEffects', ->
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply) and IsPonyCached(ply)
				data = GetPonyData(ply)
				data\RenderScreenspaceEffects() if data and data.RenderScreenspaceEffects
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			ply = task.ent
			if IsValid(ply) and not IsDormant(ply) and IsPonyCached(ply) and task.RenderScreenspaceEffects
				task\RenderScreenspaceEffects()

do
	catchError = (err) ->
		PPM2.Message 'Slow Update Error: ', err
		PPM2.Message debug.traceback()

	import Alive from FindMetaTable('Player')
	import IsPonyCached, IsDormant, GetPonyData from FindMetaTable('Entity')
	timer.Create 'PPM2.SlowUpdate', CLIENT and 0.5 or 5, 0, ->
		for _, ply in ipairs player.GetAll()
			if not IsDormant(ply) and Alive(ply) and IsPonyCached(ply) and GetPonyData(ply)
				data = GetPonyData(ply)
				xpcall(data.SlowUpdate, catchError, data, CLIENT) if data.SlowUpdate
		for _, task in ipairs PPM2.NetworkedPonyData.RenderTasks
			if IsValid(task.ent) and task.ent\IsPony()
				xpcall(task.SlowUpdate, catchError, task, CLIENT) if task.SlowUpdate

ENABLE_TOOLGUN = CreateConVar('ppm2_sv_ragdoll_toolgun', '0', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow toolgun usage on player death ragdolls')
ENABLE_PHYSGUN = CreateConVar('ppm2_sv_ragdoll_physgun', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'Allow physgun usage on player death ragdolls')

hook.Add 'CanTool', 'PPM2.DeathRagdoll', (ply = NULL, tr = {Entity: NULL}, tool = '') -> false if IsValid(tr.Entity) and tr.Entity\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_TOOLGUN\GetBool() and tool ~= 'rope'
hook.Add 'PhysgunPickup', 'PPM2.DeathRagdoll', (ply = NULL, ent = NULL) -> false if IsValid(ent) and ent\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_PHYSGUN\GetBool()
hook.Add 'CanProperty', 'PPM2.DeathRagdoll', (ply = NULL, mode = '', ent = NULL) -> false if IsValid(ent) and ent\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_TOOLGUN\GetBool()
hook.Add 'CanDrive', 'PPM2.DeathRagdoll', (ply = NULL, ent = NULL) -> false if IsValid(ent) and ent\GetNWBool('PPM2.IsDeathRagdoll') and not ENABLE_TOOLGUN\GetBool()


sun/assembler/init: begin dumping function

-- vim:set noet sts=0 sw=3 ts=3:

util = require "sun.assembler.util"

resume = (sink, value)-> coroutine.resume sink, type(value) == "string" or string.byte(value)

header = (sink)->
	resume = (sink, value)-> coroutine.resume sink, type(value) == "string" or string.byte(value)
	-- first bytes of header
	resume sink, "\27Lua"
	resume sink, 0x53
	resume sink, util.grab_luac_data!
	-- size checks
	sizes = util.check_sizes!
	resume sink, sizes.int
	resume sink, sizes.size_t
	resume sink, sizes.Instruction
	resume sink, sizes.lua_Integer
	resume sink, sizes.lua_Number
	-- LUAC_INT and LUAC_NUM
	resume sink, util.check_luac_int!
	resume sink, util.check_luac_num!

function_header = (sink, tree)->
	--[[ documentation for `tree` argument:
	-- `.source` source name (converted using convert_literal_string())
	-- `.line` line defined (0 for file master function)
	-- `.lastline` last line defined (0 for file master function)
	-- `.parameters` number of parameters defined
	-- `.is_vararg` byte saying if is a vararg function
	-- `.maxstacksize` number of registers used
	-- `.instructions` list of instructions to be assembled
	-- `.constants` list of constants to be compiled
	-- `.upvalues` list of upvalues
	-- `.prototypes` list of prototypes
	-- `.debug` {
	--   `.lineinfo` first and last line as two bytes
	--   `.localvars` local variables
	--   `.upvalues` list of upvalues
	-- }
	--]]
	resume sink, util.convert_literal_string assert(tree.source)
	resume sink, util.convert_int(tree.line)
	resume sink, util.convert_int(tree.lastline)
	resume sink, tree.parameters
	resume sink, tree.is_vararg -- 0 is none, 1 means uses '...' in function, 2
	                            -- is set for vararg functions
	resume sink, tree.maxstacksize
	-- Instructions
	resume sink, util.convert_int(#tree.instructions)
	--[[ Instruction bit-by-bit guide
	-- ::Two-argument instruction::
	-- 000000 00000000 000000000000000000
	-- |      |        |
	-- ^ instruction   |
	--        ^ first argument
	--                 ^ second argument
	-- ::Three-argument instruction::
	-- 000000 00000000 000000000 000000000
	-- |      |        |         |
	-- ^ instruction   |         |
	--        ^ first argument   |
	--                 ^ second argument
	--                           ^ third argument
	--]]
	--for instruction in *tree.instructions

-- vim:set noet sts=0 sw=3 ts=3:

util = require "sun.assembler.util"

header = (sink)->
	resume = (sink, value)-> coroutine.resume sink, type(value) == "string" or string.byte(value)
	-- first bytes of header
	resume sink, "\27Lua"
	resume sink, 0x53
	resume sink, util.grab_luac_data!
	-- size checks
	sizes = util.check_sizes!
	resume sink, sizes.int
	resume sink, sizes.size_t
	resume sink, sizes.Instruction
	resume sink, sizes.lua_Integer
	resume sink, sizes.lua_Number
	-- LUAC_INT and LUAC_NUM
	resume sink, util.check_luac_int!
	resume sink, util.check_luac_num!

function_header = (sink, tree)->
	--[[ documentation for `tree` argument:
	-- `.source` source name (converted using convert_literal_string())
	-- `.line` line defined (0 for file master function)
	-- `.lastline` last line defined (0 for file master function)
	-- `.parameters` list of parameters defined
	-- `.is_vararg` byte saying if is a vararg function
	-- `.maxstacksize` number of registers used
	-- `.instructions` list of instructions to be assembled
	-- `.constants` list of constants to be compiled
	-- `.upvalues` list of upvalues
	-- `.prototypes` list of prototypes
	-- `.debug` {
	--   `.lineinfo` first and last line as two bytes
	--   `.localvars` local variables
	--   `.upvalues` list of upvalues
	-- }
	--]]


Fix Javascript display in layout template

html = require "lapis.html"
import to_json from require "lapis.util"

class DefaultLayout extends html.Widget
    content: =>
        js = (require 'views.javascript')!.js
        print to_json(js)
        html_5 ->
            head ->
                meta charset: "UTF-8"

                -- This enables web-scale mode, right?
                meta name: "viewport", content:"initial-scale=1"

                link href: "/static/style.css", rel: "stylesheet", type: "text/css"
                link rel: "shortcut icon", href: "/favicon.ico?v=2"
                title (@m and @m.title) or "MG CS Week"

            body id: @page_id, ->
                @content_for "header"

                div id: "skrollr-body"
                main id: "content", ->
                    @content_for "inner"

                @content_for "footer"
                print to_json(js)
                for scrpt in *js
                    with scrpt
                        contents = .contents
                        .contents = nil
                        script scrpt, ->
                            raw contents

html = require "lapis.html"
import to_json from require "lapis.util"

class DefaultLayout extends html.Widget
    content: =>
        js = (require 'views.javascript')!.js
        print to_json(js)
        html_5 ->
            head ->
                meta charset: "UTF-8"

                -- This enables web-scale mode, right?
                meta name: "viewport", content:"initial-scale=1"

                link href: "/static/style.css", rel: "stylesheet", type: "text/css"
                link rel: "shortcut icon", href: "/favicon.ico?v=2"
                title (@m and @m.title) or "MG CS Week"

            body id: @page_id, ->
                @content_for "header"

                div id: "skrollr-body"
                main id: "content", ->
                    @content_for "inner"

                @content_for "footer"
                print to_json(js)
                for scrpt in *js
                    with scrpt
                        contents = .contents
                        .contents = nil
                        script scrpt, contents


charset: fix bug with iso. add ascii

charset: fix bug with iso. add ascii

iconv = require'iconv'

iso2utf = iconv.new('UTF-8'.."//TRANSLIT", 'ISO-8859-1')
utf2iso = iconv.new('ISO-8859-1'.."//TRANSLIT", 'UTF-8')

toUtf = (s) ->
  iso2utf\iconv(s)

toIso = (s) ->
  utf2iso\iconv(s)

latinpattern = "^#{toIso'æøå'}%??$"

PRIVMSG:
  '^æøå%??$': =>
    reply "UTF-8. Relax, you're doing fine."
  [latinpattern]: =>
    reply 'ISO-8859-1. Pls stahp.'
  '^Ã¦Ã¸Ã¥%??$': =>
    reply 'WTF8. Is very nice.'
  '^fxe%??$': =>
    reply 'fxe is Latin1 with 8th bit stripped'
  '^{%|}%??$': =>
    reply 'du sender NS-4551-1 (aka. ISO-646-NO).. og det er jo litt ut.'
  '^��?%??$': =>
    reply 'du sender replacement chars.'
  '^aeoeaa%?$': =>
    reply 'ASCII!'
  '^���?%??$': =>
    reply 'du sender replacement chars.'
  '^+AOYA+ADl?%??$': =>
    reply 'du sender UTF-7'
  '^%piconv (.+) (.+) (.+)$': (source, dest, frm, to, arg) =>
    convert = iconv.new("#{to\upper!}//TRANSLIT", frm\upper!)
    unless convert
      return say 'Invalid encoding given'
    nstr, err = convert\iconv(arg)
    if err
      return say err

    say nstr

iconv = require'iconv'

iso2utf = iconv.new('UTF-8'.."//TRANSLIT", 'ISO-8859-1')
utf2iso = iconv.new('ISO-8859-1'.."//TRANSLIT", 'UTF-8')

toUtf = (s) ->
  iso2utf\iconv(s)

toIso = (s) ->
  utf2iso\iconv(s)

latinpattern = "^#{toIso'æøå'}%??"

PRIVMSG:
  '^æøå%??$': =>
    reply "UTF-8. Relax, you're doing fine."
  latinpattern: =>
    reply 'ISO-8559-1. Pls stahp.'
  '^Ã¦Ã¸Ã¥%??$': =>
    reply 'WTF8. Is very nice.'
  '^fxe%??$': =>
    reply 'fxe is Latin1 with 8th bit stripped'
  '^{%|}%??$': =>
    reply 'du sender NS-4551-1 (aka. ISO-646-NO).. og det er jo litt ut.'
  '^��?%??$': =>
    reply 'du sender replacement chars.'
  '^���?%??$': =>
    reply 'du sender replacement chars.'
  '^+AOYA+ADl?%??$': =>
    reply 'du sender UTF-7'
  '^%piconv (.+) (.+) (.+)$': (source, dest, frm, to, arg) =>
    convert = iconv.new("#{to\upper!}//TRANSLIT", frm\upper!)
    unless convert
      return say 'Invalid encoding given'
    nstr, err = convert\iconv(arg)
    if err
      return say err

    say nstr


profile_edit

html = require "lapis.html"

class ProfileEdit extends html.Widget
  mkCheck:(name,txt,val)=>
    label for:"c_"..name, class:"pure-checkbox", ->
      input type:"checkbox", id:"c_"..name, :name, checked:(val and "checked" or nil)
      text txt
  content: =>
    form action:@url_for("profile_edit"), class: "pure-form pure-form-stacked", method:"post", ->
      with @profiledata
        fieldset ->
          legend "Birthday"
          input id:"birthday", name:"birthday", type:"date", value: .birthday or ""
          fieldset ->
            legend "Show publicly:"
            div class: "pure-control-group", ->
              @mkCheck "privacy_birthday_dm", "Date & Month", .privacy_birthday_dm
              @mkCheck "privacy_birthday_y", "Year", .privacy_birthday_y
              @mkCheck "privacy_birthday_age", "Age in years", .privacy_birthday_age
        fieldset ->
          legend "About me"
          textarea id:"about", name:"about", ->
            raw .about
          text "This field has "
          a target:"_blank", href:"https://daringfireball.net/projects/markdown/syntax", "markdown support"
          text " but you may not use HTML for security reasons."
      div class: "pure-controls", ->
        input type:"submit", class: "pure-button pure-button-primary"

html = require "lapis.html"

class ProfileEdit extends html.Widget
  mkCheck:(name,txt,val)=>
    label for:"c_"..name, class:"pure-checkbox", ->
      input type:"checkbox", id:"c_"..name, :name, checked:(val and "checked" or nil)
      text txt
  content: =>
    form action:@url_for("profile_edit"), class: "pure-form pure-form-stacked", method:"post", ->
      with @profiledata
        fieldset ->
          legend "Birthday"
          input id:"birthday", name:"birthday", type:"date", value: .birthday or ""
          fieldset ->
            legend "Show:"
            div class: "pure-control-group", ->
              @mkCheck "privacy_birthday_dm", "Date & Month", .privacy_birthday_dm
              @mkCheck "privacy_birthday_y", "Year", .privacy_birthday_y
              @mkCheck "privacy_birthday_age", "Age in years", .privacy_birthday_age
        fieldset ->
          legend "About me"
          textarea id:"about", name:"about", ->
            raw .about
          text "This field has "
          a target:"_blank", href:"https://daringfireball.net/projects/markdown/syntax", "markdown support"
          text " but you may not use HTML for security reasons."
      div class: "pure-controls", ->
        input type:"submit", class: "pure-button pure-button-primary"


print score on quiz-results

print score on quiz-results

import Widget from require "lapis.html"

Helpers = require('views.helpers')

class QuizResults extends Widget
  @include Helpers

  content: =>
    h2 @_("Quiz ") .. @quiz.name
    h2 @_("Student ") .. @quiz.user
    h2 @quiz.right_answers .. ' / ' .. @quiz.tasks
    for task in *(@quiz\all_tasks!)
      @task_table task
      raw '<br/>'

import Widget from require "lapis.html"

Helpers = require('views.helpers')

class QuizResults extends Widget
  @include Helpers

  content: =>
    h2 @_("Quiz ") .. @quiz.name
    h2 @_("Student ") .. @quiz.user
    for task in *(@quiz\all_tasks!)
      @task_table task
      raw '<br/>'