stop ticking uuuuu

stop ticking uuuuu

--
-- Copyright (C) 2017-2018 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

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

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

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

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

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

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

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

PPM2.BonesSequence = BonesSequence

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

RESET_BONE_POS = Vector(0, 0, 0)
RESET_BONE_ANGLES = Angle(0, 0, 0)
RESET_BONE_SCALE = Vector(1, 1, 1)
resetBones = (ent) ->
	for i = 0, ent\GetBoneCount() - 1
		ent\ManipulateBonePosition2Safe(i, RESET_BONE_POS)
		ent\ManipulateBoneScale2Safe(i, RESET_BONE_SCALE)
		ent\ManipulateBoneAngles2Safe(i, RESET_BONE_ANGLES)

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

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

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

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

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

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

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

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

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

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

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

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

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

	@SequenceObject = BonesSequence

	PreDrawOpaqueRenderables = (a, b) ->
		for obj in *@OBJECTS
			if not obj\IsValid()
				oldObjects = @OBJECTS
				@OBJECTS = {}
				for obj2 in *oldObjects
					if obj2\IsValid()
						table.insert(@OBJECTS, obj2)
					else
						lent = obj2.ent
						obj2.invalidate = true
						if IsValid(lent)
							lent.__ppmBonesModifiers = nil
				return

			if obj.ent\IsPony() and not obj.ent\IsPlayer()
				if obj\CanThink() and not obj.ent\IsDormant() and not obj.ent\GetNoDraw()
					obj.ent\ResetBoneManipCache()
					resetBones(obj.ent)
					data = obj.ent\GetPonyData()
					hook.Call('PPM2.SetupBones', nil, StrongEntity(obj.ent), data) if data
					obj\Think()
					obj.ent.__ppmBonesModified = true
					obj.ent\ApplyBoneManipulations()
			elseif obj.ent.__ppmBonesModified
				resetBones(obj.ent)
				obj.ent.__ppmBonesModified = false

	hook.Add 'PreDrawOpaqueRenderables', 'PPM2.EntityBonesModifier', PreDrawOpaqueRenderables, -5

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

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

	CanThink: => @callFrame ~= FrameNumberL() and (not @defferReset or @defferReset < RealTimeL())
	Think: (force = false) =>
		return if not super() or not force and @callFrame == FrameNumberL()
		@callFrame = FrameNumberL()

		with @ent
			calcBonesPos = {}
			calcBonesAngles = {}
			calcBonesScale = {}

			for id = 0, @boneCount - 1
				calcBonesPos[id] = \GetManipulateBonePosition2Safe(id)
				calcBonesAngles[id] = \GetManipulateBoneAngles2Safe(id)
				calcBonesScale[id] = \GetManipulateBoneScale2Safe(id)

			for data in *@bonesIterable
				id = data[1]
				name = data[2]
				calc = data[3]
				calcScale = data[4]
				calcAngles = data[5]
				calcBonesPos[id] += @[calc](@)
				calcBonesAngles[id] += @[calcAngles](@)
				calcBonesScale[id] += @[calcScale](@)

			for id = 0, @boneCount - 1
				\ManipulateBonePosition2Safe(id, calcBonesPos[id])
				\ManipulateBoneScale2Safe(id, calcBonesScale[id])
				\ManipulateBoneAngles2Safe(id, calcBonesAngles[id])

	ResetBones: =>
		return if @defferReset and @defferReset > RealTimeL()
		resetBones(@ent)

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

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

hook.Add 'PAC3ResetBones', 'PPM2.EntityBonesModifier', =>
	return if not @IsPony()
	data = @GetPonyData()
	@ResetBoneManipCache()
	hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
	if @__ppmBonesModifiers
		@__ppmBonesModifiers\Think(true)
		@__ppmBonesModifiers.defferReset = RealTimeL() + 0.2
	@ApplyBoneManipulations()

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

--
-- Copyright (C) 2017-2018 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

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

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

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

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

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

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

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

PPM2.BonesSequence = BonesSequence

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

RESET_BONE_POS = Vector(0, 0, 0)
RESET_BONE_ANGLES = Angle(0, 0, 0)
RESET_BONE_SCALE = Vector(1, 1, 1)
resetBones = (ent) ->
	for i = 0, ent\GetBoneCount() - 1
		ent\ManipulateBonePosition2Safe(i, RESET_BONE_POS)
		ent\ManipulateBoneScale2Safe(i, RESET_BONE_SCALE)
		ent\ManipulateBoneAngles2Safe(i, RESET_BONE_ANGLES)

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

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

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

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

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

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

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

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

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

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

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

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

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

	@SequenceObject = BonesSequence

	PreDrawOpaqueRenderables = (a, b) ->
		for obj in *@OBJECTS
			if not obj\IsValid()
				oldObjects = @OBJECTS
				@OBJECTS = {}
				for obj2 in *oldObjects
					if obj2\IsValid()
						table.insert(@OBJECTS, obj2)
					else
						lent = obj2.ent
						obj2.invalidate = true
						if IsValid(lent)
							lent.__ppmBonesModifiers = nil
				return

			if obj.ent\IsPony()
				if obj\CanThink() and not obj.ent\IsDormant() and not obj.ent\GetNoDraw()
					obj.ent\ResetBoneManipCache()
					resetBones(obj.ent)
					data = obj.ent\GetPonyData()
					hook.Call('PPM2.SetupBones', nil, StrongEntity(obj.ent), data) if data
					obj\Think()
					obj.ent.__ppmBonesModified = true
					obj.ent\ApplyBoneManipulations()
			elseif obj.ent.__ppmBonesModified
				resetBones(obj.ent)
				obj.ent.__ppmBonesModified = false

	hook.Add 'PreDrawOpaqueRenderables', 'PPM2.EntityBonesModifier', PreDrawOpaqueRenderables, -5

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

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

	CanThink: => @callFrame ~= FrameNumberL() and (not @defferReset or @defferReset < RealTimeL())
	Think: (force = false) =>
		return if not super() or not force and @callFrame == FrameNumberL()
		@callFrame = FrameNumberL()

		with @ent
			calcBonesPos = {}
			calcBonesAngles = {}
			calcBonesScale = {}

			for id = 0, @boneCount - 1
				calcBonesPos[id] = \GetManipulateBonePosition2Safe(id)
				calcBonesAngles[id] = \GetManipulateBoneAngles2Safe(id)
				calcBonesScale[id] = \GetManipulateBoneScale2Safe(id)

			for data in *@bonesIterable
				id = data[1]
				name = data[2]
				calc = data[3]
				calcScale = data[4]
				calcAngles = data[5]
				calcBonesPos[id] += @[calc](@)
				calcBonesAngles[id] += @[calcAngles](@)
				calcBonesScale[id] += @[calcScale](@)

			for id = 0, @boneCount - 1
				\ManipulateBonePosition2Safe(id, calcBonesPos[id])
				\ManipulateBoneScale2Safe(id, calcBonesScale[id])
				\ManipulateBoneAngles2Safe(id, calcBonesAngles[id])

	ResetBones: =>
		return if @defferReset and @defferReset > RealTimeL()
		resetBones(@ent)

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

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

hook.Add 'PAC3ResetBones', 'PPM2.EntityBonesModifier', =>
	return if not @IsPony()
	data = @GetPonyData()
	@ResetBoneManipCache()
	hook.Call('PPM2.SetupBones', nil, StrongEntity(@), data) if data
	if @__ppmBonesModifiers
		@__ppmBonesModifiers\Think(true)
		@__ppmBonesModifiers.defferReset = RealTimeL() + 0.2
	@ApplyBoneManipulations()

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


Quick fix to Vardump

Quick fix to Vardump

[=[
Class: UtilX

A static class used for utility functions that are not specific to Garry's Mod.

Depends On:
	* <TableX>
	* Moon standard library

Revisions:
	4.0.0 - Initial.
]=]
class ulx.UtilX
	[=[
	Function: Trim
	Trims leading and tailing whitespace from a string.

	Parameters:
		str - The *string* to trim.

	Returns:
		The trimmed *string*.

	Notes:
		* This is 'trim6' from <http://lua-users.org/wiki/StringTrim>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Trim: (str using nil) ->
		str\match("^()%s*$") and "" or str\match("^%s*(.*%S)")

	[=[
	Function: LTrim
	Exactly like <Trim> except it only trims the left side. Taken from <http://lua-users.org/wiki/CommonFunctions>

	Revisions:
		4.0.0 - Initial.
	]=]
	@LTrim: (str using nil) ->
		(str\gsub("^%s*", ""))

	[=[
	Function: RTrim
	Exactly like <Trim> except it only trims the right side. Taken from <http://lua-users.org/wiki/CommonFunctions>

	Revisions:
		4.0.0 - Initial.
	]=]
	@RTrim: (str using nil) ->
		n = #str
		while n > 0 and str\find("^%s", n)
			n -= 1

		str\sub( 1, n )

	VardumpHelper = (value, depth, key, done using nil) ->
		str = string.rep "  ", depth

		if key ~= nil
			t = type key
			if t == "string"
				str ..= string.format "%q", key
			else
				str ..= tostring key
			str ..= " = "

		t = type value
		if t == "table" and not done[value]
			done[value] = true
			str ..= string.format "(table: array size=%i, total values=%i)\n", #value, ulx.TableX.Count(value)
			for k, v in pairs value
				str ..= VardumpHelper v, depth+1, k, done

		elseif t == "string" then
			str ..= string.format "%q\n", value

		else
			str ..= tostring(value) .. "\n"

		str

	[=[
	Function: Vardump
	Returns useful, readable information about variables.

	Parameters:
		... - Accepts any number of parameters of *any type* and prints them one by one.

	Returns:
		A readable *string* serialization of the data passed in.

	Example:
		:Vardump( { "foo", apple="green", floor=41, shopping={ "milk", "cookies" } } )

		returns the string...

		:(table: array size=1, total values=4)
		:  1 = "foo"
		:  "apple" = "green"
		:  "floor" = 41
		:  "shopping" = (table: array size=2, total values=2)
		:    1 = "milk"
		:    2 = "cookies"

	Notes:
		* A string will always be surrounded by quotes and a number will always stand by itself. This is to make it easier to identify numbers stored as strings.
		* Array size and total size are shown in the table header. Array size is the result of the pound operator (#) on the table, total size is the result of <Count>.
		  Array size is useful debug information when iterating over a table with ipairs or fori.

	Revisions:
		v4.0.0 - Initial.
	]=]
	@Vardump: (... using nil) ->
		str = ""
		t = {...}
		for i=1, select("#", ...)
			str ..= VardumpHelper(t[i], 0, nil, {})

		str\sub(1, -2) -- Remove last newline

	[=[
	Function: Raise
	TODO
	]=]
	@Raise: (str, level=1 using nil) ->
		error str, level+1

	[=[
	Function: RaiseUnimplemented
	TODO
	]=]
	@RaiseUnimplemented: (str, level=1 using nil) ->
		@.Raise str .. " called, but unimplemented", level+1

	[=[
	Function: Round
	Rounds a number to a given decimal place.

	Parameters:
		num    - The *number* to round.
		places - The *optional number* of places to round to. Defaults to _0_.
		         0 rounds to the nearest whole number, 1 rounds to the nearest tenth, 2 rounds to the nearest thousandth, etc.
		         Negative numbers round into the non-fractional places; -1 rounds to the nearest tens, -2 rounds to the nearest hundreds, etc.

	Returns:
		The rounded *number*.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Round: (num, places=0 using nil) ->
		mult = 10 ^ places
		math.floor(num * mult + 0.5) / mult

	timeCodes =
		m: 60
		minute: 60
		minutes: 60
		h: 60 * 60
		hour: 60 * 60
		hours: 60 * 60
		d: 60 * 60 * 24
		day: 60 * 60 * 24
		days: 60 * 60 * 24
		w: 60 * 60 * 24 * 7
		week: 60 * 60 * 24 * 7
		weeks: 60 * 60 * 24 * 7
		M: 60 * 60 * 24 * (365.25/12) -- Very hand-wavy, but we only need an approximate
		month: 60 * 60 * 24 * (365.25/12)
		months: 60 * 60 * 24 * (365.25/12)
		y: 60 * 60 * 24 * 365.25
		year: 60 * 60 * 24 * 365.25
		years: 60 * 60 * 24 * 365.25
	[=[
	Function: TimeStringToSeconds
	A "time string" to convert into seconds. Can accept minutes, hours, days, weeks, months, or years. See examples below.

	Parameters:
		str - The *string* or *number* to convert to seconds. If it's a number of a string of a number, passes back that number.

	Returns:
		The *number* of seconds.

	Examples:
		All of the following return the same thing...
		* "1M7d5h3m11s"
		* "1M 7d 5h 3m 11"
		* "1M, 7d, 5h, 3m 11s"
		* "1 month  7 days, 5h 3minute, 11 seconds"

	Notes:
		* Commas and spacing are ignored.
		* Any time multiplier (E.G., weeks) that isn't recognized or supported (E.G., milliseconds) will be considered to be seconds.

	Revisions:
		4.0.0 - Initial.
	]=]
	@TimeStringToSeconds: (str using nil) ->
		@.CheckArg "TimeStringToSeconds", 1, {"string", "number"}, str

		if num = tonumber(str)
			return num

		str = @.Trim(str\gsub ",", "")
		len = #str
		num = 0
		startIdx = 1
		codeIdx = str\find "%D", startIdx
		while codeIdx
			nextIdx = (str\find("%d", codeIdx)) or len+1

			units = str\sub startIdx, codeIdx-1
			units = tonumber units
			code = @.Trim(str\sub codeIdx, nextIdx-1)
			multiplier = timeCodes[code] or 1
			num += units * multiplier

			startIdx = nextIdx
			codeIdx = str\find "%D", startIdx

			if not codeIdx and startIdx < len
				num += tonumber(str\sub startIdx)

		num

	-- Transforms the "expected" argument for functions below into a list of strings.
	expectedToStrings = (expected using nil) ->
		[ type(v) == "string" and v or v.__name for v in *expected ]

	[=[
	Function: RaiseBadArg
	Raises an error similar to the Lua standard error of "bad argument #x to <fn_name> (<type> expected, got <type>)".
	This function intelligently figures out how best to word the error with the given information.

	Parameters:
		fnName   - The *optional string* of the function name being called.
		argnum   - The *optional number* of the argument that was bad.
		expected - The *optional string, class (via moon.type), or list* of the type(s) you expected.
		data     - *Optional and any type*, the actual data you got.
		level    - The *optional number* of how many levels up (from this function) to throw the error. Defaults to _1_.

	Returns:
		Always returns using *<Raise()>*.

	Revisions:
		4.0.0 - Initial.
	]=]
	@RaiseBadArg: (fnName, argnum, expected, data, level=1 using nil) ->
		expected = { expected } if expected and moon.type(expected) ~= "table"
		dataStr = moon.type(data)
		dataStr = data.__name if type(dataStr) ~= "string"

		str = "bad argument"
		if argnum then
			str ..= " #" .. tostring(argnum)
		if fnName then
			str ..= " to " .. fnName
		if expected or data then
			str ..= " ("
			if expected then
				str ..= table.concat(expectedToStrings(expected), " or ") .. " expected"
			if expected and data then
				str ..= ", "
			if data then
				str ..= "got " .. dataStr
			str ..= ")"

		@.Raise str, level+1

	[=[
	Function: CheckArg
	Used to check to see if a function argument matches what is expected. If it doesn't, calls <RaiseBadArg()>.
	This function is primarily useful at the beginning of a function definition to ensure that the correct type of data was passed in.

	Parameters:
		fnName   - The *optional string* of the function name being called.
		argnum   - The *optional number* of the argument that was bad.
		expected - The *optional string, class (via moon.type), or list* of the type(s) you expected.
		data     - *Optional and any type*, the actual data you got.
		level    - The *optional number* of how many levels up (from this function) to throw the error. Defaults to _1_.

	Returns:
		Returns using *<Raise()>* if the argument doesn't match what's expected, otherwise returns *true*.

	Example:
		:CheckArg("test", 1, "number", 41)

		returns...

		:true

	Revisions:
		4.0.0 - Initial.
	]=]
	@CheckArg: (fnName, argnum, expected, data, level=1 using nil) ->
		if expected == nil return true
		if moon.type(expected) ~= "table"
			if expected == moon.type(data)
				return true
		elseif ulx.TableX.HasValueI(expected, moon.type(data)) then
			return true

		@.RaiseBadArg fnName, argnum, expected, data, level+1

	[=[
	Function: CheckArgs
	A shortcut for checking all the arguments passed into a function via <CheckArg>.

	Parameters:
		fnName - The *optional string* of the function name being called.
		args   - The *array* containing one *array* for each argument.
		         In each sub-array, the first argument is the expected arg in <CheckArg>.
		         The second argument is the data arg in <CheckArg>.
		level  - The *optional number* of how many levels up (from this function) to throw the error. Defaults to _1_.

	Returns:
		Returns using *<Raise()>* if the arguments don't match what's expected, otherwise returns *true*.

	Example:
		:CheckArgs("test", {{"boolean", true}, {"string", "41"}})

		returns...

		:true

	Revisions:
		4.0.0 - Initial.
	]=]
	@CheckArgs: (fnName, args, level=1 using nil) ->
		for argnum=1, #args
			continue if args[argnum] == nil
			{expected, data} = args[argnum]

			if not @.CheckArg fnName, argnum, expected, data, level+1
				return false

		true

	[=[
	Function: Explode
	Split a string by a separator.

	Parameters:
		separator - The *string* separator. This can be RegEx.
		str - The *string* to explode.
		limit - The *optional number* specifying the maximum times to explode.

	Returns:
		A *list of strings*.

	Example:
		:Explode( " ", "This is a sentence" )

		returns...

		:{ "This", "is", "a", "sentence" }

		Revisions:
			4.0.0 - Initial.
	]=]
	@Explode: (separator, str, limit using nil) ->
		t = {}
		curpos = 1
		while true -- We have a break in the loop
			newpos, endpos = str\find separator, curpos -- find the next separator in the string
			if newpos ~= nil -- if found then..
				table.insert t, str\sub(curpos, newpos-1) -- Save it in our table.
				curpos = endpos + 1 -- save just after where we found it for searching next time.
			else
				if limit and #t > limit
					return t -- Reached limit
				table.insert( t, str\sub(curpos) ) -- Save what's left in our array.
				break

		return t

	[=[
	Function: SplitArgs
	This is similar to a string explode function on whitespace, except that it also allows for grouping with startToken and endToken.

	Parameters:
		args - The *string* to split into individual arguments.
		startToken - The *string* character to start a string with, default to _'"'_ (double quotes).
		endToken - The *string* character to end a string with, default to _'"'_ (double quotes).

	Returns:
		1 - A *list of strings*. Each item is one argument.
		2 - A *boolean*, which is true if the start and end tokens were mismatched (parsing ended while looking for an endToken).

	Example:
		:SplitArgs( "This is a \"Cool sentence to\" make \"split up\"" )

		returns...

		:{ "This", "is", "a", "Cool sentence to", "make", "split up" }

	Notes:
		* Mismatched quotes will result in having the last quote grouping the remaining input into one argument.
		* Args outside of quotes are trimmed (via string.Trim), while args inside quotes is not trimmed at all.

	Revisions:
		4.0.0 - Initial.
	]=]
	@SplitArgs: (args, startToken='"', endToken='"' using nil) ->
		args = @.Trim args
		argv = {}
		currentPos = 1 -- Our current position within the string
		inQuote = false -- Is the text we're currently processing in a quote?
		argsLen = #args

		while inQuote or currentPos <= argsLen
			tokenToLookFor = inQuote and endToken or startToken
			tokenPos = args\find tokenToLookFor, currentPos, true

			-- The string up to the quote, the whole string if no quote was found
			prefix = args\sub currentPos, (tokenPos or 0) - 1
			if not inQuote
				trimmedPrefix = @.Trim prefix
				if trimmedPrefix ~= "" -- Something to be had from this...
					arg = @.Explode( "%s+", trimmedPrefix )
					ulx.TableX.Append argv, arg, true
			else
				table.insert argv, prefix

			-- If a quote was found, update our position and note our state
			if tokenPos ~= nil
				currentPos = tokenPos + 1
				inQuote = not inQuote
			else -- Otherwise we've processed the whole string now
				break

		return argv, inQuote

[=[
Class: UtilX

A static class used for utility functions that are not specific to Garry's Mod.

Depends On:
	* <TableX>
	* Moon standard library

Revisions:
	4.0.0 - Initial.
]=]
class ulx.UtilX
	[=[
	Function: Trim
	Trims leading and tailing whitespace from a string.

	Parameters:
		str - The *string* to trim.

	Returns:
		The trimmed *string*.

	Notes:
		* This is 'trim6' from <http://lua-users.org/wiki/StringTrim>.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Trim: (str using nil) ->
		str\match("^()%s*$") and "" or str\match("^%s*(.*%S)")

	[=[
	Function: LTrim
	Exactly like <Trim> except it only trims the left side. Taken from <http://lua-users.org/wiki/CommonFunctions>

	Revisions:
		4.0.0 - Initial.
	]=]
	@LTrim: (str using nil) ->
		(str\gsub("^%s*", ""))

	[=[
	Function: RTrim
	Exactly like <Trim> except it only trims the right side. Taken from <http://lua-users.org/wiki/CommonFunctions>

	Revisions:
		4.0.0 - Initial.
	]=]
	@RTrim: (str using nil) ->
		n = #str
		while n > 0 and str\find("^%s", n)
			n -= 1

		str\sub( 1, n )

	VardumpHelper = (value, depth, key, done using nil) ->
		str = string.rep "  ", depth

		if key ~= nil
			t = type key
			if t == "string"
				str = string.format "%q", key
			else
				str ..= tostring key
			str ..= " = "

		t = type value
		if t == "table" and not done[value]
			done[value] = true
			str ..= string.format "(table: array size=%i, total values=%i)\n", #value, ulx.TableX.Count(value)
			for k, v in pairs value
				str ..= VardumpHelper v, depth+1, k, done

		elseif t == "string" then
			str ..= string.format "%q\n", value

		else
			str = tostring(value) .. "\n"

		str

	[=[
	Function: Vardump
	Returns useful, readable information about variables.

	Parameters:
		... - Accepts any number of parameters of *any type* and prints them one by one.

	Returns:
		A readable *string* serialization of the data passed in.

	Example:
		:Vardump( { "foo", apple="green", floor=41, shopping={ "milk", "cookies" } } )

		returns the string...

		:(table: array size=1, total values=4)
		:  1 = "foo"
		:  "apple" = "green"
		:  "floor" = 41
		:  "shopping" = (table: array size=2, total values=2)
		:    1 = "milk"
		:    2 = "cookies"

	Notes:
		* A string will always be surrounded by quotes and a number will always stand by itself. This is to make it easier to identify numbers stored as strings.
		* Array size and total size are shown in the table header. Array size is the result of the pound operator (#) on the table, total size is the result of <Count>.
		  Array size is useful debug information when iterating over a table with ipairs or fori.

	Revisions:
		v4.0.0 - Initial.
	]=]
	@Vardump: (... using nil) ->
		str = ""
		t = {...}
		for i=1, select("#", ...)
			str ..= VardumpHelper(t[i], 0, nil, {})

		str\sub(1, -2) -- Remove last newline

	[=[
	Function: Raise
	TODO
	]=]
	@Raise: (str, level=1 using nil) ->
		error str, level+1

	[=[
	Function: RaiseUnimplemented
	TODO
	]=]
	@RaiseUnimplemented: (str, level=1 using nil) ->
		@.Raise str .. " called, but unimplemented", level+1

	[=[
	Function: Round
	Rounds a number to a given decimal place.

	Parameters:
		num    - The *number* to round.
		places - The *optional number* of places to round to. Defaults to _0_.
		         0 rounds to the nearest whole number, 1 rounds to the nearest tenth, 2 rounds to the nearest thousandth, etc.
		         Negative numbers round into the non-fractional places; -1 rounds to the nearest tens, -2 rounds to the nearest hundreds, etc.

	Returns:
		The rounded *number*.

	Revisions:
		4.0.0 - Initial.
	]=]
	@Round: (num, places=0 using nil) ->
		mult = 10 ^ places
		math.floor(num * mult + 0.5) / mult

	timeCodes =
		m: 60
		minute: 60
		minutes: 60
		h: 60 * 60
		hour: 60 * 60
		hours: 60 * 60
		d: 60 * 60 * 24
		day: 60 * 60 * 24
		days: 60 * 60 * 24
		w: 60 * 60 * 24 * 7
		week: 60 * 60 * 24 * 7
		weeks: 60 * 60 * 24 * 7
		M: 60 * 60 * 24 * (365.25/12) -- Very hand-wavy, but we only need an approximate
		month: 60 * 60 * 24 * (365.25/12)
		months: 60 * 60 * 24 * (365.25/12)
		y: 60 * 60 * 24 * 365.25
		year: 60 * 60 * 24 * 365.25
		years: 60 * 60 * 24 * 365.25
	[=[
	Function: TimeStringToSeconds
	A "time string" to convert into seconds. Can accept minutes, hours, days, weeks, months, or years. See examples below.

	Parameters:
		str - The *string* or *number* to convert to seconds. If it's a number of a string of a number, passes back that number.

	Returns:
		The *number* of seconds.

	Examples:
		All of the following return the same thing...
		* "1M7d5h3m11s"
		* "1M 7d 5h 3m 11"
		* "1M, 7d, 5h, 3m 11s"
		* "1 month  7 days, 5h 3minute, 11 seconds"

	Notes:
		* Commas and spacing are ignored.
		* Any time multiplier (E.G., weeks) that isn't recognized or supported (E.G., milliseconds) will be considered to be seconds.

	Revisions:
		4.0.0 - Initial.
	]=]
	@TimeStringToSeconds: (str using nil) ->
		@.CheckArg "TimeStringToSeconds", 1, {"string", "number"}, str

		if num = tonumber(str)
			return num

		str = @.Trim(str\gsub ",", "")
		len = #str
		num = 0
		startIdx = 1
		codeIdx = str\find "%D", startIdx
		while codeIdx
			nextIdx = (str\find("%d", codeIdx)) or len+1

			units = str\sub startIdx, codeIdx-1
			units = tonumber units
			code = @.Trim(str\sub codeIdx, nextIdx-1)
			multiplier = timeCodes[code] or 1
			num += units * multiplier

			startIdx = nextIdx
			codeIdx = str\find "%D", startIdx

			if not codeIdx and startIdx < len
				num += tonumber(str\sub startIdx)

		num

	-- Transforms the "expected" argument for functions below into a list of strings.
	expectedToStrings = (expected using nil) ->
		[ type(v) == "string" and v or v.__name for v in *expected ]

	[=[
	Function: RaiseBadArg
	Raises an error similar to the Lua standard error of "bad argument #x to <fn_name> (<type> expected, got <type>)".
	This function intelligently figures out how best to word the error with the given information.

	Parameters:
		fnName   - The *optional string* of the function name being called.
		argnum   - The *optional number* of the argument that was bad.
		expected - The *optional string, class (via moon.type), or list* of the type(s) you expected.
		data     - *Optional and any type*, the actual data you got.
		level    - The *optional number* of how many levels up (from this function) to throw the error. Defaults to _1_.

	Returns:
		Always returns using *<Raise()>*.

	Revisions:
		4.0.0 - Initial.
	]=]
	@RaiseBadArg: (fnName, argnum, expected, data, level=1 using nil) ->
		expected = { expected } if expected and moon.type(expected) ~= "table"
		dataStr = moon.type(data)
		dataStr = data.__name if type(dataStr) ~= "string"

		str = "bad argument"
		if argnum then
			str ..= " #" .. tostring(argnum)
		if fnName then
			str ..= " to " .. fnName
		if expected or data then
			str ..= " ("
			if expected then
				str ..= table.concat(expectedToStrings(expected), " or ") .. " expected"
			if expected and data then
				str ..= ", "
			if data then
				str ..= "got " .. dataStr
			str ..= ")"

		@.Raise str, level+1

	[=[
	Function: CheckArg
	Used to check to see if a function argument matches what is expected. If it doesn't, calls <RaiseBadArg()>.
	This function is primarily useful at the beginning of a function definition to ensure that the correct type of data was passed in.

	Parameters:
		fnName   - The *optional string* of the function name being called.
		argnum   - The *optional number* of the argument that was bad.
		expected - The *optional string, class (via moon.type), or list* of the type(s) you expected.
		data     - *Optional and any type*, the actual data you got.
		level    - The *optional number* of how many levels up (from this function) to throw the error. Defaults to _1_.

	Returns:
		Returns using *<Raise()>* if the argument doesn't match what's expected, otherwise returns *true*.

	Example:
		:CheckArg("test", 1, "number", 41)

		returns...

		:true

	Revisions:
		4.0.0 - Initial.
	]=]
	@CheckArg: (fnName, argnum, expected, data, level=1 using nil) ->
		if expected == nil return true
		if moon.type(expected) ~= "table"
			if expected == moon.type(data)
				return true
		elseif ulx.TableX.HasValueI(expected, moon.type(data)) then
			return true

		@.RaiseBadArg fnName, argnum, expected, data, level+1

	[=[
	Function: CheckArgs
	A shortcut for checking all the arguments passed into a function via <CheckArg>.

	Parameters:
		fnName - The *optional string* of the function name being called.
		args   - The *array* containing one *array* for each argument.
		         In each sub-array, the first argument is the expected arg in <CheckArg>.
		         The second argument is the data arg in <CheckArg>.
		level  - The *optional number* of how many levels up (from this function) to throw the error. Defaults to _1_.

	Returns:
		Returns using *<Raise()>* if the arguments don't match what's expected, otherwise returns *true*.

	Example:
		:CheckArgs("test", {{"boolean", true}, {"string", "41"}})

		returns...

		:true

	Revisions:
		4.0.0 - Initial.
	]=]
	@CheckArgs: (fnName, args, level=1 using nil) ->
		for argnum=1, #args
			continue if args[argnum] == nil
			{expected, data} = args[argnum]

			if not @.CheckArg fnName, argnum, expected, data, level+1
				return false

		true

	[=[
	Function: Explode
	Split a string by a separator.

	Parameters:
		separator - The *string* separator. This can be RegEx.
		str - The *string* to explode.
		limit - The *optional number* specifying the maximum times to explode.

	Returns:
		A *list of strings*.

	Example:
		:Explode( " ", "This is a sentence" )

		returns...

		:{ "This", "is", "a", "sentence" }

		Revisions:
			4.0.0 - Initial.
	]=]
	@Explode: (separator, str, limit using nil) ->
		t = {}
		curpos = 1
		while true -- We have a break in the loop
			newpos, endpos = str\find separator, curpos -- find the next separator in the string
			if newpos ~= nil -- if found then..
				table.insert t, str\sub(curpos, newpos-1) -- Save it in our table.
				curpos = endpos + 1 -- save just after where we found it for searching next time.
			else
				if limit and #t > limit
					return t -- Reached limit
				table.insert( t, str\sub(curpos) ) -- Save what's left in our array.
				break

		return t

	[=[
	Function: SplitArgs
	This is similar to a string explode function on whitespace, except that it also allows for grouping with startToken and endToken.

	Parameters:
		args - The *string* to split into individual arguments.
		startToken - The *string* character to start a string with, default to _'"'_ (double quotes).
		endToken - The *string* character to end a string with, default to _'"'_ (double quotes).

	Returns:
		1 - A *list of strings*. Each item is one argument.
		2 - A *boolean*, which is true if the start and end tokens were mismatched (parsing ended while looking for an endToken).

	Example:
		:SplitArgs( "This is a \"Cool sentence to\" make \"split up\"" )

		returns...

		:{ "This", "is", "a", "Cool sentence to", "make", "split up" }

	Notes:
		* Mismatched quotes will result in having the last quote grouping the remaining input into one argument.
		* Args outside of quotes are trimmed (via string.Trim), while args inside quotes is not trimmed at all.

	Revisions:
		4.0.0 - Initial.
	]=]
	@SplitArgs: (args, startToken='"', endToken='"' using nil) ->
		args = @.Trim args
		argv = {}
		currentPos = 1 -- Our current position within the string
		inQuote = false -- Is the text we're currently processing in a quote?
		argsLen = #args

		while inQuote or currentPos <= argsLen
			tokenToLookFor = inQuote and endToken or startToken
			tokenPos = args\find tokenToLookFor, currentPos, true

			-- The string up to the quote, the whole string if no quote was found
			prefix = args\sub currentPos, (tokenPos or 0) - 1
			if not inQuote
				trimmedPrefix = @.Trim prefix
				if trimmedPrefix ~= "" -- Something to be had from this...
					arg = @.Explode( "%s+", trimmedPrefix )
					ulx.TableX.Append argv, arg, true
			else
				table.insert argv, prefix

			-- If a quote was found, update our position and note our state
			if tokenPos ~= nil
				currentPos = tokenPos + 1
				inQuote = not inQuote
			else -- Otherwise we've processed the whole string now
				break

		return argv, inQuote


Delete files because they are now useless

--
-- Copyright (C) 2017-2018 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.
--

file.CreateDir('ppm2')
file.CreateDir('ppm2/backups')
file.CreateDir('ppm2/thumbnails')

for ffind in *file.Find('ppm2/*.txt', 'DATA')
	fTarget = ffind\sub(1, -5)
	-- maybe joined server with old ppm2 and new clear _current was generated
	if not file.Exists('ppm2/' .. fTarget .. '.dat', 'DATA')
		fRead = file.Read('ppm2/' .. ffind, 'DATA')
		json = util.JSONToTable(fRead)
		if json
			TagCompound = DLib.NBT.TagCompound()
			for key, value in pairs json
				switch type(value)
					when 'string'
						TagCompound\AddString(key, value)
					when 'number'
						TagCompound\AddFloat(key, value)
					when 'boolean'
						TagCompound\AddByte(key, value and 1 or 0)
					when 'table'
						-- assume color
						TagCompound\AddByteArray(key, {value.r - 128, value.g - 128, value.b - 128, value.a - 128}) if value.r and value.g and value.b and value.a
					else
						error(type(value))
			buf = DLib.BytesBuffer()
			TagCompound\WriteFile(buf)
			stream = file.Open('ppm2/' .. fTarget .. '.dat', 'wb', 'DATA')
			buf\ToFileStream(stream)
			stream\Flush()
			stream\Close()
	file.Delete('ppm2/' .. ffind)

class PonyDataInstance
	@DATA_DIR = "ppm2/"
	@DATA_DIR_BACKUP = "ppm2/backups/"

	@FindFiles = =>
		output = [str\sub(1, #str - 4) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@FindInstances = =>
		output = [@(str\sub(1, #str - 4)) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@PONY_DATA = PPM2.PonyDataRegistry

	@PONY_DATA_MAPPING = {getFunc\lower(), key for key, {:getFunc} in pairs @PONY_DATA}
	@PONY_DATA_MAPPING[key] = key for key, value in pairs @PONY_DATA

	for key, data in pairs @PONY_DATA
		continue unless data.enum
		data.enum = [arg\upper() for arg in *data.enum]
		data.enumMapping = {}
		data.enumMappingBackward = {}
		i = -1
		for enumVal in *data.enum
			i += 1
			data.enumMapping[i] = enumVal
			data.enumMappingBackward[enumVal] = i
	for key, {:getFunc, :fix, :enumMappingBackward, :enumMapping, :enum, :min, :max, :default} in pairs @PONY_DATA
		@__base["Get#{getFunc}"] = => @dataTable[key]
		@__base["GetMin#{getFunc}"] = => min if min
		@__base["GetMax#{getFunc}"] = => max if max
		@__base["Enum#{getFunc}"] = => enum if enum
		@__base["Get#{getFunc}Types"] = => enum if enum

		@["GetMin#{getFunc}"] = => min if min
		@["GetMax#{getFunc}"] = => max if max
		@["GetDefault#{getFunc}"] = default
		@["GetEnum#{getFunc}"] = => enum if enum
		@["Enum#{getFunc}"] = => enum if enum

		if enumMapping
			@__base["Get#{getFunc}Enum"] = => enumMapping[@dataTable[key]] or enumMapping[0] or @dataTable[key]
			@__base["GetEnum#{getFunc}"] = @__base["Get#{getFunc}Enum"]
		@__base["Reset#{getFunc}"] = => @["Set#{getFunc}"](@, default())
		@__base["Set#{getFunc}"] = (val = defValue, ...) =>
			if type(val) == 'string' and enumMappingBackward
				newVal = enumMappingBackward[val\upper()]
				val = newVal if newVal
			newVal = fix(val)
			oldVal = @dataTable[key]
			@dataTable[key] = newVal
			@ValueChanges(key, oldVal, newVal, ...)

	WriteNetworkData: =>
		for {:strName, :writeFunc, :getName, :defValue} in *PPM2.NetworkedPonyData.NW_Vars
			if @["Get#{getName}"]
				writeFunc(@["Get#{getName}"](@))
			else
				writeFunc(defValue)

	Copy: (fileName = @filename) =>
		copyOfData = {}
		for key, val in pairs @dataTable
			switch type(val)
				when 'number'
					copyOfData[key] = val
				when 'string'
					copyOfData[key] = val
				when 'boolean'
					copyOfData[key] = val
				when 'table'
					{:r, :g, :b} = val
					if r and g and b
						copyOfData[key] = Color(r, g, b)
		newData = @@(fileName, copyOfData, false)
		return newData
	CreateCustomNetworkObject: (goingToNetwork = false, ply = LocalPlayer(), ...) =>
		newData = PPM2.NetworkedPonyData(nil, ply)
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(ply)
		@ApplyDataToObject(newData, ...)
		return newData
	CreateNetworkObject: (goingToNetwork = true, ...) =>
		newData = PPM2.NetworkedPonyData(nil, LocalPlayer())
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(LocalPlayer())
		@ApplyDataToObject(newData, ...)
		return newData
	ApplyDataToObject: (target, ...) =>
		for key, value in pairs @GetAsNetworked()
			error("Attempt to apply data to object #{target} at unknown index #{key}!") if not target["Set#{key}"]
			target["Set#{key}"](target, value, ...)
	UpdateController: (...) => @ApplyDataToObject(@nwObj, ...)
	CreateController: (...) => @CreateNetworkObject(false, ...)
	CreateCustomController: (...) => @CreateCustomNetworkObject(false, ...)

	new: (filename, data, readIfExists = true, force = false, doBackup = true) =>
		@SetFilename(filename)
		@NBTTagCompound = DLib.NBT.TagCompound()
		@updateNWObject = true
		@networkNWObject = true
		@valid = @isOpen
		@rawData = data
		@dataTable = {k, default() for k, {:default} in pairs @@PONY_DATA}
		@saveOnChange = true
		if data
			@SetupData(data, true)
		elseif @exists and readIfExists
			@ReadFromDisk(force, doBackup)

	Reset: => @['Reset' .. getFunc](@) for k, {:getFunc} in pairs @@PONY_DATA

	@ERR_MISSING_PARAMETER = 4
	@ERR_MISSING_CONTENT = 5

	GetSaveOnChange: => @saveOnChange
	SaveOnChange: => @saveOnChange
	SetSaveOnChange: (val = true) => @saveOnChange = val
	GetValueFromNBT: (mapData, value) =>
		if mapData.enum and type(value) == 'string'
			mapData.fix(mapData.enumMappingBackward[value\upper()])
		elseif mapData.type == 'COLOR'
			if value.r and value.g and value.b and value.a
				mapData.fix(Color(value))
			else
				mapData.fix(Color(value[1] + 128, value[2] + 128, value[3] + 128, value[4] + 128))
		elseif mapData.type == 'BOOLEAN'
			if type(value) == 'boolean'
				mapData.fix(value)
			else
				mapData.fix(value == 1)
		else
			mapData.fix(value)

	SetupData: (data = @NBTTagCompound, force = false, doBackup = false) =>
		if type(data) == 'NBTCompound'
			data = data\GetValue()
		if doBackup or not force
			makeBackup = false
			for key, value2 in pairs(data)
				key = key\lower()
				map = @@PONY_DATA_MAPPING[key]
				if map
					mapData = @@PONY_DATA[map]
					value = @GetValueFromNBT(mapData, value2)
					if mapData.enum
						if type(value) == 'string' and not mapData.enumMappingBackward[value\upper()] or type(value) == 'number' and not mapData.enumMapping[value]
							return @@ERR_MISSING_CONTENT if not force
							makeBackup = true
							break
			if doBackup and makeBackup and @exists
				bkName = "#{@@DATA_DIR_BACKUP}#{@filename}_bak_#{os.date('%S_%M_%H-%d_%m_%Y', os.time())}.dat"
				fRead = file.Read(@fpath, 'DATA')
				file.Write(bkName, fRead)

		for key, value2 in pairs(data)
			key = key\lower()
			map = @@PONY_DATA_MAPPING[key]
			continue unless map
			mapData = @@PONY_DATA[map]
			@dataTable[key] = @GetValueFromNBT(mapData, value2)
	ValueChanges: (key, oldVal, newVal, saveNow = @exists and @saveOnChange) =>
		if @nwObj and @updateNWObject
			{:getFunc} = @@PONY_DATA[key]
			@nwObj["Set#{getFunc}"](@nwObj, newVal, @networkNWObject)
		@Save() if saveNow
	SetFilename: (filename) =>
		@filename = filename
		@filenameFull = "#{filename}.dat"
		@fpath = "#{@@DATA_DIR}#{filename}.dat"
		@preview = "#{@@DATA_DIR}thumbnails/#{filename}.png"
		@fpathBackup = "#{@@DATA_DIR}#{filename}.bak.dat"
		@fpathFull = "data/#{@@DATA_DIR}#{filename}.dat"
		@isOpen = @filename ~= nil
		@exists = file.Exists(@fpath, 'DATA')
		return @exists
	SetNetworkData: (nwObj) => @nwObj = nwObj
	SetPonyData: (nwObj) => @nwObj = nwObj
	SetPonyDataController: (nwObj) => @nwObj = nwObj
	SetPonyController: (nwObj) => @nwObj = nwObj
	SetController: (nwObj) => @nwObj = nwObj
	SetDataController: (nwObj) => @nwObj = nwObj

	SetNetworkOnChange: (newVal = true) => @networkNWObject = newVal
	SetUpdateOnChange: (newVal = true) => @updateNWObject = newVal

	GetNetworkOnChange: => @networkNWObject
	GetUpdateOnChange: => @updateNWObject

	GetNetworkData: => @nwObj
	GetPonyData: => @nwObj
	GetPonyDataController: => @nwObj
	GetPonyController: => @nwObj
	GetController: => @nwObj
	GetDataController: => @nwObj

	IsValid: => @valid
	Exists: => @exists
	FileExists: => @exists
	IsExists: => @exists
	GetFileName: => @filename
	GetFilename: => @filename
	GetFileNameFull: => @filenameFull
	GetFilenameFull: => @filenameFull
	GetFilePath: => @fpath
	GetFullFilePath: => @fpathFull
	SerealizeValue: (valID = '') =>
		map = @@PONY_DATA[valID]
		return unless map
		val = @dataTable[valID]
		if map.enum
			return DLib.NBT.TagString(map.enumMapping[val] or map.enumMapping[map.default()])
		elseif map.serealize
			return map.serealize(val)
		else
			switch map.type
				when 'INT'
					return DLib.NBT.TagInt(val)
				when 'FLOAT'
					return DLib.NBT.TagFloat(val)
				when 'URL'
					return DLib.NBT.TagString(val)
				when 'BOOLEAN'
					return DLib.NBT.TagByte(val and 1 or 0)
				when 'COLOR'
					return DLib.NBT.TagByteArray({val.r - 128, val.g - 128, val.b - 128, val.a - 128})
	GetAsNetworked: => {getFunc, @dataTable[k] for k, {:getFunc} in pairs @@PONY_DATA}

	@READ_SUCCESS = 0
	@ERR_FILE_NOT_EXISTS = 1
	@ERR_FILE_EMPTY = 2
	@ERR_FILE_CORRUPT = 3
	ReadFromDisk: (force = false, doBackup = true) =>
		return @@ERR_FILE_NOT_EXISTS unless @exists
		fRead = file.Read(@fpath, 'DATA')
		return @@ERR_FILE_EMPTY if not fRead or fRead == ''
		@NBTTagCompound\ReadFile(DLib.BytesBuffer(fRead))
		return @SetupData(@NBTTagCompound, force, doBackup) or @@READ_SUCCESS

	SaveAs: (path = @fpath) =>
		@NBTTagCompound\AddTag(key, @SerealizeValue(key)) for key, val in pairs @dataTable
		buf = DLib.BytesBuffer()
		@NBTTagCompound\WriteFile(buf)
		stream = file.Open(path, 'wb', 'DATA')
		buf\ToFileStream(stream)
		stream\Flush()
		stream\Close()
		return buf

	SavePreview: (path = @preview) =>
		buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		buildingModel\SetNoDraw(true)
		buildingModel\SetModelScale(0.9)
		model = ClientsideModel('models/ppm/player_default_base_new_nj.mdl')

		with model
			\SetNoDraw(true)
			data = @CreateCustomController(model)
			.__PPM2_PonyData = data
			ctrl = data\GetRenderController()

			if bg = data\GetBodygroupController()
				bg\ApplyBodygroups()

			with model\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, model, data)
				\Think(true)

			\SetSequence(22)
			\FrameAdvance(0)

		timer.Simple 0.5, ->
			renderTarget = GetRenderTarget('ppm2_save_preview_generate2', 1024, 1024, false)
			renderTarget\Download()
			render.PushRenderTarget(renderTarget)
			--render.SuppressEngineLighting(true)
			render.Clear(0, 0, 0, 255, true, true)
			cam.Start3D(Vector(49.373046875, -35.021484375, 58.332901000977), Angle(0, 141, 0), 90, 0, 0, 1024, 1024)

			buildingModel\DrawModel()

			with model
				data = .__PPM2_PonyData
				ctrl = data\GetRenderController()

				if bg = data\GetBodygroupController()
					bg\ApplyBodygroups()

				with model\PPMBonesModifier()
					\ResetBones()
					hook.Call('PPM2.SetupBones', nil, model, data)
					\Think(true)

				with ctrl
					\DrawModels()
					\HideModels(true)
					\PreDraw(model, true)

				\DrawModel()
				ctrl\PostDraw(model, true)

			cam.End3D()

			data = render.Capture({
				format: 'png'
				x: 0
				y: 0
				w: 1024
				h: 1024
				alpha: false
			})

			model\Remove()
			buildingModel\Remove()

			file.Write(path, data)

			--render.SuppressEngineLighting(false)
			render.PopRenderTarget()

	Save: (doBackup = true, preview = true) =>
		if doBackup and @exists
			fRead = file.Read(@fpath, 'DATA')
			file.Write(@fpathBackup, fRead)
		buf = @SaveAs(@fpath)
		@SavePreview(@preview) if preview
		@exists = true
		return buf

PPM2.PonyDataInstance = PonyDataInstance

PPM2.MainDataInstance = nil
PPM2.GetMainData = ->
	if not PPM2.MainDataInstance
		PPM2.MainDataInstance = PonyDataInstance('_current', nil, true, true)
		if not PPM2.MainDataInstance\FileExists()
			PPM2.MainDataInstance\Save()
	return PPM2.MainDataInstance

--
-- Copyright (C) 2017-2018 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.
--

file.CreateDir('ppm2')
file.CreateDir('ppm2/backups')
file.CreateDir('ppm2/thumbnails')

for ffind in *file.Find('ppm2/*.txt', 'DATA')
	fTarget = ffind\sub(1, -5)
	-- maybe joined server with old ppm2 and new clear _current was generated
	if not file.Exists('ppm2/' .. fTarget .. '.dat', 'DATA')
		fRead = file.Read('ppm2/' .. ffind, 'DATA')
		json = util.JSONToTable(fRead)
		if json
			TagCompound = DLib.NBT.TagCompound()
			for key, value in pairs json
				switch type(value)
					when 'string'
						TagCompound\AddString(key, value)
					when 'number'
						TagCompound\AddFloat(key, value)
					when 'boolean'
						TagCompound\AddByte(key, value and 1 or 0)
					when 'table'
						-- assume color
						TagCompound\AddByteArray(key, {value.r - 128, value.g - 128, value.b - 128, value.a - 128}) if value.r and value.g and value.b and value.a
					else
						error(type(value))
			buf = DLib.BytesBuffer()
			TagCompound\WriteFile(buf)
			stream = file.Open('ppm2/' .. fTarget .. '.dat', 'wb', 'DATA')
			buf\ToFileStream(stream)
			stream\Flush()
			stream\Close()
	--file.Delete('ppm2/' .. ffind)

class PonyDataInstance
	@DATA_DIR = "ppm2/"
	@DATA_DIR_BACKUP = "ppm2/backups/"

	@FindFiles = =>
		output = [str\sub(1, #str - 4) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@FindInstances = =>
		output = [@(str\sub(1, #str - 4)) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@PONY_DATA = PPM2.PonyDataRegistry

	@PONY_DATA_MAPPING = {getFunc\lower(), key for key, {:getFunc} in pairs @PONY_DATA}
	@PONY_DATA_MAPPING[key] = key for key, value in pairs @PONY_DATA

	for key, data in pairs @PONY_DATA
		continue unless data.enum
		data.enum = [arg\upper() for arg in *data.enum]
		data.enumMapping = {}
		data.enumMappingBackward = {}
		i = -1
		for enumVal in *data.enum
			i += 1
			data.enumMapping[i] = enumVal
			data.enumMappingBackward[enumVal] = i
	for key, {:getFunc, :fix, :enumMappingBackward, :enumMapping, :enum, :min, :max, :default} in pairs @PONY_DATA
		@__base["Get#{getFunc}"] = => @dataTable[key]
		@__base["GetMin#{getFunc}"] = => min if min
		@__base["GetMax#{getFunc}"] = => max if max
		@__base["Enum#{getFunc}"] = => enum if enum
		@__base["Get#{getFunc}Types"] = => enum if enum

		@["GetMin#{getFunc}"] = => min if min
		@["GetMax#{getFunc}"] = => max if max
		@["GetDefault#{getFunc}"] = default
		@["GetEnum#{getFunc}"] = => enum if enum
		@["Enum#{getFunc}"] = => enum if enum

		if enumMapping
			@__base["Get#{getFunc}Enum"] = => enumMapping[@dataTable[key]] or enumMapping[0] or @dataTable[key]
			@__base["GetEnum#{getFunc}"] = @__base["Get#{getFunc}Enum"]
		@__base["Reset#{getFunc}"] = => @["Set#{getFunc}"](@, default())
		@__base["Set#{getFunc}"] = (val = defValue, ...) =>
			if type(val) == 'string' and enumMappingBackward
				newVal = enumMappingBackward[val\upper()]
				val = newVal if newVal
			newVal = fix(val)
			oldVal = @dataTable[key]
			@dataTable[key] = newVal
			@ValueChanges(key, oldVal, newVal, ...)

	WriteNetworkData: =>
		for {:strName, :writeFunc, :getName, :defValue} in *PPM2.NetworkedPonyData.NW_Vars
			if @["Get#{getName}"]
				writeFunc(@["Get#{getName}"](@))
			else
				writeFunc(defValue)

	Copy: (fileName = @filename) =>
		copyOfData = {}
		for key, val in pairs @dataTable
			switch type(val)
				when 'number'
					copyOfData[key] = val
				when 'string'
					copyOfData[key] = val
				when 'boolean'
					copyOfData[key] = val
				when 'table'
					{:r, :g, :b} = val
					if r and g and b
						copyOfData[key] = Color(r, g, b)
		newData = @@(fileName, copyOfData, false)
		return newData
	CreateCustomNetworkObject: (goingToNetwork = false, ply = LocalPlayer(), ...) =>
		newData = PPM2.NetworkedPonyData(nil, ply)
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(ply)
		@ApplyDataToObject(newData, ...)
		return newData
	CreateNetworkObject: (goingToNetwork = true, ...) =>
		newData = PPM2.NetworkedPonyData(nil, LocalPlayer())
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(LocalPlayer())
		@ApplyDataToObject(newData, ...)
		return newData
	ApplyDataToObject: (target, ...) =>
		for key, value in pairs @GetAsNetworked()
			error("Attempt to apply data to object #{target} at unknown index #{key}!") if not target["Set#{key}"]
			target["Set#{key}"](target, value, ...)
	UpdateController: (...) => @ApplyDataToObject(@nwObj, ...)
	CreateController: (...) => @CreateNetworkObject(false, ...)
	CreateCustomController: (...) => @CreateCustomNetworkObject(false, ...)

	new: (filename, data, readIfExists = true, force = false, doBackup = true) =>
		@SetFilename(filename)
		@NBTTagCompound = DLib.NBT.TagCompound()
		@updateNWObject = true
		@networkNWObject = true
		@valid = @isOpen
		@rawData = data
		@dataTable = {k, default() for k, {:default} in pairs @@PONY_DATA}
		@saveOnChange = true
		if data
			@SetupData(data, true)
		elseif @exists and readIfExists
			@ReadFromDisk(force, doBackup)

	Reset: => @['Reset' .. getFunc](@) for k, {:getFunc} in pairs @@PONY_DATA

	@ERR_MISSING_PARAMETER = 4
	@ERR_MISSING_CONTENT = 5

	GetSaveOnChange: => @saveOnChange
	SaveOnChange: => @saveOnChange
	SetSaveOnChange: (val = true) => @saveOnChange = val
	GetValueFromNBT: (mapData, value) =>
		if mapData.enum and type(value) == 'string'
			mapData.fix(mapData.enumMappingBackward[value\upper()])
		elseif mapData.type == 'COLOR'
			if value.r and value.g and value.b and value.a
				mapData.fix(Color(value))
			else
				mapData.fix(Color(value[1] + 128, value[2] + 128, value[3] + 128, value[4] + 128))
		elseif mapData.type == 'BOOLEAN'
			if type(value) == 'boolean'
				mapData.fix(value)
			else
				mapData.fix(value == 1)
		else
			mapData.fix(value)

	SetupData: (data = @NBTTagCompound, force = false, doBackup = false) =>
		if type(data) == 'NBTCompound'
			data = data\GetValue()
		if doBackup or not force
			makeBackup = false
			for key, value2 in pairs(data)
				key = key\lower()
				map = @@PONY_DATA_MAPPING[key]
				if map
					mapData = @@PONY_DATA[map]
					value = @GetValueFromNBT(mapData, value2)
					if mapData.enum
						if type(value) == 'string' and not mapData.enumMappingBackward[value\upper()] or type(value) == 'number' and not mapData.enumMapping[value]
							return @@ERR_MISSING_CONTENT if not force
							makeBackup = true
							break
			if doBackup and makeBackup and @exists
				bkName = "#{@@DATA_DIR_BACKUP}#{@filename}_bak_#{os.date('%S_%M_%H-%d_%m_%Y', os.time())}.dat"
				fRead = file.Read(@fpath, 'DATA')
				file.Write(bkName, fRead)

		for key, value2 in pairs(data)
			key = key\lower()
			map = @@PONY_DATA_MAPPING[key]
			continue unless map
			mapData = @@PONY_DATA[map]
			@dataTable[key] = @GetValueFromNBT(mapData, value2)
	ValueChanges: (key, oldVal, newVal, saveNow = @exists and @saveOnChange) =>
		if @nwObj and @updateNWObject
			{:getFunc} = @@PONY_DATA[key]
			@nwObj["Set#{getFunc}"](@nwObj, newVal, @networkNWObject)
		@Save() if saveNow
	SetFilename: (filename) =>
		@filename = filename
		@filenameFull = "#{filename}.dat"
		@fpath = "#{@@DATA_DIR}#{filename}.dat"
		@preview = "#{@@DATA_DIR}thumbnails/#{filename}.png"
		@fpathBackup = "#{@@DATA_DIR}#{filename}.bak.dat"
		@fpathFull = "data/#{@@DATA_DIR}#{filename}.dat"
		@isOpen = @filename ~= nil
		@exists = file.Exists(@fpath, 'DATA')
		return @exists
	SetNetworkData: (nwObj) => @nwObj = nwObj
	SetPonyData: (nwObj) => @nwObj = nwObj
	SetPonyDataController: (nwObj) => @nwObj = nwObj
	SetPonyController: (nwObj) => @nwObj = nwObj
	SetController: (nwObj) => @nwObj = nwObj
	SetDataController: (nwObj) => @nwObj = nwObj

	SetNetworkOnChange: (newVal = true) => @networkNWObject = newVal
	SetUpdateOnChange: (newVal = true) => @updateNWObject = newVal

	GetNetworkOnChange: => @networkNWObject
	GetUpdateOnChange: => @updateNWObject

	GetNetworkData: => @nwObj
	GetPonyData: => @nwObj
	GetPonyDataController: => @nwObj
	GetPonyController: => @nwObj
	GetController: => @nwObj
	GetDataController: => @nwObj

	IsValid: => @valid
	Exists: => @exists
	FileExists: => @exists
	IsExists: => @exists
	GetFileName: => @filename
	GetFilename: => @filename
	GetFileNameFull: => @filenameFull
	GetFilenameFull: => @filenameFull
	GetFilePath: => @fpath
	GetFullFilePath: => @fpathFull
	SerealizeValue: (valID = '') =>
		map = @@PONY_DATA[valID]
		return unless map
		val = @dataTable[valID]
		if map.enum
			return DLib.NBT.TagString(map.enumMapping[val] or map.enumMapping[map.default()])
		elseif map.serealize
			return map.serealize(val)
		else
			switch map.type
				when 'INT'
					return DLib.NBT.TagInt(val)
				when 'FLOAT'
					return DLib.NBT.TagFloat(val)
				when 'URL'
					return DLib.NBT.TagString(val)
				when 'BOOLEAN'
					return DLib.NBT.TagByte(val and 1 or 0)
				when 'COLOR'
					return DLib.NBT.TagByteArray({val.r - 128, val.g - 128, val.b - 128, val.a - 128})
	GetAsNetworked: => {getFunc, @dataTable[k] for k, {:getFunc} in pairs @@PONY_DATA}

	@READ_SUCCESS = 0
	@ERR_FILE_NOT_EXISTS = 1
	@ERR_FILE_EMPTY = 2
	@ERR_FILE_CORRUPT = 3
	ReadFromDisk: (force = false, doBackup = true) =>
		return @@ERR_FILE_NOT_EXISTS unless @exists
		fRead = file.Read(@fpath, 'DATA')
		return @@ERR_FILE_EMPTY if not fRead or fRead == ''
		@NBTTagCompound\ReadFile(DLib.BytesBuffer(fRead))
		return @SetupData(@NBTTagCompound, force, doBackup) or @@READ_SUCCESS

	SaveAs: (path = @fpath) =>
		@NBTTagCompound\AddTag(key, @SerealizeValue(key)) for key, val in pairs @dataTable
		buf = DLib.BytesBuffer()
		@NBTTagCompound\WriteFile(buf)
		stream = file.Open(path, 'wb', 'DATA')
		buf\ToFileStream(stream)
		stream\Flush()
		stream\Close()
		return buf

	SavePreview: (path = @preview) =>
		buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		buildingModel\SetNoDraw(true)
		buildingModel\SetModelScale(0.9)
		model = ClientsideModel('models/ppm/player_default_base_new_nj.mdl')

		with model
			\SetNoDraw(true)
			data = @CreateCustomController(model)
			.__PPM2_PonyData = data
			ctrl = data\GetRenderController()

			if bg = data\GetBodygroupController()
				bg\ApplyBodygroups()

			with model\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, model, data)
				\Think(true)

			\SetSequence(22)
			\FrameAdvance(0)

		timer.Simple 0.5, ->
			renderTarget = GetRenderTarget('ppm2_save_preview_generate2', 1024, 1024, false)
			renderTarget\Download()
			render.PushRenderTarget(renderTarget)
			--render.SuppressEngineLighting(true)
			render.Clear(0, 0, 0, 255, true, true)
			cam.Start3D(Vector(49.373046875, -35.021484375, 58.332901000977), Angle(0, 141, 0), 90, 0, 0, 1024, 1024)

			buildingModel\DrawModel()

			with model
				data = .__PPM2_PonyData
				ctrl = data\GetRenderController()

				if bg = data\GetBodygroupController()
					bg\ApplyBodygroups()

				with model\PPMBonesModifier()
					\ResetBones()
					hook.Call('PPM2.SetupBones', nil, model, data)
					\Think(true)

				with ctrl
					\DrawModels()
					\HideModels(true)
					\PreDraw(model, true)

				\DrawModel()
				ctrl\PostDraw(model, true)

			cam.End3D()

			data = render.Capture({
				format: 'png'
				x: 0
				y: 0
				w: 1024
				h: 1024
				alpha: false
			})

			model\Remove()
			buildingModel\Remove()

			file.Write(path, data)

			--render.SuppressEngineLighting(false)
			render.PopRenderTarget()

	Save: (doBackup = true, preview = true) =>
		if doBackup and @exists
			fRead = file.Read(@fpath, 'DATA')
			file.Write(@fpathBackup, fRead)
		buf = @SaveAs(@fpath)
		@SavePreview(@preview) if preview
		@exists = true
		return buf

PPM2.PonyDataInstance = PonyDataInstance

PPM2.MainDataInstance = nil
PPM2.GetMainData = ->
	if not PPM2.MainDataInstance
		PPM2.MainDataInstance = PonyDataInstance('_current', nil, true, true)
		if not PPM2.MainDataInstance\FileExists()
			PPM2.MainDataInstance\Save()
	return PPM2.MainDataInstance