Re-parent clothes model on update

Re-parent clothes model on update

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

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

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

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

import PPM2, ents, LocalPlayer, SERVER, NULL, CLIENT, EF_BONEMERGE from _G

ALLOW_TO_MODIFY_SCALE = PPM2.ALLOW_TO_MODIFY_SCALE

TRACKED_ENTS = {}
TRACKED_ENTS_FRAME = 0
ents_GetAll = ->
	if TRACKED_ENTS_FRAME ~= FrameNumberL()
		TRACKED_ENTS = ents.GetAll()
		TRACKED_ENTS_FRAME = FrameNumberL()
	return TRACKED_ENTS

if CLIENT
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyLegsModel or ent.isPonyPropModel
			ent\Remove()

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

diff = (num) ->
	num = num\abs()
	num < 0.99 or num > 1.01

vector_one = Vector(1, 1, 1)
empty = {}

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

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

	@BODYGROUP_SKELETON = 0
	@BODYGROUP_GENDER = 1
	@BODYGROUP_HORN = 2
	@BODYGROUP_WINGS = 3
	@BODYGROUP_MANE_UPPER = 4
	@BODYGROUP_MANE_LOWER = 5
	@BODYGROUP_TAIL = 6
	@BODYGROUP_CMARK = 7
	@BODYGROUP_EYELASH = 8

	@NEXT_OBJ_ID = 0

	@COOLDOWN_TIME = 5
	@COOLDOWN_MAX_COUNT = 4

	@BONE_MANE_1 = 'Mane01'
	@BONE_MANE_2 = 'Mane02'
	@BONE_MANE_3 = 'Mane03'
	@BONE_MANE_4 = 'Mane04'
	@BONE_MANE_5 = 'Mane05'
	@BONE_MANE_6 = 'Mane06'
	@BONE_MANE_7 = 'Mane07'
	@BONE_MANE_8 = 'Mane03_tip'

	@BONE_TAIL_1 = 'Tail01'
	@BONE_TAIL_2 = 'Tail02'
	@BONE_TAIL_3 = 'Tail03'

	@BONE_SPINE_ROOT = 'LrigPelvis'
	@BONE_SPINE = 'LrigSpine2'

	@_BONE_REMAP_R = {
		'BONE_SPINE_ROOT'
		'BONE_TAIL_1', 'BONE_TAIL_2', 'BONE_TAIL_3'
		'BONE_SPINE', 'BONE_MANE_1', 'BONE_MANE_2'
		'BONE_MANE_3', 'BONE_MANE_4', 'BONE_MANE_5'
		'BONE_MANE_6', 'BONE_MANE_7', 'BONE_MANE_8'
	}

	Remap: =>
		@validSkeleton = true

		for _, name in ipairs @@_BONE_REMAP_R
			@[name] = @GetEntity()\LookupBone(@@[name])

			if not @[name]
				@validSkeleton = false

	new: (controller) =>
		super(controller)
		@isValid = true
		@objID = @@NEXT_OBJ_ID
		@@NEXT_OBJ_ID += 1
		@lastPAC3BoneReset = 0
		@Remap()

		PPM2.DebugPrint('Created new bodygroups controller for ', @GetEntity(), ' as part of ', controller, '; internal ID is ', @objID)

	IsValid: => @isValid

	@ATTACHMENT_EYES = 4
	@ATTACHMENT_EYES_NAME = 'eyes'

	CreateGenericModel: (force, fModelIndex, mIndex, mName, mTranslateModelName, mCallType) =>
		return @[mIndex] or NULL if SERVER or not @isValid or not IsValid(@GetEntity()) or not force and @GetEntity()\IsDormant() or not @GetEntity()\IsPony()
		return @[mIndex] if IsValid(@[mIndex])

		for _, ent in ipairs ents_GetAll()
			if ent.isPonyPropModel and ent[fModelIndex] and ent.manePlayer == @GetEntity()
				@[mIndex] = ent
				@SetData(mName, @[mIndex])
				PPM2.DebugPrint('Resuing ', @[mIndex], ' as ', mName, ' for ', @GetEntity())
				return ent

		model, modelID, bodygroupID = mTranslateModelName(@GrabData(mCallType)) if mCallType
		model = mTranslateModelName() if not mCallType
		with @[mIndex] = ClientsideModel(model)
			.isPonyPropModel = true
			.manePlayer = @GetEntity()
			\DrawShadow(true)
			\SetPos(@GetEntity()\EyePos())
			\SetBodygroup(1, bodygroupID) if bodygroupID
			\Spawn()
			\Activate()
			\SetNoDraw(true)
			\SetParent(@GetEntity())
			\AddEffects(EF_BONEMERGE)

		@[mIndex][fModelIndex] = true

		PPM2.DebugPrint('Creating new ', mName, ' for ', @GetEntity(), ' as ', @[mIndex])
		@SetData(mName, @[mIndex])
		return @[mIndex]

	UpdateGenericModel: (force, fcallNone, mIndex, mName, mTranslateModelName, mCallType) =>
		return @[mIndex] or NULL if SERVER or not @isValid or not IsValid(@GetEntity()) or not force and @GetEntity()\IsDormant() or not @GetEntity()\IsPony()
		@[mIndex] = @[fcallNone](@, force) if not IsValid(@[mIndex])
		return NULL if not IsValid(@[mIndex])

		model, modelID, bodygroupID = mTranslateModelName(@GrabData(mCallType)) if mCallType
		model, modelID, bodygroupID = mTranslateModelName() if not mCallType
		with @[mIndex]
			\SetModel(model) if model ~= \GetModel()
			\SetBodygroup(1, bodygroupID) if bodygroupID and \GetBodygroup(1) ~= bodygroupID
			\SetParent(@GetEntity()) if \GetParent() ~= @GetEntity() and IsValid(@GetEntity())

		@SetData(mName, @[mIndex])
		return @[mIndex]

	UpdateHornModel: (force = false) => @UpdateGenericModel(force, 'CreateHornModel', 'hornModel', 'HornModel', PPM2.GetHornModelName, 'NewHornType')
	UpdateSocksModel: (force = false) => @UpdateGenericModel(force, 'CreateSocksModel', 'socksModel', 'SocksModel', @_SocksModelName)
	UpdateNewSocksModel: (force = false) => @UpdateGenericModel(force, 'CreateNewSocksModel', 'newSocksModel', 'NewSocksModel', @_SocksModelName)

	_SocksModelName: => 'models/props_pony/ppm/cosmetics/ppm_socks.mdl'
	_NewSocksModelName: => 'models/props_pony/ppm/cosmetics/ppm2_socks.mdl'
	_ClothesModelname: => 'models/ppm/player_default_clothes1.mdl'

	CreateSocksModel: (force = false) => @CreateGenericModel(force, 'isSocks', 'socksModel', 'SocksModel', @_SocksModelName)
	CreateNewSocksModel: (force = false) => @CreateGenericModel(force, 'isNewSocks', 'newSocksModel', 'NewSocksModel', @_NewSocksModelName)
	CreateHornModel: (force = false) => @CreateGenericModel(force, 'isHornModel', 'hornModel', 'HornModel', PPM2.GetHornModelName, 'NewHornType')
	CreateClothesModel: (force = false) => @CreateGenericModel(force, 'isClothesModel', 'clothesModel', 'ClothesModel', @_ClothesModelname)

	CreateClothesModelIfNotExist: (force = false) =>
		return @clothesModel if IsValid(@clothesModel)
		return @CreateClothesModel(force)

	CreateNewSocksModelIfNotExists: (force = false) =>
		return @newSocksModel if IsValid(@newSocksModel)
		return @CreateNewSocksModel(force)

	CreateSocksModelIfNotExists: (force = false) =>
		return @socksModel if IsValid(@socksModel)
		return @CreateSocksModel(force)

	CreateHornModelIfNotExists: (force = false) =>
		return @hornModel if IsValid(@hornModel)
		return @CreateHornModel(force)

	UpdateClothesModel: (force = false) =>
		@CreateClothesModelIfNotExist(force)
		return NULL if not IsValid(@clothesModel)
		@clothesModel\SetParent(@GetEntity()) if IsValid(@GetEntity())

		with @clothesModel
			\SetBodygroup(1, @GrabData('HeadClothes'))
			\SetBodygroup(2, @GrabData('NeckClothes'))
			\SetBodygroup(3, @GrabData('BodyClothes'))
			\SetBodygroup(8, @GrabData('EyeClothes'))

	MergeModels: (targetEnt = NULL) =>
		return if SERVER or not @isValid or not IsValid(targetEnt)

		socks = @CreateSocksModelIfNotExists(true) if @GrabData('SocksAsModel')
		socks2 = @CreateNewSocksModelIfNotExists(true) if @GrabData('SocksAsNewModel')
		horn = @CreateHornModelIfNotExists(true) if @GrabData('UseNewHorn')
		clothesModel = @CreateClothesModelIfNotExist(true)

		if IsValid(clothesModel)
			clothesModel\SetParent(targetEnt)

		if IsValid(horn)
			horn\SetParent(targetEnt)

		if IsValid(socks)
			socks\SetParent(targetEnt)

		if IsValid(socks2)
			socks2\SetParent(targetEnt)

	GetSocks: => @socksModel or NULL

	ApplyRace: =>
		return unless @isValid
		return NULL if not IsValid(@GetEntity())
		with @GetEntity()
			switch @GrabData('Race')
				when PPM2.RACE_EARTH
					\SetBodygroup(@@BODYGROUP_HORN, 1)
					\SetBodygroup(@@BODYGROUP_WINGS, 1)
				when PPM2.RACE_PEGASUS
					\SetBodygroup(@@BODYGROUP_HORN, 1)
					\SetBodygroup(@@BODYGROUP_WINGS, 0)
				when PPM2.RACE_UNICORN
					\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
					\SetBodygroup(@@BODYGROUP_WINGS, 1)
				when PPM2.RACE_ALICORN
					\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
					\SetBodygroup(@@BODYGROUP_WINGS, 0)

	ResetTail: =>
		return if not CLIENT
		return if not @validSkeleton
		with @GetEntity()
			\ManipulateBoneScale(@BONE_TAIL_1, vector_one)
			\ManipulateBoneScale(@BONE_TAIL_2, vector_one)
			\ManipulateBoneScale(@BONE_TAIL_3, vector_one)
			\ManipulateBoneAngles(@BONE_TAIL_1, Angle(0, 0, 0))
			\ManipulateBoneAngles(@BONE_TAIL_2, Angle(0, 0, 0))
			\ManipulateBoneAngles(@BONE_TAIL_3, Angle(0, 0, 0))
			\ManipulateBonePosition(@BONE_TAIL_1, vector_origin)
			\ManipulateBonePosition(@BONE_TAIL_2, vector_origin)
			\ManipulateBonePosition(@BONE_TAIL_3, vector_origin)

	ResetBack: =>
		return if not CLIENT
		return if not @validSkeleton
		with @GetEntity()
			\ManipulateBoneScale(@BONE_SPINE_ROOT, vector_one)
			\ManipulateBoneScale(@BONE_SPINE, vector_one)
			\ManipulateBoneAngles(@BONE_SPINE_ROOT, Angle(0, 0, 0))
			\ManipulateBoneAngles(@BONE_SPINE, Angle(0, 0, 0))
			\ManipulateBonePosition(@BONE_SPINE_ROOT, vector_origin)
			\ManipulateBonePosition(@BONE_SPINE, vector_origin)

	ResetMane: =>
		return if not CLIENT
		return if not @validSkeleton

		vec1, ang, vec2 = vector_one, Angle(0, 0, 0), vector_origin

		with @GetEntity()
			for i = 1, 7
				\ManipulateBoneScale(@['BONE_MANE_' .. i], vec1)
				\ManipulateBoneAngles(@['BONE_MANE_' .. i], ang)
				\ManipulateBonePosition(@['BONE_MANE_' .. i], vec2)

	ResetBodygroups: =>
		return unless @isValid
		return unless IsValid(@GetEntity())
		return unless @GetEntity()\GetBodyGroups()
		return if not @validSkeleton

		for _, grp in ipairs @GetEntity()\GetBodyGroups()
			@GetEntity()\SetBodygroup(grp.id, 0)

		if @lastPAC3BoneReset < RealTimeL()
			@ResetTail()
			@ResetMane()
			@ResetBack()

	Reset: => @ResetBodygroups()
	RemoveModels: =>
		@socksModel\Remove() if IsValid(@socksModel)
		@newSocksModel\Remove() if IsValid(@newSocksModel)

	UpdateTailSize: (ent = @GetEntity()) =>
		return if not CLIENT
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		return if not @validSkeleton
		size = @GrabData('TailSize')
		size *= @GrabData('PonySize') if not ent\IsRagdoll() and not ent\IsNJPony()
		return if not diff(size)
		vecTail = vector_one * size
		vecTailPos = Vector((size - 1) * 8, 0, 0)

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or empty

		with ent
			\ManipulateBoneScale(@BONE_TAIL_1, vecTail)
			\ManipulateBoneScale(@BONE_TAIL_2, vecTail)
			\ManipulateBoneScale(@BONE_TAIL_3, vecTail)

			--\ManipulateBonePosition(@BONE_TAIL_1, vecTail + (boneAnimTable[@BONE_TAIL_1] or vector_origin))
			\ManipulateBonePosition(@BONE_TAIL_2, vecTailPos + (boneAnimTable[@BONE_TAIL_2] or vector_origin))
			\ManipulateBonePosition(@BONE_TAIL_3, vecTailPos + (boneAnimTable[@BONE_TAIL_3] or vector_origin))

	UpdateManeSize: (ent = @GetEntity()) =>
		return if not CLIENT
		return if ent\IsRagdoll()
		return if ent\IsNJPony()
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		return if not @validSkeleton
		size = @GrabData('PonySize')
		return if not diff(size)
		vecMane = vector_one * size

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or empty

		with ent
			\ManipulateBoneScale(@BONE_MANE_1, vecMane)
			\ManipulateBoneScale(@BONE_MANE_2, vecMane)
			\ManipulateBoneScale(@BONE_MANE_3, vecMane)
			\ManipulateBoneScale(@BONE_MANE_4, vecMane)
			\ManipulateBoneScale(@BONE_MANE_5, vecMane)
			\ManipulateBoneScale(@BONE_MANE_6, vecMane)
			\ManipulateBoneScale(@BONE_MANE_7, vecMane)
			\ManipulateBoneScale(@BONE_MANE_8, vecMane)

			\ManipulateBonePosition(@BONE_MANE_1, Vector(-(size - 1) * 4, (1 - size) * 3, 0) + (boneAnimTable[@BONE_MANE_1] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_2, Vector(-(size - 1) * 4, (size - 1) * 2, 1) + (boneAnimTable[@BONE_MANE_2] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_3, Vector((size - 1) * 2, 0, 0) +               (boneAnimTable[@BONE_MANE_3] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_4, Vector(1 - size, (1 - size) * 4, 1 - size) + (boneAnimTable[@BONE_MANE_4] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_5, Vector((size - 1) * 4, (1 - size) * 2, (size - 1) * 3) + (boneAnimTable[@BONE_MANE_5] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_6, Vector(0, 0, -(size - 1) * 2) +              (boneAnimTable[@BONE_MANE_6] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_7, Vector(0, 0, -(size - 1) * 2) +              (boneAnimTable[@BONE_MANE_7] or vector_origin))

	UpdateBack: (ent = @GetEntity()) =>
		return if not CLIENT
		return if ent\IsRagdoll()
		return if ent\IsNJPony()
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		return if not @validSkeleton
		return if not diff(@GrabData('BackSize'))

		vecModify = Vector(-(@GrabData('BackSize') - 1) * 2, 0, 0)
		vecModify2 = Vector((@GrabData('BackSize') - 1) * 5, 0, 0)

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or empty

		with ent
			\ManipulateBonePosition(@BONE_SPINE_ROOT, vecModify + (boneAnimTable[@BONE_SPINE_ROOT] or vector_origin))
			\ManipulateBonePosition(@BONE_SPINE, vecModify2 + (boneAnimTable[@BONE_SPINE] or vector_origin))

	SlowUpdate: (createModels = CLIENT, ent = @GetEntity(), force = false) =>
		return if not IsValid(ent)
		return if not ent\IsPony()
		with ent
			\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GrabData('ManeType'))
			\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GrabData('ManeTypeLower'))
			\SetBodygroup(@@BODYGROUP_TAIL, @GrabData('TailType'))
			\SetBodygroup(@@BODYGROUP_EYELASH, @GrabData('EyelashType'))
			\SetBodygroup(@@BODYGROUP_GENDER, @GrabData('Gender'))

		@ApplyRace()
		if createModels
			@UpdateClothesModel()
			@UpdateHornModel(force) if @GrabData('UseNewHorn')
			@UpdateSocksModel(force) if @GrabData('SocksAsModel')
			@UpdateNewSocksModel(force) if @GrabData('SocksAsNewModel')

	ApplyBodygroups: (createModels = CLIENT, force = false) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@ResetBodygroups()
		return if not @GetEntity()\IsPony()
		@SlowUpdate(createModels, force)

	Remove: =>
		@RemoveModels()
		@ResetBodygroups()
		@isValid = false

	DataChanges: (state) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@Remap()

		switch state\GetKey()
			when 'HeadClothes', 'NeckClothes', 'BodyClothes', 'EyeClothes'
				@UpdateClothesModel()
			when 'ManeType'
				@GetEntity()\SetBodygroup(@@BODYGROUP_MANE_UPPER, state\GetValue())
			when 'ManeTypeLower'
				@GetEntity()\SetBodygroup(@@BODYGROUP_MANE_LOWER, state\GetValue())
			when 'TailType'
				@GetEntity()\SetBodygroup(@@BODYGROUP_TAIL, state\GetValue())
			when 'TailSize', 'PonySize'
				@UpdateTailSize()
			when 'EyelashType'
				@GetEntity()\SetBodygroup(@@BODYGROUP_EYELASH, state\GetValue())
			when 'Gender'
				@GetEntity()\SetBodygroup(@@BODYGROUP_GENDER, state\GetValue())
			when 'SocksAsModel'
				if state\GetValue()
					@UpdateSocksModel()
				else
					@socksModel\Remove() if IsValid(@socksModel)
			when 'UseNewHorn'
				if state\GetValue()
					@CreateHornModelIfNotExists()
				else
					@hornModel\Remove() if IsValid(@hornModel)

				@ApplyRace()
			when 'SocksAsNewModel'
				if state\GetValue()
					@UpdateNewSocksModel()
				else
					@newSocksModel\Remove() if IsValid(@newSocksModel)
			when 'Race'
				@ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
	@MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

	new: (...) => super(...)

	ApplyRace: =>
		return unless @isValid
		switch @GrabData('Race')
			when PPM2.RACE_EARTH
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 1)
			when PPM2.RACE_PEGASUS
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 0)
			when PPM2.RACE_UNICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 0)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 1)
			when PPM2.RACE_ALICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 2)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 3)

-- 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   right_hand
-- 43   wing_l
-- 44   wing_r
-- 45   Tail01
-- 46   Tail02
-- 47   Tail03
-- 48   wing_l_bat
-- 49   wing_r_bat
-- 50   wing_open_l
-- 51   wing_open_r

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

	@BODYGROUP_SKELETON = 0
	@BODYGROUP_GENDER = -1
	@BODYGROUP_HORN = 1
	@BODYGROUP_WINGS = 2

	@EAR_L = 'Ear_L'
	@EAR_R = 'Ear_R'

	@WING_LEFT_1 = 'wing_l'
	@WING_LEFT_2 = 'wing_l_bat'
	@WING_RIGHT_1 = 'wing_r'
	@WING_RIGHT_2 = 'wing_r_bat'

	@WING_OPEN_LEFT = 'wing_open_l'
	@WING_OPEN_RIGHT = 'wing_open_r'

	@BONE_SPINE = 'LrigSpine1'

	@_BONE_REMAP = {
		'EAR_L', 'EAR_R'
		'WING_LEFT_1', 'WING_LEFT_2', 'WING_RIGHT_1'
		'WING_RIGHT_2', 'WING_OPEN_LEFT', 'WING_OPEN_RIGHT'
	}

	Remap: =>
		super!

		for _, name in ipairs @@_BONE_REMAP
			@[name] = @GetEntity()\LookupBone(@@[name])

			if not @[name]
				@validSkeleton = false

	CreateUpperManeModel: (force = false) => @CreateGenericModel(force, 'isUpperMane', 'maneModelUpper', 'UpperManeModel', PPM2.GetUpperManeModelName, 'ManeTypeNew')
	CreateLowerManeModel: (force = false) => @CreateGenericModel(force, 'isLowerMane', 'maneModelLower', 'LowerManeModel', PPM2.GetLowerManeModelName, 'ManeTypeLowerNew')
	CreateTailModel: (force = false) => @CreateGenericModel(force, 'isTailModel', 'tailModel', 'TailModel', PPM2.GetTailModelName, 'TailTypeNew')

	CreateUpperManeModelIfNotExists: (force = false) =>
		return @maneModelUpper if IsValid(@maneModelUpper)
		return @CreateUpperManeModel()

	CreateLowerManeModelIfNotExists: (force = false) =>
		return @maneModelLower if IsValid(@maneModelLower)
		return @CreateLowerManeModel()

	CreateTailModelIfNotExists: (force = false) =>
		return @tailModel if IsValid(@tailModel)
		return @CreateTailModel()

	UpdateUpperMane: (force = false) => @UpdateGenericModel(force, 'CreateUpperManeModel', 'maneModelUpper', 'UpperManeModel', PPM2.GetUpperManeModelName, 'ManeTypeNew')
	UpdateLowerMane: (force = false) => @UpdateGenericModel(force, 'CreateLowerManeModel', 'maneModelLower', 'LowerManeModel', PPM2.GetLowerManeModelName, 'ManeTypeLowerNew')
	UpdateTailModel: (force = false) => @UpdateGenericModel(force, 'CreateTailModel', 'tailModel', 'TailModel', PPM2.GetTailModelName, 'TailTypeNew')

	GetUpperMane: => @maneModelUpper or NULL
	GetLowerMane: => @maneModelLower or NULL
	GetTail: => @tailModel or NULL

	MergeModels: (targetEnt = NULL) =>
		return unless @isValid
		super(targetEnt)
		return unless IsValid(targetEnt)
		for _, e in ipairs {@CreateUpperManeModelIfNotExists(true), @CreateLowerManeModelIfNotExists(true), @CreateTailModelIfNotExists(true)}
			e\SetParent(targetEnt) if IsValid(e)

	@FLEX_ID_EYELASHES = 16
	@FLEX_ID_MALE = 25
	@FLEX_ID_MALE_2 = 35
	@FLEX_ID_MALE_BODY = 36
	@FLEX_ID_BAT_PONY_EARS = 28
	@FLEX_ID_FANGS = 31
	@FLEX_ID_FANGS2 = 29
	@FLEX_ID_CLAW_TEETH = 30
	@FLEX_ID_HOOF_FLUFF = 26

	ResetWings: =>
		return if SERVER
		return if not @validSkeleton
		ang, vec1, vec2 = Angle(0, 0, 0), vector_one, vector_origin
		for _, wing in ipairs {@WING_LEFT_1, @WING_LEFT_2, @WING_RIGHT_1, @WING_RIGHT_2, @WING_OPEN_LEFT, @WING_OPEN_RIGHT}
			with @GetEntity()
				\ManipulateBoneAngles(wing, ang)
				\ManipulateBoneScale(wing, vec1)
				\ManipulateBonePosition(wing, vec2)

	UpdateWings: =>
		return if SERVER
		return if not @validSkeleton
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		left = @GrabData('LWingSize')

		if diff(left)
			left = left * vector_one
		else
			left = nil

		leftX = @GrabData('LWingX')
		leftY = @GrabData('LWingY')
		leftZ = @GrabData('LWingZ')
		right = @GrabData('RWingSize')

		if diff(right)
			right = right * vector_one
		else
			right = nil

		rightX = @GrabData('RWingX')
		rightY = @GrabData('RWingY')
		rightZ = @GrabData('RWingZ')

		leftPos = Vector(leftX, leftY, leftZ) if diff(leftX) or diff(leftY) or diff(leftZ)
		rightPos = Vector(rightX, rightY, rightZ) if diff(rightX) or diff(rightY) or diff(rightZ)

		with @GetEntity()
			if left
				\ManipulateBoneScale(@WING_LEFT_1, left)
				\ManipulateBoneScale(@WING_LEFT_2, left)
				\ManipulateBoneScale(@WING_OPEN_LEFT, left)

			if right
				\ManipulateBoneScale(@WING_RIGHT_1, right)
				\ManipulateBoneScale(@WING_RIGHT_2, right)
				\ManipulateBoneScale(@WING_OPEN_RIGHT, right)

			if leftPos
				\ManipulateBonePosition(@WING_LEFT_1, leftPos)
				\ManipulateBonePosition(@WING_LEFT_2, leftPos)
				\ManipulateBonePosition(@WING_OPEN_LEFT, leftPos)

			if rightPos
				\ManipulateBonePosition(@WING_RIGHT_1, rightPos)
				\ManipulateBonePosition(@WING_RIGHT_2, rightPos)
				\ManipulateBonePosition(@WING_OPEN_RIGHT, rightPos)

	UpdateEars: =>
		size = @GrabData('EarsSize')
		if diff(size)
			vec = vector_one * size
			@GetEntity()\ManipulateBoneScale(@EAR_L, vec)
			@GetEntity()\ManipulateBoneScale(@EAR_R, vec)

	ResetEars: =>
		ang, vec1, vec2 = Angle(0, 0, 0), vector_one, vector_origin
		for _, part in ipairs {@EAR_L, @EAR_R}
			with @GetEntity()
				\ManipulateBoneAngles(part, ang)
				\ManipulateBoneScale(part, vec1)
				\ManipulateBonePosition(part, vec2)

	ResetBodygroups: =>
		return unless @isValid
		return unless IsValid(@GetEntity())
		@GetEntity()\SetFlexWeight(@@FLEX_ID_EYELASHES, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_BODY, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, 0)
		@ResetWings()
		@ResetEars()
		super()

	_UpdateMaleBuff: =>
		maleModifier = @GrabData('Gender') == PPM2.GENDER_MALE and 1 or 0

		if @GrabData('NewMuzzle')
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE, 0)
		else
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)

		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_BODY,     maleModifier * @GrabData('MaleBuff'))

	SlowUpdate: (createModels = CLIENT, force = false) =>
		return if not IsValid(@GetEntity())
		return if not @GetEntity()\IsPony()
		@GetEntity()\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GrabData('EyelashType') == PPM2.EYELASHES_NONE and 1 or 0)
		@_UpdateMaleBuff()

		@GetEntity()\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GrabData('BatPonyEars') and @GrabData('BatPonyEarsStrength') or 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GrabData('ClawTeeth') and @GrabData('ClawTeethStrength') or 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_HOOF_FLUFF,    @GrabData('HoofFluffers') and @GrabData('HoofFluffersStrength') or 0)

		if @GrabData('Fangs')
			if @GrabData('AlternativeFangs')
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, @GrabData('FangsStrength'))
			else
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, @GrabData('FangsStrength'))
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)
		else
			@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
			@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)

		@ApplyRace()

		if createModels
			@UpdateClothesModel()
			@UpdateUpperMane(force)
			@UpdateLowerMane(force)
			@UpdateTailModel(force)
			@UpdateHornModel(force) if @GrabData('UseNewHorn')
			@UpdateSocksModel(force) if @GrabData('SocksAsModel')
			@UpdateNewSocksModel(force) if @GrabData('SocksAsNewModel')

	RemoveModels: =>
		@maneModelUpper\Remove() if IsValid(@maneModelUpper)
		@maneModelLower\Remove() if IsValid(@maneModelLower)
		@tailModel\Remove() if IsValid(@tailModel)
		super()

	ApplyBodygroups: (createModels = CLIENT, force = false) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@ResetBodygroups()
		return @RemoveModels() if not @GetEntity()\IsPony()
		@SlowUpdate(createModels, force)

	@NOCLIP_ANIMATIONS = {9, 10, 11}

	SelectWingsType: =>
		wtype = @GrabData('WingsType')
		if (@GrabData('Fly') or @GetEntity().GetMoveType and @GetEntity()\GetMoveType() == MOVETYPE_NOCLIP) and (not @GetEntity().InVehicle or not @GetEntity()\InVehicle())
			wtype += PPM2.MAX_WINGS + 1
		return wtype

	ApplyRace: =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		switch @GrabData('Race')
			when PPM2.RACE_EARTH
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS * 2 + 2)
			when PPM2.RACE_PEGASUS
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, @SelectWingsType())
			when PPM2.RACE_UNICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS * 2 + 2)
			when PPM2.RACE_ALICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, @SelectWingsType())

	DataChanges: (state) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@Remap()
		switch state\GetKey()
			when 'HeadClothes', 'NeckClothes', 'BodyClothes', 'EyeClothes'
				@UpdateClothesModel()
			when 'EyelashType'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_EYELASHES, state\GetValue() == PPM2.EYELASHES_NONE and 1 or 0)
			when 'Gender', 'NewMuzzle'
				@_UpdateMaleBuff()
			when 'Fly'
				@ApplyRace()
			when 'BatPonyEars', 'BatPonyEarsStrength'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GrabData('BatPonyEars') and @GrabData('BatPonyEarsStrength') or 0)
			when 'Fangs', 'AlternativeFangs', 'FangsStrength'
				if @GrabData('Fangs')
					if @GrabData('AlternativeFangs')
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, @GrabData('FangsStrength'))
					else
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, @GrabData('FangsStrength'))
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)
				else
					@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
					@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)
			when 'EarFluffers', 'EarFluffersStrength'
				@UpdateEars()
			when 'HoofFluffers', 'HoofFluffersStrength'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_HOOF_FLUFF, @GrabData('HoofFluffers') and @GrabData('HoofFluffersStrength') or 0)
			when 'ClawTeeth', 'ClawTeethStrength'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GrabData('ClawTeeth') and @GrabData('ClawTeethStrength') or 0)
			when 'ManeTypeNew'
				@UpdateUpperMane() if CLIENT
			when 'ManeTypeLowerNew'
				@UpdateLowerMane() if CLIENT
			when 'TailSize', 'TailTypeNew'
				@UpdateTailModel()
				@UpdateTailSize()
			when 'PonySize'
				@UpdateTailSize()
			when 'UseNewHorn'
				if state\GetValue()
					@CreateHornModelIfNotExists()
				else
					@hornModel\Remove() if IsValid(@hornModel)

				@ApplyRace()
			when 'Race'
				@ApplyRace()
			when 'WingsType'
				@ApplyRace()
			when 'LWingSize', 'RWingSize', 'LWingX', 'RWingX', 'LWingY', 'RWingY', 'LWingZ', 'RWingZ'
				@UpdateWings()
			when 'MaleBuff'
				maleModifier = @GrabData('Gender') == PPM2.GENDER_MALE and 1 or 0
				@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GrabData('MaleBuff'))
			when 'SocksAsModel'
				return if SERVER
				if state\GetValue()
					@UpdateSocksModel()
				else
					@socksModel\Remove() if IsValid(@socksModel)
			when 'SocksAsNewModel'
				if state\GetValue()
					@UpdateNewSocksModel()
				else
					@newSocksModel\Remove() if IsValid(@newSocksModel)

if CLIENT
	hook.Add 'PPM2.SetupBones', 'PPM2.Bodygroups', (ent, data) ->
		if bodygroup = data\GetBodygroupController()
			bodygroup.ent = ent
			bodygroup\UpdateBack()
			bodygroup\UpdateTailSize()
			bodygroup\UpdateManeSize()
			bodygroup\UpdateWings() if bodygroup.UpdateWings
			bodygroup\UpdateEars() if bodygroup.UpdateEars
			bodygroup.lastPAC3BoneReset = RealTimeL() + 1

	ppm2_sv_allow_resize = ->
		for _, ply in ipairs player.GetAll()
			if data = ply\GetPonyData()
				if bodygroup = data\GetBodygroupController()
					bodygroup\ResetTail()
					bodygroup\ResetMane()
					bodygroup\ResetBack()

	cvars.AddChangeCallback 'ppm2_sv_allow_resize', ppm2_sv_allow_resize, 'PPM2.Bodygroups'
else
	hook.Add 'PlayerNoClip', 'PPM2.WingsCheck', =>
		timer.Simple 0, ->
			return if not IsValid(@)
			if data = @GetPonyData()
				if bg = data\GetBodygroupController()
					bg\SlowUpdate()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

PPM2.GetBodygroupController = (model = 'models/ppm/player_default_base.mdl') -> DefaultBodygroupController.AVALIABLE_CONTROLLERS[model\lower()] or DefaultBodygroupController

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

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

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

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

import PPM2, ents, LocalPlayer, SERVER, NULL, CLIENT, EF_BONEMERGE from _G

ALLOW_TO_MODIFY_SCALE = PPM2.ALLOW_TO_MODIFY_SCALE

TRACKED_ENTS = {}
TRACKED_ENTS_FRAME = 0
ents_GetAll = ->
	if TRACKED_ENTS_FRAME ~= FrameNumberL()
		TRACKED_ENTS = ents.GetAll()
		TRACKED_ENTS_FRAME = FrameNumberL()
	return TRACKED_ENTS

if CLIENT
	for _, ent in ipairs ents.GetAll()
		if ent.isPonyLegsModel or ent.isPonyPropModel
			ent\Remove()

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

diff = (num) ->
	num = num\abs()
	num < 0.99 or num > 1.01

vector_one = Vector(1, 1, 1)
empty = {}

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

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

	@BODYGROUP_SKELETON = 0
	@BODYGROUP_GENDER = 1
	@BODYGROUP_HORN = 2
	@BODYGROUP_WINGS = 3
	@BODYGROUP_MANE_UPPER = 4
	@BODYGROUP_MANE_LOWER = 5
	@BODYGROUP_TAIL = 6
	@BODYGROUP_CMARK = 7
	@BODYGROUP_EYELASH = 8

	@NEXT_OBJ_ID = 0

	@COOLDOWN_TIME = 5
	@COOLDOWN_MAX_COUNT = 4

	@BONE_MANE_1 = 'Mane01'
	@BONE_MANE_2 = 'Mane02'
	@BONE_MANE_3 = 'Mane03'
	@BONE_MANE_4 = 'Mane04'
	@BONE_MANE_5 = 'Mane05'
	@BONE_MANE_6 = 'Mane06'
	@BONE_MANE_7 = 'Mane07'
	@BONE_MANE_8 = 'Mane03_tip'

	@BONE_TAIL_1 = 'Tail01'
	@BONE_TAIL_2 = 'Tail02'
	@BONE_TAIL_3 = 'Tail03'

	@BONE_SPINE_ROOT = 'LrigPelvis'
	@BONE_SPINE = 'LrigSpine2'

	@_BONE_REMAP_R = {
		'BONE_SPINE_ROOT'
		'BONE_TAIL_1', 'BONE_TAIL_2', 'BONE_TAIL_3'
		'BONE_SPINE', 'BONE_MANE_1', 'BONE_MANE_2'
		'BONE_MANE_3', 'BONE_MANE_4', 'BONE_MANE_5'
		'BONE_MANE_6', 'BONE_MANE_7', 'BONE_MANE_8'
	}

	Remap: =>
		@validSkeleton = true

		for _, name in ipairs @@_BONE_REMAP_R
			@[name] = @GetEntity()\LookupBone(@@[name])

			if not @[name]
				@validSkeleton = false

	new: (controller) =>
		super(controller)
		@isValid = true
		@objID = @@NEXT_OBJ_ID
		@@NEXT_OBJ_ID += 1
		@lastPAC3BoneReset = 0
		@Remap()

		PPM2.DebugPrint('Created new bodygroups controller for ', @GetEntity(), ' as part of ', controller, '; internal ID is ', @objID)

	IsValid: => @isValid

	@ATTACHMENT_EYES = 4
	@ATTACHMENT_EYES_NAME = 'eyes'

	CreateGenericModel: (force, fModelIndex, mIndex, mName, mTranslateModelName, mCallType) =>
		return @[mIndex] or NULL if SERVER or not @isValid or not IsValid(@GetEntity()) or not force and @GetEntity()\IsDormant() or not @GetEntity()\IsPony()
		return @[mIndex] if IsValid(@[mIndex])

		for _, ent in ipairs ents_GetAll()
			if ent.isPonyPropModel and ent[fModelIndex] and ent.manePlayer == @GetEntity()
				@[mIndex] = ent
				@SetData(mName, @[mIndex])
				PPM2.DebugPrint('Resuing ', @[mIndex], ' as ', mName, ' for ', @GetEntity())
				return ent

		model, modelID, bodygroupID = mTranslateModelName(@GrabData(mCallType)) if mCallType
		model = mTranslateModelName() if not mCallType
		with @[mIndex] = ClientsideModel(model)
			.isPonyPropModel = true
			.manePlayer = @GetEntity()
			\DrawShadow(true)
			\SetPos(@GetEntity()\EyePos())
			\SetBodygroup(1, bodygroupID) if bodygroupID
			\Spawn()
			\Activate()
			\SetNoDraw(true)
			\SetParent(@GetEntity())
			\AddEffects(EF_BONEMERGE)

		@[mIndex][fModelIndex] = true

		PPM2.DebugPrint('Creating new ', mName, ' for ', @GetEntity(), ' as ', @[mIndex])
		@SetData(mName, @[mIndex])
		return @[mIndex]

	UpdateGenericModel: (force, fcallNone, mIndex, mName, mTranslateModelName, mCallType) =>
		return @[mIndex] or NULL if SERVER or not @isValid or not IsValid(@GetEntity()) or not force and @GetEntity()\IsDormant() or not @GetEntity()\IsPony()
		@[mIndex] = @[fcallNone](@, force) if not IsValid(@[mIndex])
		return NULL if not IsValid(@[mIndex])

		model, modelID, bodygroupID = mTranslateModelName(@GrabData(mCallType)) if mCallType
		model, modelID, bodygroupID = mTranslateModelName() if not mCallType
		with @[mIndex]
			\SetModel(model) if model ~= \GetModel()
			\SetBodygroup(1, bodygroupID) if bodygroupID and \GetBodygroup(1) ~= bodygroupID
			\SetParent(@GetEntity()) if \GetParent() ~= @GetEntity() and IsValid(@GetEntity())

		@SetData(mName, @[mIndex])
		return @[mIndex]

	UpdateHornModel: (force = false) => @UpdateGenericModel(force, 'CreateHornModel', 'hornModel', 'HornModel', PPM2.GetHornModelName, 'NewHornType')
	UpdateSocksModel: (force = false) => @UpdateGenericModel(force, 'CreateSocksModel', 'socksModel', 'SocksModel', @_SocksModelName)
	UpdateNewSocksModel: (force = false) => @UpdateGenericModel(force, 'CreateNewSocksModel', 'newSocksModel', 'NewSocksModel', @_SocksModelName)

	_SocksModelName: => 'models/props_pony/ppm/cosmetics/ppm_socks.mdl'
	_NewSocksModelName: => 'models/props_pony/ppm/cosmetics/ppm2_socks.mdl'
	_ClothesModelname: => 'models/ppm/player_default_clothes1.mdl'

	CreateSocksModel: (force = false) => @CreateGenericModel(force, 'isSocks', 'socksModel', 'SocksModel', @_SocksModelName)
	CreateNewSocksModel: (force = false) => @CreateGenericModel(force, 'isNewSocks', 'newSocksModel', 'NewSocksModel', @_NewSocksModelName)
	CreateHornModel: (force = false) => @CreateGenericModel(force, 'isHornModel', 'hornModel', 'HornModel', PPM2.GetHornModelName, 'NewHornType')
	CreateClothesModel: (force = false) => @CreateGenericModel(force, 'isClothesModel', 'clothesModel', 'ClothesModel', @_ClothesModelname)

	CreateClothesModelIfNotExist: (force = false) =>
		return @clothesModel if IsValid(@clothesModel)
		return @CreateClothesModel(force)

	CreateNewSocksModelIfNotExists: (force = false) =>
		return @newSocksModel if IsValid(@newSocksModel)
		return @CreateNewSocksModel(force)

	CreateSocksModelIfNotExists: (force = false) =>
		return @socksModel if IsValid(@socksModel)
		return @CreateSocksModel(force)

	CreateHornModelIfNotExists: (force = false) =>
		return @hornModel if IsValid(@hornModel)
		return @CreateHornModel(force)

	UpdateClothesModel: (force = false) =>
		@CreateClothesModelIfNotExist(force)
		return NULL if not IsValid(@clothesModel)

		with @clothesModel
			\SetBodygroup(1, @GrabData('HeadClothes'))
			\SetBodygroup(2, @GrabData('NeckClothes'))
			\SetBodygroup(3, @GrabData('BodyClothes'))
			\SetBodygroup(8, @GrabData('EyeClothes'))

	MergeModels: (targetEnt = NULL) =>
		return if SERVER or not @isValid or not IsValid(targetEnt)

		socks = @CreateSocksModelIfNotExists(true) if @GrabData('SocksAsModel')
		socks2 = @CreateNewSocksModelIfNotExists(true) if @GrabData('SocksAsNewModel')
		horn = @CreateHornModelIfNotExists(true) if @GrabData('UseNewHorn')
		clothesModel = @CreateClothesModelIfNotExist(true)

		if IsValid(clothesModel)
			clothesModel\SetParent(targetEnt)

		if IsValid(horn)
			horn\SetParent(targetEnt)

		if IsValid(socks)
			socks\SetParent(targetEnt)

		if IsValid(socks2)
			socks2\SetParent(targetEnt)

	GetSocks: => @socksModel or NULL

	ApplyRace: =>
		return unless @isValid
		return NULL if not IsValid(@GetEntity())
		with @GetEntity()
			switch @GrabData('Race')
				when PPM2.RACE_EARTH
					\SetBodygroup(@@BODYGROUP_HORN, 1)
					\SetBodygroup(@@BODYGROUP_WINGS, 1)
				when PPM2.RACE_PEGASUS
					\SetBodygroup(@@BODYGROUP_HORN, 1)
					\SetBodygroup(@@BODYGROUP_WINGS, 0)
				when PPM2.RACE_UNICORN
					\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
					\SetBodygroup(@@BODYGROUP_WINGS, 1)
				when PPM2.RACE_ALICORN
					\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
					\SetBodygroup(@@BODYGROUP_WINGS, 0)

	ResetTail: =>
		return if not CLIENT
		return if not @validSkeleton
		with @GetEntity()
			\ManipulateBoneScale(@BONE_TAIL_1, vector_one)
			\ManipulateBoneScale(@BONE_TAIL_2, vector_one)
			\ManipulateBoneScale(@BONE_TAIL_3, vector_one)
			\ManipulateBoneAngles(@BONE_TAIL_1, Angle(0, 0, 0))
			\ManipulateBoneAngles(@BONE_TAIL_2, Angle(0, 0, 0))
			\ManipulateBoneAngles(@BONE_TAIL_3, Angle(0, 0, 0))
			\ManipulateBonePosition(@BONE_TAIL_1, vector_origin)
			\ManipulateBonePosition(@BONE_TAIL_2, vector_origin)
			\ManipulateBonePosition(@BONE_TAIL_3, vector_origin)

	ResetBack: =>
		return if not CLIENT
		return if not @validSkeleton
		with @GetEntity()
			\ManipulateBoneScale(@BONE_SPINE_ROOT, vector_one)
			\ManipulateBoneScale(@BONE_SPINE, vector_one)
			\ManipulateBoneAngles(@BONE_SPINE_ROOT, Angle(0, 0, 0))
			\ManipulateBoneAngles(@BONE_SPINE, Angle(0, 0, 0))
			\ManipulateBonePosition(@BONE_SPINE_ROOT, vector_origin)
			\ManipulateBonePosition(@BONE_SPINE, vector_origin)

	ResetMane: =>
		return if not CLIENT
		return if not @validSkeleton

		vec1, ang, vec2 = vector_one, Angle(0, 0, 0), vector_origin

		with @GetEntity()
			for i = 1, 7
				\ManipulateBoneScale(@['BONE_MANE_' .. i], vec1)
				\ManipulateBoneAngles(@['BONE_MANE_' .. i], ang)
				\ManipulateBonePosition(@['BONE_MANE_' .. i], vec2)

	ResetBodygroups: =>
		return unless @isValid
		return unless IsValid(@GetEntity())
		return unless @GetEntity()\GetBodyGroups()
		return if not @validSkeleton

		for _, grp in ipairs @GetEntity()\GetBodyGroups()
			@GetEntity()\SetBodygroup(grp.id, 0)

		if @lastPAC3BoneReset < RealTimeL()
			@ResetTail()
			@ResetMane()
			@ResetBack()

	Reset: => @ResetBodygroups()
	RemoveModels: =>
		@socksModel\Remove() if IsValid(@socksModel)
		@newSocksModel\Remove() if IsValid(@newSocksModel)

	UpdateTailSize: (ent = @GetEntity()) =>
		return if not CLIENT
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		return if not @validSkeleton
		size = @GrabData('TailSize')
		size *= @GrabData('PonySize') if not ent\IsRagdoll() and not ent\IsNJPony()
		return if not diff(size)
		vecTail = vector_one * size
		vecTailPos = Vector((size - 1) * 8, 0, 0)

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or empty

		with ent
			\ManipulateBoneScale(@BONE_TAIL_1, vecTail)
			\ManipulateBoneScale(@BONE_TAIL_2, vecTail)
			\ManipulateBoneScale(@BONE_TAIL_3, vecTail)

			--\ManipulateBonePosition(@BONE_TAIL_1, vecTail + (boneAnimTable[@BONE_TAIL_1] or vector_origin))
			\ManipulateBonePosition(@BONE_TAIL_2, vecTailPos + (boneAnimTable[@BONE_TAIL_2] or vector_origin))
			\ManipulateBonePosition(@BONE_TAIL_3, vecTailPos + (boneAnimTable[@BONE_TAIL_3] or vector_origin))

	UpdateManeSize: (ent = @GetEntity()) =>
		return if not CLIENT
		return if ent\IsRagdoll()
		return if ent\IsNJPony()
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		return if not @validSkeleton
		size = @GrabData('PonySize')
		return if not diff(size)
		vecMane = vector_one * size

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or empty

		with ent
			\ManipulateBoneScale(@BONE_MANE_1, vecMane)
			\ManipulateBoneScale(@BONE_MANE_2, vecMane)
			\ManipulateBoneScale(@BONE_MANE_3, vecMane)
			\ManipulateBoneScale(@BONE_MANE_4, vecMane)
			\ManipulateBoneScale(@BONE_MANE_5, vecMane)
			\ManipulateBoneScale(@BONE_MANE_6, vecMane)
			\ManipulateBoneScale(@BONE_MANE_7, vecMane)
			\ManipulateBoneScale(@BONE_MANE_8, vecMane)

			\ManipulateBonePosition(@BONE_MANE_1, Vector(-(size - 1) * 4, (1 - size) * 3, 0) + (boneAnimTable[@BONE_MANE_1] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_2, Vector(-(size - 1) * 4, (size - 1) * 2, 1) + (boneAnimTable[@BONE_MANE_2] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_3, Vector((size - 1) * 2, 0, 0) +               (boneAnimTable[@BONE_MANE_3] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_4, Vector(1 - size, (1 - size) * 4, 1 - size) + (boneAnimTable[@BONE_MANE_4] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_5, Vector((size - 1) * 4, (1 - size) * 2, (size - 1) * 3) + (boneAnimTable[@BONE_MANE_5] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_6, Vector(0, 0, -(size - 1) * 2) +              (boneAnimTable[@BONE_MANE_6] or vector_origin))
			\ManipulateBonePosition(@BONE_MANE_7, Vector(0, 0, -(size - 1) * 2) +              (boneAnimTable[@BONE_MANE_7] or vector_origin))

	UpdateBack: (ent = @GetEntity()) =>
		return if not CLIENT
		return if ent\IsRagdoll()
		return if ent\IsNJPony()
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		return if not @validSkeleton
		return if not diff(@GrabData('BackSize'))

		vecModify = Vector(-(@GrabData('BackSize') - 1) * 2, 0, 0)
		vecModify2 = Vector((@GrabData('BackSize') - 1) * 5, 0, 0)

		boneAnimTable = ent.pac_boneanim and ent.pac_boneanim.positions or empty

		with ent
			\ManipulateBonePosition(@BONE_SPINE_ROOT, vecModify + (boneAnimTable[@BONE_SPINE_ROOT] or vector_origin))
			\ManipulateBonePosition(@BONE_SPINE, vecModify2 + (boneAnimTable[@BONE_SPINE] or vector_origin))

	SlowUpdate: (createModels = CLIENT, ent = @GetEntity(), force = false) =>
		return if not IsValid(ent)
		return if not ent\IsPony()
		with ent
			\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GrabData('ManeType'))
			\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GrabData('ManeTypeLower'))
			\SetBodygroup(@@BODYGROUP_TAIL, @GrabData('TailType'))
			\SetBodygroup(@@BODYGROUP_EYELASH, @GrabData('EyelashType'))
			\SetBodygroup(@@BODYGROUP_GENDER, @GrabData('Gender'))

		@ApplyRace()
		if createModels
			@UpdateClothesModel()
			@UpdateHornModel(force) if @GrabData('UseNewHorn')
			@UpdateSocksModel(force) if @GrabData('SocksAsModel')
			@UpdateNewSocksModel(force) if @GrabData('SocksAsNewModel')

	ApplyBodygroups: (createModels = CLIENT, force = false) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@ResetBodygroups()
		return if not @GetEntity()\IsPony()
		@SlowUpdate(createModels, force)

	Remove: =>
		@RemoveModels()
		@ResetBodygroups()
		@isValid = false

	DataChanges: (state) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@Remap()

		switch state\GetKey()
			when 'HeadClothes', 'NeckClothes', 'BodyClothes', 'EyeClothes'
				@UpdateClothesModel()
			when 'ManeType'
				@GetEntity()\SetBodygroup(@@BODYGROUP_MANE_UPPER, state\GetValue())
			when 'ManeTypeLower'
				@GetEntity()\SetBodygroup(@@BODYGROUP_MANE_LOWER, state\GetValue())
			when 'TailType'
				@GetEntity()\SetBodygroup(@@BODYGROUP_TAIL, state\GetValue())
			when 'TailSize', 'PonySize'
				@UpdateTailSize()
			when 'EyelashType'
				@GetEntity()\SetBodygroup(@@BODYGROUP_EYELASH, state\GetValue())
			when 'Gender'
				@GetEntity()\SetBodygroup(@@BODYGROUP_GENDER, state\GetValue())
			when 'SocksAsModel'
				if state\GetValue()
					@UpdateSocksModel()
				else
					@socksModel\Remove() if IsValid(@socksModel)
			when 'UseNewHorn'
				if state\GetValue()
					@CreateHornModelIfNotExists()
				else
					@hornModel\Remove() if IsValid(@hornModel)

				@ApplyRace()
			when 'SocksAsNewModel'
				if state\GetValue()
					@UpdateNewSocksModel()
				else
					@newSocksModel\Remove() if IsValid(@newSocksModel)
			when 'Race'
				@ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
	@MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

	new: (...) => super(...)

	ApplyRace: =>
		return unless @isValid
		switch @GrabData('Race')
			when PPM2.RACE_EARTH
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 1)
			when PPM2.RACE_PEGASUS
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 0)
			when PPM2.RACE_UNICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 0)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 1)
			when PPM2.RACE_ALICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 2)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, 3)

-- 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   right_hand
-- 43   wing_l
-- 44   wing_r
-- 45   Tail01
-- 46   Tail02
-- 47   Tail03
-- 48   wing_l_bat
-- 49   wing_r_bat
-- 50   wing_open_l
-- 51   wing_open_r

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

	@BODYGROUP_SKELETON = 0
	@BODYGROUP_GENDER = -1
	@BODYGROUP_HORN = 1
	@BODYGROUP_WINGS = 2

	@EAR_L = 'Ear_L'
	@EAR_R = 'Ear_R'

	@WING_LEFT_1 = 'wing_l'
	@WING_LEFT_2 = 'wing_l_bat'
	@WING_RIGHT_1 = 'wing_r'
	@WING_RIGHT_2 = 'wing_r_bat'

	@WING_OPEN_LEFT = 'wing_open_l'
	@WING_OPEN_RIGHT = 'wing_open_r'

	@BONE_SPINE = 'LrigSpine1'

	@_BONE_REMAP = {
		'EAR_L', 'EAR_R'
		'WING_LEFT_1', 'WING_LEFT_2', 'WING_RIGHT_1'
		'WING_RIGHT_2', 'WING_OPEN_LEFT', 'WING_OPEN_RIGHT'
	}

	Remap: =>
		super!

		for _, name in ipairs @@_BONE_REMAP
			@[name] = @GetEntity()\LookupBone(@@[name])

			if not @[name]
				@validSkeleton = false

	CreateUpperManeModel: (force = false) => @CreateGenericModel(force, 'isUpperMane', 'maneModelUpper', 'UpperManeModel', PPM2.GetUpperManeModelName, 'ManeTypeNew')
	CreateLowerManeModel: (force = false) => @CreateGenericModel(force, 'isLowerMane', 'maneModelLower', 'LowerManeModel', PPM2.GetLowerManeModelName, 'ManeTypeLowerNew')
	CreateTailModel: (force = false) => @CreateGenericModel(force, 'isTailModel', 'tailModel', 'TailModel', PPM2.GetTailModelName, 'TailTypeNew')

	CreateUpperManeModelIfNotExists: (force = false) =>
		return @maneModelUpper if IsValid(@maneModelUpper)
		return @CreateUpperManeModel()

	CreateLowerManeModelIfNotExists: (force = false) =>
		return @maneModelLower if IsValid(@maneModelLower)
		return @CreateLowerManeModel()

	CreateTailModelIfNotExists: (force = false) =>
		return @tailModel if IsValid(@tailModel)
		return @CreateTailModel()

	UpdateUpperMane: (force = false) => @UpdateGenericModel(force, 'CreateUpperManeModel', 'maneModelUpper', 'UpperManeModel', PPM2.GetUpperManeModelName, 'ManeTypeNew')
	UpdateLowerMane: (force = false) => @UpdateGenericModel(force, 'CreateLowerManeModel', 'maneModelLower', 'LowerManeModel', PPM2.GetLowerManeModelName, 'ManeTypeLowerNew')
	UpdateTailModel: (force = false) => @UpdateGenericModel(force, 'CreateTailModel', 'tailModel', 'TailModel', PPM2.GetTailModelName, 'TailTypeNew')

	GetUpperMane: => @maneModelUpper or NULL
	GetLowerMane: => @maneModelLower or NULL
	GetTail: => @tailModel or NULL

	MergeModels: (targetEnt = NULL) =>
		return unless @isValid
		super(targetEnt)
		return unless IsValid(targetEnt)
		for _, e in ipairs {@CreateUpperManeModelIfNotExists(true), @CreateLowerManeModelIfNotExists(true), @CreateTailModelIfNotExists(true)}
			e\SetParent(targetEnt) if IsValid(e)

	@FLEX_ID_EYELASHES = 16
	@FLEX_ID_MALE = 25
	@FLEX_ID_MALE_2 = 35
	@FLEX_ID_MALE_BODY = 36
	@FLEX_ID_BAT_PONY_EARS = 28
	@FLEX_ID_FANGS = 31
	@FLEX_ID_FANGS2 = 29
	@FLEX_ID_CLAW_TEETH = 30
	@FLEX_ID_HOOF_FLUFF = 26

	ResetWings: =>
		return if SERVER
		return if not @validSkeleton
		ang, vec1, vec2 = Angle(0, 0, 0), vector_one, vector_origin
		for _, wing in ipairs {@WING_LEFT_1, @WING_LEFT_2, @WING_RIGHT_1, @WING_RIGHT_2, @WING_OPEN_LEFT, @WING_OPEN_RIGHT}
			with @GetEntity()
				\ManipulateBoneAngles(wing, ang)
				\ManipulateBoneScale(wing, vec1)
				\ManipulateBonePosition(wing, vec2)

	UpdateWings: =>
		return if SERVER
		return if not @validSkeleton
		return if @GetEntity().Alive and not @GetEntity()\Alive()
		left = @GrabData('LWingSize')

		if diff(left)
			left = left * vector_one
		else
			left = nil

		leftX = @GrabData('LWingX')
		leftY = @GrabData('LWingY')
		leftZ = @GrabData('LWingZ')
		right = @GrabData('RWingSize')

		if diff(right)
			right = right * vector_one
		else
			right = nil

		rightX = @GrabData('RWingX')
		rightY = @GrabData('RWingY')
		rightZ = @GrabData('RWingZ')

		leftPos = Vector(leftX, leftY, leftZ) if diff(leftX) or diff(leftY) or diff(leftZ)
		rightPos = Vector(rightX, rightY, rightZ) if diff(rightX) or diff(rightY) or diff(rightZ)

		with @GetEntity()
			if left
				\ManipulateBoneScale(@WING_LEFT_1, left)
				\ManipulateBoneScale(@WING_LEFT_2, left)
				\ManipulateBoneScale(@WING_OPEN_LEFT, left)

			if right
				\ManipulateBoneScale(@WING_RIGHT_1, right)
				\ManipulateBoneScale(@WING_RIGHT_2, right)
				\ManipulateBoneScale(@WING_OPEN_RIGHT, right)

			if leftPos
				\ManipulateBonePosition(@WING_LEFT_1, leftPos)
				\ManipulateBonePosition(@WING_LEFT_2, leftPos)
				\ManipulateBonePosition(@WING_OPEN_LEFT, leftPos)

			if rightPos
				\ManipulateBonePosition(@WING_RIGHT_1, rightPos)
				\ManipulateBonePosition(@WING_RIGHT_2, rightPos)
				\ManipulateBonePosition(@WING_OPEN_RIGHT, rightPos)

	UpdateEars: =>
		size = @GrabData('EarsSize')
		if diff(size)
			vec = vector_one * size
			@GetEntity()\ManipulateBoneScale(@EAR_L, vec)
			@GetEntity()\ManipulateBoneScale(@EAR_R, vec)

	ResetEars: =>
		ang, vec1, vec2 = Angle(0, 0, 0), vector_one, vector_origin
		for _, part in ipairs {@EAR_L, @EAR_R}
			with @GetEntity()
				\ManipulateBoneAngles(part, ang)
				\ManipulateBoneScale(part, vec1)
				\ManipulateBonePosition(part, vec2)

	ResetBodygroups: =>
		return unless @isValid
		return unless IsValid(@GetEntity())
		@GetEntity()\SetFlexWeight(@@FLEX_ID_EYELASHES, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_BODY, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, 0)
		@ResetWings()
		@ResetEars()
		super()

	_UpdateMaleBuff: =>
		maleModifier = @GrabData('Gender') == PPM2.GENDER_MALE and 1 or 0

		if @GrabData('NewMuzzle')
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_2, maleModifier)
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE, 0)
		else
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_2, 0)
			@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE, maleModifier)

		@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_BODY,     maleModifier * @GrabData('MaleBuff'))

	SlowUpdate: (createModels = CLIENT, force = false) =>
		return if not IsValid(@GetEntity())
		return if not @GetEntity()\IsPony()
		@GetEntity()\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GrabData('EyelashType') == PPM2.EYELASHES_NONE and 1 or 0)
		@_UpdateMaleBuff()

		@GetEntity()\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GrabData('BatPonyEars') and @GrabData('BatPonyEarsStrength') or 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GrabData('ClawTeeth') and @GrabData('ClawTeethStrength') or 0)
		@GetEntity()\SetFlexWeight(@@FLEX_ID_HOOF_FLUFF,    @GrabData('HoofFluffers') and @GrabData('HoofFluffersStrength') or 0)

		if @GrabData('Fangs')
			if @GrabData('AlternativeFangs')
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, @GrabData('FangsStrength'))
			else
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, @GrabData('FangsStrength'))
				@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)
		else
			@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
			@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)

		@ApplyRace()

		if createModels
			@UpdateClothesModel()
			@UpdateUpperMane(force)
			@UpdateLowerMane(force)
			@UpdateTailModel(force)
			@UpdateHornModel(force) if @GrabData('UseNewHorn')
			@UpdateSocksModel(force) if @GrabData('SocksAsModel')
			@UpdateNewSocksModel(force) if @GrabData('SocksAsNewModel')

	RemoveModels: =>
		@maneModelUpper\Remove() if IsValid(@maneModelUpper)
		@maneModelLower\Remove() if IsValid(@maneModelLower)
		@tailModel\Remove() if IsValid(@tailModel)
		super()

	ApplyBodygroups: (createModels = CLIENT, force = false) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@ResetBodygroups()
		return @RemoveModels() if not @GetEntity()\IsPony()
		@SlowUpdate(createModels, force)

	@NOCLIP_ANIMATIONS = {9, 10, 11}

	SelectWingsType: =>
		wtype = @GrabData('WingsType')
		if (@GrabData('Fly') or @GetEntity().GetMoveType and @GetEntity()\GetMoveType() == MOVETYPE_NOCLIP) and (not @GetEntity().InVehicle or not @GetEntity()\InVehicle())
			wtype += PPM2.MAX_WINGS + 1
		return wtype

	ApplyRace: =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		switch @GrabData('Race')
			when PPM2.RACE_EARTH
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS * 2 + 2)
			when PPM2.RACE_PEGASUS
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, 1)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, @SelectWingsType())
			when PPM2.RACE_UNICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, PPM2.MAX_WINGS * 2 + 2)
			when PPM2.RACE_ALICORN
				@GetEntity()\SetBodygroup(@@BODYGROUP_HORN, @GrabData('UseNewHorn') and 1 or 0)
				@GetEntity()\SetBodygroup(@@BODYGROUP_WINGS, @SelectWingsType())

	DataChanges: (state) =>
		return unless @isValid
		return if not IsValid(@GetEntity())
		@Remap()
		switch state\GetKey()
			when 'HeadClothes', 'NeckClothes', 'BodyClothes', 'EyeClothes'
				@UpdateClothesModel()
			when 'EyelashType'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_EYELASHES, state\GetValue() == PPM2.EYELASHES_NONE and 1 or 0)
			when 'Gender', 'NewMuzzle'
				@_UpdateMaleBuff()
			when 'Fly'
				@ApplyRace()
			when 'BatPonyEars', 'BatPonyEarsStrength'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GrabData('BatPonyEars') and @GrabData('BatPonyEarsStrength') or 0)
			when 'Fangs', 'AlternativeFangs', 'FangsStrength'
				if @GrabData('Fangs')
					if @GrabData('AlternativeFangs')
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, @GrabData('FangsStrength'))
					else
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, @GrabData('FangsStrength'))
						@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)
				else
					@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS, 0)
					@GetEntity()\SetFlexWeight(@@FLEX_ID_FANGS2, 0)
			when 'EarFluffers', 'EarFluffersStrength'
				@UpdateEars()
			when 'HoofFluffers', 'HoofFluffersStrength'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_HOOF_FLUFF, @GrabData('HoofFluffers') and @GrabData('HoofFluffersStrength') or 0)
			when 'ClawTeeth', 'ClawTeethStrength'
				@GetEntity()\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GrabData('ClawTeeth') and @GrabData('ClawTeethStrength') or 0)
			when 'ManeTypeNew'
				@UpdateUpperMane() if CLIENT
			when 'ManeTypeLowerNew'
				@UpdateLowerMane() if CLIENT
			when 'TailSize', 'TailTypeNew'
				@UpdateTailModel()
				@UpdateTailSize()
			when 'PonySize'
				@UpdateTailSize()
			when 'UseNewHorn'
				if state\GetValue()
					@CreateHornModelIfNotExists()
				else
					@hornModel\Remove() if IsValid(@hornModel)

				@ApplyRace()
			when 'Race'
				@ApplyRace()
			when 'WingsType'
				@ApplyRace()
			when 'LWingSize', 'RWingSize', 'LWingX', 'RWingX', 'LWingY', 'RWingY', 'LWingZ', 'RWingZ'
				@UpdateWings()
			when 'MaleBuff'
				maleModifier = @GrabData('Gender') == PPM2.GENDER_MALE and 1 or 0
				@GetEntity()\SetFlexWeight(@@FLEX_ID_MALE_BODY, maleModifier * @GrabData('MaleBuff'))
			when 'SocksAsModel'
				return if SERVER
				if state\GetValue()
					@UpdateSocksModel()
				else
					@socksModel\Remove() if IsValid(@socksModel)
			when 'SocksAsNewModel'
				if state\GetValue()
					@UpdateNewSocksModel()
				else
					@newSocksModel\Remove() if IsValid(@newSocksModel)

if CLIENT
	hook.Add 'PPM2.SetupBones', 'PPM2.Bodygroups', (ent, data) ->
		if bodygroup = data\GetBodygroupController()
			bodygroup.ent = ent
			bodygroup\UpdateBack()
			bodygroup\UpdateTailSize()
			bodygroup\UpdateManeSize()
			bodygroup\UpdateWings() if bodygroup.UpdateWings
			bodygroup\UpdateEars() if bodygroup.UpdateEars
			bodygroup.lastPAC3BoneReset = RealTimeL() + 1

	ppm2_sv_allow_resize = ->
		for _, ply in ipairs player.GetAll()
			if data = ply\GetPonyData()
				if bodygroup = data\GetBodygroupController()
					bodygroup\ResetTail()
					bodygroup\ResetMane()
					bodygroup\ResetBack()

	cvars.AddChangeCallback 'ppm2_sv_allow_resize', ppm2_sv_allow_resize, 'PPM2.Bodygroups'
else
	hook.Add 'PlayerNoClip', 'PPM2.WingsCheck', =>
		timer.Simple 0, ->
			return if not IsValid(@)
			if data = @GetPonyData()
				if bg = data\GetBodygroupController()
					bg\SlowUpdate()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

PPM2.GetBodygroupController = (model = 'models/ppm/player_default_base.mdl') -> DefaultBodygroupController.AVALIABLE_CONTROLLERS[model\lower()] or DefaultBodygroupController


textutils: new funs ltr and rtl

textutils: new funs ltr and rtl

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    say table.concat([ivar2.util.nonickalert(ivar2.channels[destination\lower!].nicks, n) for n,k in pairs(ivar2.channels[destination\lower!].nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    say ivar2.util.stripformatting(arg)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) =>
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)
  '^%prot13 (.+)$': (source, destination, arg) =>
    say ivar2.util.rot13(arg)
  '^%phex (.+)$': (source, destination, arg) =>
    say arg\gsub '.', (b) ->
      ('%02x ')\format(b\byte!)
  '^%prtl (.+)$': (source, destination, arg) =>
    say ''..arg
  '^%pltr (.+)$': (source, destination, arg) =>
    say ''..arg
  --'^%prepeat (%d+) (.*)$': (source, destination, nr, command) =>
  --  for i=1, nr
  --    @DispatchCommand 'PRIVMSG', command, source, destination

PRIVMSG:
  '^%pupper (.+)$': (source, destination, arg) =>
    say arg\upper!
  '^%plower (.+)$': (source, destination, arg) =>
    say arg\lower!
  '^%preverse (.+)$': (source, destination, arg) =>
    say arg\reverse!
  '^%plen (.+)$': (source, destination, arg) =>
    say tostring(arg\len!)
  '^%pnicks$': (source, destination) =>
    say table.concat([ivar2.util.nonickalert(ivar2.channels[destination\lower!].nicks, n) for n,k in pairs(ivar2.channels[destination\lower!].nicks)], ' ')
  '^%prandom (.+)$': (source, destination, arg) =>
    words = [word for word in arg\gmatch('%S+')]
    say words[math.random(1, #words)]
  '^%pbold (.+)$': (source, destination, arg) =>
    say ivar2.util.bold(arg)
  '^%punderline (.+)$': (source, destination, arg) =>
    say ivar2.util.underline(arg)
  '^%pitalic (.+)$': (source, destination, arg) =>
    say ivar2.util.italic(arg)
  '^%pinvert (.+)$': (source, destination, arg) =>
    say ivar2.util.reverse(arg)
  '^%ptrim (.+)$': (source, destination, arg) =>
    say ivar2.util.trim(arg)
  '^%pcolor ([0-9]+) (.+)$': (source, destination, color, arg) =>
    say ivar2.util.color(arg, color)
  '^%pfirst (.+)$': (source, destination, arg) =>
    say ivar2.util.split(arg, '%s')[1]
  '^%psplit (.-) (.*)$': (source, destination, arg, args) =>
    say table.concat(ivar2.util.split(args, arg), ' ')
  '^%preplace (.-) (.-) (.*)$': (source, destination, pat, repl, arg) =>
    new, n = string.gsub(arg, pat, repl)
    say(new)
  '^%pnocolor (.*)$': (source, destination, arg) =>
    say ivar2.util.stripformatting(arg)
  '^%pstutter (.*)$': (source, destination, arg) =>
    -- Stutter by anders from luabot
    s_senpai = 0.65
    say arg\gsub("(%a[%w%p]+)", (w) =>
      if math.random! >= s_senpai
        return (w\sub(1, 1).."-")\rep(math.random(1, 3))..w
      else
        return w)
  '^%prot13 (.+)$': (source, destination, arg) =>
    say ivar2.util.rot13(arg)
  '^%phex (.+)$': (source, destination, arg) =>
    say arg\gsub '.', (b) ->
      ('%02x ')\format(b\byte!)
  --'^%prepeat (%d+) (.*)$': (source, destination, nr, command) =>
  --  for i=1, nr
  --    @DispatchCommand 'PRIVMSG', command, source, destination


Minor syntactic changes to Tween

Minor syntactic changes to Tween

* Use unless instead of if not
* explicit return for finish so a value is not returned

import AddEventListener, DispatchEvent from require "LunoPunk.utils.EventListener"
import TweenEvent from require "LunoPunk.tweens.TweenEvent"

TweenType = {
	Persist: "Persist"
	Looping: "Looping"
	OneShot: "OneShot"
}

class Tween
	-- Constructor. Specify basic information about the Tween.
	-- @param	duration		Duration of the tween (in seconds or frames).
	-- @param	type			Tween type, one of Tween.PERSIST (default), Tween.LOOPING, or Tween.ONESHOT.
	-- @param	complete		Optional callback for when the Tween completes.
	-- @param	ease			Optional easer function to apply to the Tweened value.
	new: (duration, type = TweenType.Persist, complete = nil, ease = nil) =>
		assert duration != nil, "Cannot create a Tween without a duration"
		@active = false
		@__scale = 0.0
		@__parent = nil
		@__finish = false

		@__target = duration
		@type = if type == nil then "Persist" else type
		@ease = ease
		@t = 0
		@__time = 0

		unless complete == nil
			AddEventListener TweenEvent.FINISH, complete

	-- Updates the Tween, called by World.
	update: =>
		import LP from require "LunoPunk.LP"
		-- Only update the Tween if active
		return unless @active
		@__time += if LP.fixed then 1 else LP.elapsed
		@t = @__time/@__target
		if @ease != nil and @t > 0 and @t < 1
			@t = @ease @t

		if @__time >= @__target
			@t = 0
			@__finish = true

		DispatchEvent TweenEvent TweenEvent.UPDATE
		return

	-- Starts the Tween, or restarts it if it's currently running.
	start: =>
		@__time = 0
		if @__target == 0
			@active = false
			DispatchEvent TweenEvent TweenEvent.FINISH
		else
			@active = true
			DispatchEvent TweenEvent TweenEvent.START
		return

	-- @private Called when the Tween completes.
	finish: =>
		switch @type
			when "Persist"
				@__time = @__target
				@active = false
			when "Looping"
				@__time %= @__target
				@t = @__time/@__target
				if @ease != nil and @t > 0 and @t < 1
					@t = @ease @t
				@start!
			when "OneShot"
				@__time = @__target
				@active = false
				@__parent\removeTween @ unless @__parent == nil

		@__finish = false
		DispatchEvent TweenEvent TweenEvent.FINISH
		return

	-- Immediately stops the Tween and removes it from its Tweener without calling the complete callback.
	cancel: =>
		@active = false
		@__parent\removeTween @ unless @__parent == nil
		return

	percent: (value) =>
		return @__time/@__target if value == nil
		@__time = @__target * value
		@__time

	scale: => @__scale

{ :TweenType, :Tween }

import AddEventListener, DispatchEvent from require "LunoPunk.utils.EventListener"
import TweenEvent from require "LunoPunk.tweens.TweenEvent"

TweenType = {
	Persist: "Persist"
	Looping: "Looping"
	OneShot: "OneShot"
}

class Tween
	-- Constructor. Specify basic information about the Tween.
	-- @param	duration		Duration of the tween (in seconds or frames).
	-- @param	type			Tween type, one of Tween.PERSIST (default), Tween.LOOPING, or Tween.ONESHOT.
	-- @param	complete		Optional callback for when the Tween completes.
	-- @param	ease			Optional easer function to apply to the Tweened value.
	new: (duration, type = TweenType.Persist, complete = nil, ease = nil) =>
		assert duration != nil, "Cannot create a Tween without a duration"
		@active = false
		@__scale = 0.0
		@__parent = nil
		@__finish = false

		@__target = duration
		@type = if type == nil then "Persist" else type
		@ease = ease
		@t = 0
		@__time = 0

		unless complete == nil
			AddEventListener TweenEvent.FINISH, complete

	-- Updates the Tween, called by World.
	update: =>
		import LP from require "LunoPunk.LP"
		-- Only update the Tween if active
		return if not @active
		@__time += if LP.fixed then 1 else LP.elapsed
		@t = @__time/@__target
		if @ease != nil and @t > 0 and @t < 1
			@t = @ease @t

		if @__time >= @__target
			@t = 0
			@__finish = true

		DispatchEvent TweenEvent TweenEvent.UPDATE
		return

	-- Starts the Tween, or restarts it if it's currently running.
	start: =>
		@__time = 0
		if @__target == 0
			@active = false
			DispatchEvent TweenEvent TweenEvent.FINISH
		else
			@active = true
			DispatchEvent TweenEvent TweenEvent.START
		return

	-- @private Called when the Tween completes.
	finish: =>
		switch @type
			when "Persist"
				@__time = @__target
				@active = false
			when "Looping"
				@__time %= @__target
				@t = @__time/@__target
				if @ease != nil and @t > 0 and @t < 1
					@t = @ease @t
				@start!
			when "OneShot"
				@__time = @__target
				@active = false
				@__parent\removeTween @ unless @__parent == nil

		@__finish = false
		DispatchEvent TweenEvent TweenEvent.FINISH

	-- Immediately stops the Tween and removes it from its Tweener without calling the complete callback.
	cancel: =>
		@active = false
		@__parent\removeTween @ unless @__parent == nil
		return

	percent: (value) =>
		return @__time/@__target if value == nil
		@__time = @__target * value
		@__time

	scale: => @__scale

{ :TweenType, :Tween }


utf8: fix and add circle

utf8: fix and add circle

Former-commit-id: ecd4328d5c30a8f16405ad0336b8a5424145b8c9 [formerly 89a06c3c509d34d79f97eb03b67951d1d3d0075c]
Former-commit-id: 6dc447e8ea445474c68f45d193f8a4623216134c

html2unicode = require'html'

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

    table.concat(t, "")

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

html2unicode = require'html'

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

    table.concat(t, "")

wide = (source, destination, arg) =>
  @Msg 'privmsg', destination, source, replace(0xFEE0, arg)

blackletter = (source, destination, arg) =>
  @Msg 'privmsg', destination, source, replace(0x1D4A3, arg)

PRIVMSG:
  '^%pwide (.+)$': wide
  '^%pblackletter (.+)$': blackletter


Use new models by default

Use new models by default

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

export PPM2

player_manager.AddValidModel('pony', 'models/ppm/player_default_base_new.mdl')
list.Set('PlayerOptionsModel', 'pony', 'models/ppm/player_default_base_new.mdl')

player_manager.AddValidModel('pony_old', 'models/ppm/player_default_base.mdl')
list.Set('PlayerOptionsModel', 'pony_old', 'models/ppm/player_default_base.mdl')

player_manager.AddValidModel('pony_cppm', 'models/cppm/player_default_base.mdl')
list.Set('PlayerOptionsModel', 'pony_cppm', 'models/cppm/player_default_base.mdl')

player_manager.AddValidModel('ponynj', 'models/ppm/player_default_base_nj.mdl')
list.Set('PlayerOptionsModel', 'ponynj', 'models/ppm/player_default_base_nj.mdl')

player_manager.AddValidModel('ponynj_cppm', 'models/cppm/player_default_base_nj.mdl')
list.Set('PlayerOptionsModel', 'ponynj_cppm', 'models/cppm/player_default_base_nj.mdl')

player_manager.AddValidHands(model, 'models/cppm/pony_arms.mdl', 0, '') for model in *{'pony', 'pony_cppm', 'ponynj', 'ponynj_cppm', 'pony_old'}

PPM2 = PPM2 or {}

entMeta = FindMetaTable('Entity')
entMeta.IsPony = =>
    switch @GetModel()
        when 'models/ppm/player_default_base.mdl'
            return true
        when 'models/ppm/player_default_base_new.mdl'
            return true
        when 'models/ppm/player_default_base_nj.mdl'
            return true
        when 'models/cppm/player_default_base.mdl'
            return true
        when 'models/cppm/player_default_base_nj.mdl'
            return true
        else
            return false
entMeta.IsPonyCached = =>
    switch @__ppm2_lastmodel
        when 'models/ppm/player_default_base.mdl'
            return true
        when 'models/ppm/player_default_base_new.mdl'
            return true
        when 'models/ppm/player_default_base_nj.mdl'
            return true
        when 'models/cppm/player_default_base.mdl'
            return true
        when 'models/cppm/player_default_base_nj.mdl'
            return true
        else
            return false
entMeta.HasPonyModel = entMeta.IsPony

include 'autorun/ppm2/sh_networked_object.lua'
include 'autorun/ppm2/sh_registry.lua'
include 'autorun/ppm2/sh_hooks.lua'
include 'autorun/ppm2/sh_bodygroup_controller.lua'
include 'autorun/ppm2/sh_weight_controller.lua'
include 'autorun/ppm2/sh_flex_controller.lua'
include 'autorun/ppm2/sh_ponydata.lua'

if CLIENT
    file.CreateDir('ppm2')
    file.CreateDir('ppm2/backups')
    include 'autorun/ppm2/cl_data_instance.lua'
    include 'autorun/ppm2/cl_texture_controller.lua'
    include 'autorun/ppm2/cl_hooks.lua'
    include 'autorun/ppm2/cl_render_controller.lua'
    include 'autorun/ppm2/cl_editor.lua'

    for ent in *ents.GetAll()
        if ent.isPonyLegsModel
            ent\Remove()
else
    util.AddNetworkString('PPM2.RequestPonyData')
    util.AddNetworkString('PPM2.PlayerRespawn')
    util.AddNetworkString('PPM2.Require')
    util.AddNetworkString('PPM2.EditorStatus')
    util.AddNetworkString('PPM2.DamageAnimation')
    util.AddNetworkString('PPM2.KillAnimation')
    util.AddNetworkString('PPM2.AngerAnimation')

    AddCSLuaFile 'autorun/ppm2/sh_networked_object.lua'
    AddCSLuaFile 'autorun/ppm2/sh_registry.lua'
    AddCSLuaFile 'autorun/ppm2/sh_ponydata.lua'
    AddCSLuaFile 'autorun/ppm2/sh_hooks.lua'
    AddCSLuaFile 'autorun/ppm2/sh_bodygroup_controller.lua'
    AddCSLuaFile 'autorun/ppm2/sh_weight_controller.lua'
    AddCSLuaFile 'autorun/ppm2/sh_flex_controller.lua'
    AddCSLuaFile 'autorun/ppm2/cl_data_instance.lua'
    AddCSLuaFile 'autorun/ppm2/cl_texture_controller.lua'
    AddCSLuaFile 'autorun/ppm2/cl_hooks.lua'
    AddCSLuaFile 'autorun/ppm2/cl_render_controller.lua'
    AddCSLuaFile 'autorun/ppm2/cl_editor.lua'
    include 'autorun/ppm2/sv_hooks.lua'
    include 'autorun/ppm2/sv_fastdl.lua'

include 'autorun/ppm2/sh_vll_loader.lua' if VLL

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

export PPM2

player_manager.AddValidModel('ppm2_pony_new', 'models/ppm/player_default_base_new.mdl')
list.Set('PlayerOptionsModel', 'ppm2_pony_new', 'models/ppm/player_default_base_new.mdl')

player_manager.AddValidModel('pony', 'models/ppm/player_default_base.mdl')
list.Set('PlayerOptionsModel', 'pony', 'models/ppm/player_default_base.mdl')

player_manager.AddValidModel('ppm2_pony_cppm', 'models/cppm/player_default_base.mdl')
list.Set('PlayerOptionsModel', 'ppm2_pony_cppm', 'models/cppm/player_default_base.mdl')

player_manager.AddValidModel('ponynj', 'models/ppm/player_default_base_nj.mdl')
list.Set('PlayerOptionsModel', 'ponynj', 'models/ppm/player_default_base_nj.mdl')

player_manager.AddValidModel('ppm2_ponynj_cppm', 'models/cppm/player_default_base_nj.mdl')
list.Set('PlayerOptionsModel', 'ppm2_ponynj_cppm', 'models/cppm/player_default_base_nj.mdl')

player_manager.AddValidHands(model, 'models/cppm/pony_arms.mdl', 0, '') for model in *{'ppm2_pony', 'ppm2_pony_cppm', 'ppm2_ponynj', 'ppm2_ponynj_cppm', 'ppm2_pony_new'}

PPM2 = PPM2 or {}

entMeta = FindMetaTable('Entity')
entMeta.IsPony = =>
    switch @GetModel()
        when 'models/ppm/player_default_base.mdl'
            return true
        when 'models/ppm/player_default_base_new.mdl'
            return true
        when 'models/ppm/player_default_base_nj.mdl'
            return true
        when 'models/cppm/player_default_base.mdl'
            return true
        when 'models/cppm/player_default_base_nj.mdl'
            return true
        else
            return false
entMeta.IsPonyCached = =>
    switch @__ppm2_lastmodel
        when 'models/ppm/player_default_base.mdl'
            return true
        when 'models/ppm/player_default_base_new.mdl'
            return true
        when 'models/ppm/player_default_base_nj.mdl'
            return true
        when 'models/cppm/player_default_base.mdl'
            return true
        when 'models/cppm/player_default_base_nj.mdl'
            return true
        else
            return false
entMeta.HasPonyModel = entMeta.IsPony

include 'autorun/ppm2/sh_networked_object.lua'
include 'autorun/ppm2/sh_registry.lua'
include 'autorun/ppm2/sh_hooks.lua'
include 'autorun/ppm2/sh_bodygroup_controller.lua'
include 'autorun/ppm2/sh_weight_controller.lua'
include 'autorun/ppm2/sh_flex_controller.lua'
include 'autorun/ppm2/sh_ponydata.lua'

if CLIENT
    file.CreateDir('ppm2')
    file.CreateDir('ppm2/backups')
    include 'autorun/ppm2/cl_data_instance.lua'
    include 'autorun/ppm2/cl_texture_controller.lua'
    include 'autorun/ppm2/cl_hooks.lua'
    include 'autorun/ppm2/cl_render_controller.lua'
    include 'autorun/ppm2/cl_editor.lua'

    for ent in *ents.GetAll()
        if ent.isPonyLegsModel
            ent\Remove()
else
    util.AddNetworkString('PPM2.RequestPonyData')
    util.AddNetworkString('PPM2.PlayerRespawn')
    util.AddNetworkString('PPM2.Require')
    util.AddNetworkString('PPM2.EditorStatus')
    util.AddNetworkString('PPM2.DamageAnimation')
    util.AddNetworkString('PPM2.KillAnimation')
    util.AddNetworkString('PPM2.AngerAnimation')

    AddCSLuaFile 'autorun/ppm2/sh_networked_object.lua'
    AddCSLuaFile 'autorun/ppm2/sh_registry.lua'
    AddCSLuaFile 'autorun/ppm2/sh_ponydata.lua'
    AddCSLuaFile 'autorun/ppm2/sh_hooks.lua'
    AddCSLuaFile 'autorun/ppm2/sh_bodygroup_controller.lua'
    AddCSLuaFile 'autorun/ppm2/sh_weight_controller.lua'
    AddCSLuaFile 'autorun/ppm2/sh_flex_controller.lua'
    AddCSLuaFile 'autorun/ppm2/cl_data_instance.lua'
    AddCSLuaFile 'autorun/ppm2/cl_texture_controller.lua'
    AddCSLuaFile 'autorun/ppm2/cl_hooks.lua'
    AddCSLuaFile 'autorun/ppm2/cl_render_controller.lua'
    AddCSLuaFile 'autorun/ppm2/cl_editor.lua'
    include 'autorun/ppm2/sv_hooks.lua'
    include 'autorun/ppm2/sv_fastdl.lua'

include 'autorun/ppm2/sh_vll_loader.lua' if VLL


Better Tween defaults

Better Tween defaults

* Also assert if duration isn't set

export ^

TweenType = {
	"Persist"
	"Looping"
	"OneShot"
}

class Tween
	new: (duration, type = TweenType.Persist, complete = nil, ease = nil) =>
		assert duration != nil, "Cannot create a Tween without a duration"
		@active = false
		@percent = 0.0
		@__scale = 0.0
		@__parent = nil

		@target = duration
		@type = if type == nil then "Persist" else type
		@ease = ease
		@t = 0

		--if complete != nil
			-- TODO: Add event for when tween is finished
			-- addEventListener(TweenEvent.FINISH, complete);

	scale: =>
		@__scale

	update: =>
		@time += if LP.fixed then 1 else LP.elapsed
		@t = @time/@target
		if @ease != nil and @t > 0 and @t < 1
			@t = @ease @t

		if @time >= @target
			@t = 1
			@finish = true

		-- dispatchEvent(new TweenEvent(TweenEvent.UPDATE));
		return

	start: =>
		@time = 0
		if @target == 0
			@active = false
			-- dispatchEvent(new TweenEvent(TweenEvent.FINISH));
		else
			@active = true
			-- dispatchEvent(new TweenEvent(TweenEvent.START));
		return

	finish = =>
		switch @type
			when "Persist"
				@time = @target
				@active = false
			when "Looping"
				@time %= @target
				@t = @time/@target
				if @ease != nil and @t > 0 and @t < 1
					@t = @ease @t
				@\start!
			when "OneShot"
				@time = @target
				@active = false
				-- parent.removeTween(this);
		@finish = false
		-- dispatchEvent(new TweenEvent(TweenEvent.FINISH));

	cancel: =>
		@active = false
		@parent.removeTween @ if @parent != nil
		return

export ^

TweenType = {
	"Persist"
	"Looping"
	"OneShot"
}

class Tween
	new: (duration, type, complete, ease) =>
		@active = false
		@percent = 0.0
		@__scale = 0.0
		@__parent = nil

		@target = duration
		@type = if type == nil then "Persist" else type
		@ease = ease
		@t = 0

		--if complete != nil
			-- TODO: Add event for when tween is finished
			-- addEventListener(TweenEvent.FINISH, complete);

	scale: =>
		@__scale

	update: =>
		@time += if LP.fixed then 1 else LP.elapsed
		@t = @time/@target
		if @ease != nil and @t > 0 and @t < 1
			@t = @ease @t

		if @time >= @target
			@t = 1
			@finish = true

		-- dispatchEvent(new TweenEvent(TweenEvent.UPDATE));
		return

	start: =>
		@time = 0
		if @target == 0
			@active = false
			-- dispatchEvent(new TweenEvent(TweenEvent.FINISH));
		else
			@active = true
			-- dispatchEvent(new TweenEvent(TweenEvent.START));
		return

	finish = =>
		switch @type
			when "Persist"
				@time = @target
				@active = false
			when "Looping"
				@time %= @target
				@t = @time/@target
				if @ease != nil and @t > 0 and @t < 1
					@t = @ease @t
				@\start!
			when "OneShot"
				@time = @target
				@active = false
				-- parent.removeTween(this);
		@finish = false
		-- dispatchEvent(new TweenEvent(TweenEvent.FINISH));

	cancel: =>
		@active = false
		@parent.removeTween @ if @parent != nil
		return


Called wrong method when converting to number.

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

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

local Value

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

		@_signature = signature
		@id = signature.id

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

		return @_signature\match(value, mask)

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

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

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

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

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

		@_signature = signature
		@id = signature.id

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

		return Value(@_signature\get_value_by_name(name))

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

		return @_signature\has_value(value._value)

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

		return @_signature\get_name_by_value(value._value)

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

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

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

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

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

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

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

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

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

return Value

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

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

local Value

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

		@_signature = signature
		@id = signature.id

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

		return @_signature\match(value, mask)

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

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

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

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

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

		@_signature = signature
		@id = signature.id

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

		return Value(@_signature\get_value_by_name(name))

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

		return @_signature\has_value(value._value)

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

		return @_signature\get_name_by_value(value._value)

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

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

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

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

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

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

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

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

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

return Value


Fix path

Fix path

-- type checking for classes
_type = type
export type = (v) ->
  base = _type v
  if base == "table"
    cls = v.__class
    return cls.__name if cls
  base

sauce3.console = require "sauce3/core/console"
sauce3.java    = require "sauce3/core/java"

sauce3.File    = require "sauce3/utils/File"
sauce3.Font    = require "sauce3/utils/Font"
sauce3.Image   = require "sauce3/utils/Image"
sauce3.Quad    = require "sauce3/utils/Quad"

sauce3.file_system = require "sauce3/core/file_system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.graphics    = require "sauce3/core/graphics"
sauce3.console     = require "sauce3/core/console"
sauce3.system      = require "sauce3/core/system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.audio       = require "sauce3/core/audio"
sauce3.ecs         = require "sauce3/core/ecs"

import key_codes, button_codes from require "sauce3/wrappers"

sauce3._key_pressed = (key) ->
  (sauce3.key_pressed key_codes[key]) if sauce3.key_pressed

sauce3._key_released = (key) ->
  (sauce3.key_released key_codes[key]) if sauce3.key_released

sauce3._mouse_pressed = (x, y, button) ->
  (sauce3.mouse_pressed button_codes[button]) if sauce3.mouse_pressed

sauce3._mouse_released = (x, y, button) ->
  (sauce3.mouse_released button_codes[button]) if sauce3.mouse_released

sauce3._quit = ->
  if sauce3.quit then sauce3.quit!
  sauce3.audio.stop_all!

sauce3.run = ->
  dt = sauce3.timer.get_delta!

  sauce3.update(dt) if sauce3.update

  sauce3.graphics.clear!
  sauce3.graphics.origin!

  sauce3.draw! if sauce3.draw

  sauce3.graphics.present!

require "main"

-- type checking for classes
_type = type
export type = (v) ->
  base = _type v
  if base == "table"
    cls = v.__class
    return cls.__name if cls
  base

sauce3.console = require "sauce3/core/console"
sauce3.java    = require "sauce3/core/java"

sauce3.File    = require "sauce3/utils/File"
sauce3.Font    = require "sauce3/utils/Font"
sauce3.Image   = require "sauce3/utils/Image"
sauce3.Quad    = require "sauce3/utils/Quad"

sauce3.file_system = require "sauce3/core/file_system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.graphics    = require "sauce3/core/graphics"
sauce3.console     = require "sauce3/core/console"
sauce3.system      = require "sauce3/core/system"
sauce3.timer       = require "sauce3/core/timer"
sauce3.audio       = require "sauce3/core/audio"

sauce3.ecs         = require "sauce3/framework/ecs"

import key_codes, button_codes from require "sauce3/wrappers"

sauce3._key_pressed = (key) ->
  (sauce3.key_pressed key_codes[key]) if sauce3.key_pressed

sauce3._key_released = (key) ->
  (sauce3.key_released key_codes[key]) if sauce3.key_released

sauce3._mouse_pressed = (x, y, button) ->
  (sauce3.mouse_pressed button_codes[button]) if sauce3.mouse_pressed

sauce3._mouse_released = (x, y, button) ->
  (sauce3.mouse_released button_codes[button]) if sauce3.mouse_released

sauce3._quit = ->
  if sauce3.quit then sauce3.quit!
  sauce3.audio.stop_all!

sauce3.run = ->
  dt = sauce3.timer.get_delta!

  sauce3.update(dt) if sauce3.update

  sauce3.graphics.clear!
  sauce3.graphics.origin!

  sauce3.draw! if sauce3.draw

  sauce3.graphics.present!

require "main"