Fixed lua errors on player death

Fixed lua errors on player death

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

import PPM2 from _G

PPM2.ALTERNATIVE_RENDER = CreateConVar('ppm2_alternative_render', '0', {FCVAR_ARCHIVE}, 'Enable alternative render mode. This decreases FPS, enables compability with third-party BROKEN addons.')
ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')

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

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

	GetEntity: => @ent
	GetModel: => @controller\GetModel()

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

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

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

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

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

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

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

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

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

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

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

		if ply\InVehicle()
			local bonePos

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

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

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

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

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

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

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

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

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

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @ent\ShouldDrawLocalPlayer()
		return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)
		@UpdateLegs()

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

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

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

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

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @ent\Alive()
		return if @ent\ShouldDrawLocalPlayer()
		return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

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

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @ent\ShouldDrawLocalPlayer()
		return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

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

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

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

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

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

	PlayerDeath: =>
		return if not @isValid
		if @emotes
			@emotes\Remove()
			@emotes = nil
		@HideModels(true)
		@GetTextureController()\ResetTextures() if @GetTextureController() and @ent\IsPony()

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

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

	HideModels: (status = true) =>
		return if @hideModels == status
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)
		@hideModels = status

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

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime

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

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

		if IsValid(@staringAt)
			trNew = util.TraceLine({
				start: lpos,
				endpos: lpos + @ent\EyeAnglesFixed()\Forward() * 270,
				filter: @ent,
			})

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

			epos = @staringAt\EyePos()
			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@ent, @staringAt) and @CheckTarget(lpos, epos)
				@ent\SetEyeTarget(epos)
				return
			@staringAt = NULL
			@ent\SetEyeTarget(Vector())

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

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

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt)
		@prevRollTargetPos = LerpVector(FrameTime() * 6, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@ent\EyeAnglesFixed())
		@ent\SetEyeTarget(@ent\EyePos() + roll)

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

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)
			if PPM2.ALTERNATIVE_RENDER\GetBool() or drawingNewTask
				\UpdateSocks(@ent, @socksModel) if IsValid(@socksModel)
				\UpdateNewSocks(@ent, @newSocksModel) if IsValid(@newSocksModel)

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

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

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

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

	@ARMS_MATERIAL_INDEX = 0
	PreDrawArms: (ent) =>
		return if not @isValid
		if ent and not @armsWeightSetup
			@armsWeightSetup = true
			weight = @GetData()\GetWeight()
			vec = Vector(weight, weight, weight)
			ent\ManipulateBoneScale2Safe(i, vec) for i = 1, 13
		render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
	PostDrawArms: (ent) =>
		return if not @isValid
		render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

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

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

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

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

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

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

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

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

	HideModels: (status = true) =>
		return if @hideModels == status
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)
		super(status)

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

		if PPM2.ALTERNATIVE_RENDER\GetBool() or drawingNewTask
			textures = @GetTextureController()
			return if not textures
			textures\UpdateUpperMane(@ent, @upperManeModel) if IsValid(@upperManeModel)
			textures\UpdateLowerMane(@ent, @lowerManeModel) if IsValid(@lowerManeModel)
			textures\UpdateTail(@ent, @tailModel) if IsValid(@tailModel)

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

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

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

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

import PPM2 from _G

PPM2.ALTERNATIVE_RENDER = CreateConVar('ppm2_alternative_render', '0', {FCVAR_ARCHIVE}, 'Enable alternative render mode. This decreases FPS, enables compability with third-party BROKEN addons.')
ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')

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

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

	GetEntity: => @ent
	GetModel: => @controller\GetModel()

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

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

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

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

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

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

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

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

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

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

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

		if ply\InVehicle()
			local bonePos

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

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

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

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

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

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

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

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

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

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @ent\ShouldDrawLocalPlayer()
		return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)
		@UpdateLegs()

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

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

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

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

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @ent\Alive()
		return if @ent\ShouldDrawLocalPlayer()
		return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

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

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @ent\ShouldDrawLocalPlayer()
		return if (@ent\GetPos() + @ent\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

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

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

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

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

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

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

	PlayerDeath: =>
		return if not @isValid
		@emotes\Remove()
		@emotes = nil
		@HideModels(true)
		@GetTextureController()\ResetTextures() if @GetTextureController() and @ent\IsPony()

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

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

	HideModels: (status = true) =>
		return if @hideModels == status
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)
		@hideModels = status

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

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime

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

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

		if IsValid(@staringAt)
			trNew = util.TraceLine({
				start: lpos,
				endpos: lpos + @ent\EyeAnglesFixed()\Forward() * 270,
				filter: @ent,
			})

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

			epos = @staringAt\EyePos()
			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@ent, @staringAt) and @CheckTarget(lpos, epos)
				@ent\SetEyeTarget(epos)
				return
			@staringAt = NULL
			@ent\SetEyeTarget(Vector())

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

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

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt)
		@prevRollTargetPos = LerpVector(FrameTime() * 6, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@ent\EyeAnglesFixed())
		@ent\SetEyeTarget(@ent\EyePos() + roll)

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

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)
			if PPM2.ALTERNATIVE_RENDER\GetBool() or drawingNewTask
				\UpdateSocks(@ent, @socksModel) if IsValid(@socksModel)
				\UpdateNewSocks(@ent, @newSocksModel) if IsValid(@newSocksModel)

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

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

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

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

	@ARMS_MATERIAL_INDEX = 0
	PreDrawArms: (ent) =>
		return if not @isValid
		if ent and not @armsWeightSetup
			@armsWeightSetup = true
			weight = @GetData()\GetWeight()
			vec = Vector(weight, weight, weight)
			ent\ManipulateBoneScale2Safe(i, vec) for i = 1, 13
		render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBody())
	PostDrawArms: (ent) =>
		return if not @isValid
		render.MaterialOverrideByIndex(@@ARMS_MATERIAL_INDEX)

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

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

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

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

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

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

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

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

	HideModels: (status = true) =>
		return if @hideModels == status
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)
		super(status)

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

		if PPM2.ALTERNATIVE_RENDER\GetBool() or drawingNewTask
			textures = @GetTextureController()
			return if not textures
			textures\UpdateUpperMane(@ent, @upperManeModel) if IsValid(@upperManeModel)
			textures\UpdateLowerMane(@ent, @lowerManeModel) if IsValid(@lowerManeModel)
			textures\UpdateTail(@ent, @tailModel) if IsValid(@tailModel)

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

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

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


Call SetIsActive immidiately after flexstate creation

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

--
-- 0    eyes_updown
-- 1    eyes_rightleft
-- 2    JawOpen
-- 3    JawClose
-- 4    Smirk
-- 5    Frown
-- 6    Stretch
-- 7    Pucker
-- 8    Grin
-- 9    CatFace
-- 10   Mouth_O
-- 11   Mouth_O2
-- 12   Mouth_Full
-- 13   Tongue_Out
-- 14   Tongue_Up
-- 15   Tongue_Down
-- 16   NoEyelashes
-- 17   Eyes_Blink
-- 18   Left_Blink
-- 19   Right_Blink
-- 20   Scrunch
-- 21   FatButt
-- 22   Stomach_Out
-- 23   Stomach_In
-- 24   Throat_Bulge
-- 25   Male
-- 26   Hoof_Fluffers
-- 27   o3o
-- 28   Ear_Fluffers
-- 29   Fangs
-- 30   Claw_Teeth
-- 31   Fang_Test
-- 32   angry_eyes
-- 33   sad_eyes
-- 34   Eyes_Blink_Lower
-- 35   Male_2
-- 36   Buff_Body
-- 37   Manliest_Chin
-- 38   Lowerlid_Raise
-- 39   Happy_Eyes
-- 40   Duck

BLINK_CURVE = {0, 0.2, 0.45, 0.65, 0.9, 1, 1, 0.75, 0.55, 0.25, 0.15, 0}
BREATH_CURVE = {0, 0, 0, 0.1, 0.3, 0.55, 0.85, 1, 1, 0.85, 0.65, 0.4, 0.2, 0, 0, 0}

DISABLE_FLEXES = CreateConVar('ppm2_disable_flexes', '0', {FCVAR_ARCHIVE}, 'Disable pony flexes controllers. Saves some FPS.')

class FlexState extends PPM2.ModifierBase
	@SetupModifiers: =>
		@RegisterModifier('Speed', 0)
		@RegisterModifier('Scale', 0)
		@RegisterModifier('Weight', 0)

	new: (controller, flexName = '', flexID = 0, scale = 1, speed = 1, active = true, min = 0, max = 1, useModifiers = true) =>
		super()
		@controller = controller
		@name = flexName
		@flexName = flexName
		@flexID = flexID
		@id = flexID
		@scale = scale
		@speed = speed
		@originalscale = scale
		@originalspeed = speed
		@min = min
		@max = max
		@current = -1
		@target = 0
		@speedModify = 1
		@scaleModify = 1
		@modifiers = {}
		@useModifiers = useModifiers
		@active = active
		@useLerp = true
		@lerpMultiplier = 1
		@activeID = "DisableFlex#{@flexName}"
		controller = @controller\GetController()\GetData()
		@SetIsActive(not controller['Get' .. @activeID](controller)) if controller['Get' .. @activeID]

	__tostring: => "[#{@@__name}:#{@flexName}[#{@flexID}]|#{@GetData()}]"

	GetFlexID: => @flexID
	GetFlexName: => @flexName
	SetUseLerp: (val = true) => @useLerp = val
	GetUseLerp: => @useLerp
	UseLerp: => @useLerp
	SetLerpModify: (val = 1) => @lerpMultiplier = val
	GetLerpModify: => @lerpMultiplier
	LerpModify: => @lerpMultiplier

	GetEntity: => @controller\GetEntity()
	GetData: => @controller
	GetController: => @controller
	GetValue: => @current
	GetRealValue: => @target
	SetValue: (val = @target) =>
		@current = math.Clamp(val, @min, @max) * @scale * @scaleModify
		@target = @target
	SetRealValue: (val = @target) => @target = math.Clamp(val, @min, @max) * @scale * @scaleModify

	GetScale: => @scale
	GetSpeed: => @speed
	GetScaleModify: => @scaleModify
	GetSpeedModify: => @speedModify
	GetOriginalScale: => @originalscale
	GetOriginalSpeed: => @originalspeed

	SetScale: (val = @scale) => @scale = val
	GetSpeed: (val = @speed) => @speed = val
	SetScaleModify: (val = @scaleModify) => @scaleModify = val
	GetSpeedModify: (val = @speedModify) => @speedModify = val

	GetIsActive: => @active
	SetIsActive: (val = true) => @active = val

	AddValue: (val = 0) => @SetValue(@current + val)
	AddRealValue: (val = 0) => @SetRealValue(@target + val)
	Think: (ent = @GetEntity(), delta = 0) =>
		return if not @active

		if @useModifiers
			@current = 0
			@scale = @originalscale * @scaleModify
			@speed = @originalspeed * @speedModify

			if @useLerp
				for i = 1, #@WeightModifiers
					@modifiers[i] = Lerp(delta * 15 * @speed * @speedModify * @lerpMultiplier, @modifiers[i] or 0, @WeightModifiers[i])
					@current += @modifiers[i]
			else
				@current += @WeightModifiers[i] for i = 1, #@WeightModifiers

			@scale += modif for _, modif in ipairs @ScaleModifiers
			@speed += modif for _, modif in ipairs @SpeedModifiers
			@current = math.Clamp(@current, @min, @max) * @scale

		ent\SetFlexWeight(@flexID, @current)
	DataChanges: (state) =>
		return if state\GetKey() ~= @activeID
		@SetIsActive(not state\GetValue())
		@GetController()\RebuildIterableList()
		@Reset()
	Reset: (resetVars = true) =>
		for name, id in pairs @modifiersNames
			@ResetModifiers(name)
		if resetVars
			@scaleModify = 1
			@speedModify = 1
		@scale = @originalscale * @scaleModify
		@speed = @originalspeed * @speedModify
		@target = 0
		@current = 0
		@GetEntity()\SetFlexWeight(@flexID, 0) if IsValid(@GetEntity())

PPM2.FlexState = FlexState

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

		{
			'ids': @flexIDsIterable
			'numid': @numid
		} = data

		@flexIDS = {}
		@flexStates = {}
		i = 1
		for _, id in ipairs data.ids
			state = controller\GetFlexState(id)
			num = state\GetModifierID(@name)
			@["flex_#{id}"] = num
			@flexIDS[id] = num
			@flexStates[id] = state
			@flexStates[i] = state
			@flexIDS[i] = num
			i += 1

		@controller = controller
		@Launch()

	GetController: => @controller
	GetModifierID: (id = '') => @flexIDS[id]
	GetFlexState: (id = '') => @flexStates[id]

	Think: (delta = 0) =>
		return false if not IsValid(@GetEntity())
		super(delta)

	Stop: =>
		super()
		return unless @parent
		@parent\GetFlexState(id)\ResetModifiers(@name) for _, id in ipairs @flexIDsIterable

	SetModifierWeight: (id = '', val = 0) => @GetFlexState(id)\SetModifierWeight(@GetModifierID(id), val)
	SetModifierSpeed: (id = '', val = 0) => @GetFlexState(id)\SetModifierSpeed(@GetModifierID(id), val)
	SetUseLerp: (id = '', status = true) => @GetFlexState(id)\SetUseLerp(status)
	GetUseLerp: (id = '') => @GetFlexState(id)\GetUseLerp()

PPM2.FlexSequence = FlexSequence

class PonyFlexController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

	@FLEX_LIST = {
		{flex: 'eyes_updown',       scale: 1, speed: 1, active: false}
		{flex: 'eyes_rightleft',    scale: 1, speed: 1, active: false}
		{flex: 'JawOpen',           scale: 1, speed: 1, active: true}
		{flex: 'JawClose',          scale: 1, speed: 1, active: true}
		{flex: 'Smirk',             scale: 1, speed: 1, active: true}
		{flex: 'Frown',             scale: 1, speed: 1, active: true}
		{flex: 'Stretch',           scale: 1, speed: 1, active: false}
		{flex: 'Pucker',            scale: 1, speed: 1, active: false}
		{flex: 'Grin',              scale: 1, speed: 1, active: true}
		{flex: 'CatFace',           scale: 1, speed: 1, active: true}
		{flex: 'Mouth_O',           scale: 1, speed: 1, active: true}
		{flex: 'Mouth_O2',          scale: 1, speed: 1, active: true}
		{flex: 'Mouth_Full',        scale: 1, speed: 1, active: false}
		{flex: 'Tongue_Out',        scale: 1, speed: 1, active: true}
		{flex: 'Tongue_Up',         scale: 1, speed: 1, active: true}
		{flex: 'Tongue_Down',       scale: 1, speed: 1, active: true}
		{flex: 'NoEyelashes',       scale: 1, speed: 1, active: false}
		{flex: 'Eyes_Blink',        scale: 1, speed: 1, active: false}
		{flex: 'Left_Blink',        scale: 1, speed: 1, active: true}
		{flex: 'Right_Blink',       scale: 1, speed: 1, active: true}
		{flex: 'Scrunch',           scale: 1, speed: 1, active: true}
		{flex: 'FatButt',           scale: 1, speed: 1, active: false}
		{flex: 'Stomach_Out',       scale: 1, speed: 1, active: true}
		{flex: 'Stomach_In',        scale: 1, speed: 1, active: true}
		{flex: 'Throat_Bulge',      scale: 1, speed: 1, active: true}
		{flex: 'Male',              scale: 1, speed: 1, active: false}
		{flex: 'Hoof_Fluffers',     scale: 1, speed: 1, active: false}
		{flex: 'o3o',               scale: 1, speed: 1, active: true}
		{flex: 'Ear_Fluffers',      scale: 1, speed: 1, active: false}
		{flex: 'Fangs',             scale: 1, speed: 1, active: false}
		{flex: 'Claw_Teeth',        scale: 1, speed: 1, active: false}
		{flex: 'Fang_Test',         scale: 1, speed: 1, active: false}
		{flex: 'angry_eyes',        scale: 1, speed: 1, active: true}
		{flex: 'sad_eyes',          scale: 1, speed: 1, active: true}
		{flex: 'Eyes_Blink_Lower',  scale: 1, speed: 1, active: true}
		{flex: 'Male_2',            scale: 1, speed: 1, active: false}
		{flex: 'Buff_Body',         scale: 1, speed: 1, active: false}
		{flex: 'Manliest_Chin',     scale: 1, speed: 1, active: false}
		{flex: 'Lowerlid_Raise',    scale: 1, speed: 1, active: false}
		{flex: 'Happy_Eyes',        scale: 1, speed: 1, active: true}
		{flex: 'Duck',              scale: 1, speed: 1, active: true}

	}

	@SEQUENCES = {
		{
			'name': 'anger'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Frown', 'Grin', 'angry_eyes', 'Scrunch'}
			'reset': =>
				@SetTime(math.random(15, 45) / 10)
				@lastStrengthUpdate = @lastStrengthUpdate or 0
				if @lastStrengthUpdate < CurTimeL()
					@lastStrengthUpdate = CurTimeL() + 2
					@frownStrength = math.random(40, 100) / 100
					@grinStrength = math.random(15, 40) / 100
					@angryStrength = math.random(30, 80) / 100
					@scrunchStrength = math.random(50, 100) / 100
					@SetModifierWeight(1, @frownStrength)
					@SetModifierWeight(2, @grinStrength)
					@SetModifierWeight(3, @angryStrength)
					@SetModifierWeight(4, @scrunchStrength)
		}

		{
			'name': 'sad'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Frown', 'Grin', 'sad_eyes'}
			'reset': =>
				@SetTime(math.random(15, 45) / 10)
				@lastStrengthUpdate = @lastStrengthUpdate or 0
				if @lastStrengthUpdate < CurTimeL()
					@lastStrengthUpdate = CurTimeL() + 2
					@frownStrength = math.random(40, 100) / 100
					@grinStrength = math.random(15, 40) / 100
					@angryStrength = math.random(30, 80) / 100
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, @frownStrength)
				@SetModifierWeight(2, @grinStrength)
				@SetModifierWeight(3, @angryStrength)
		}

		{
			'name': 'ugh'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'sad_eyes', 'Eyes_Blink_Lower'}
			'reset': =>
				@SetModifierWeight(1, math.Rand(0.27, 0.34))
				@SetModifierWeight(2, math.Rand(0.3, 0.35))
				@PauseSequence('eyes_blink')
				@PauseSequence('eyes_idle')
		}

		{
			'name': 'suggestive_eyes'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'sad_eyes', 'Eyes_Blink_Lower'}
			'reset': =>
				@SetModifierWeight(1, 0.28)
				@SetModifierWeight(2, 0.4)
				@PauseSequence('eyes_blink')
				@PauseSequence('eyes_idle')
		}

		{
			'name': 'lips_lick'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out', 'Tongue_Up'}
			'reset': =>
				@SetModifierWeight(1, 0.9)
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(2, 0.75 + math.sin(CurTimeL() * 7) * 0.25)
		}

		{
			'name': 'tongue_pullout'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 0.15 + math.sin(CurTimeL() * 10) * 0.1)
		}

		{
			'name': 'tongue_pullout_twitch'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 0.5 + math.sin(CurTimeL() * 4) * 0.5)
		}

		{
			'name': 'tongue_pullout_twitch_fast'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 0.5 + math.sin(CurTimeL() * 8) * 0.5)
		}

		{
			'name': 'suggestive_open'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Pucker', 'JawOpen', 'Scrunch'}
			'reset': =>
				@SetModifierWeight(1, math.Rand(0.28, 0.34))
				@SetModifierWeight(2, math.Rand(0.35, 0.40))
				@SetModifierWeight(3, math.Rand(0.45, 0.50))
		}

		{
			'name': 'suggestive_open_anim'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Pucker', 'JawOpen', 'Scrunch'}
			'reset': =>
				@SetModifierWeight(1, math.Rand(0.28, 0.34))
				@SetModifierWeight(3, math.Rand(0.45, 0.50))
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(2, 0.2 + math.sin(CurTimeL() * 16) * 0.07)
		}

		{
			'name': 'face_smirk'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Smirk', 'Frown'}
			'reset': =>
				@SetModifierWeight(1, 0.78)
				@SetModifierWeight(2, 0.61)
		}

		{
			'name': 'eyes_idle'
			'autostart': true
			'repeat': true
			'time': 5
			'ids': {'Left_Blink', 'Right_Blink'}
			'func': (delta, timeOfAnim) =>
				return false if @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				value = math.abs(math.sin(CurTimeL() * .5) * .15)
				@SetModifierWeight(1, value)
				@SetModifierWeight(2, value)
		}

		{
			'name': 'eyes_close'
			'autostart': true
			'repeat': true
			'time': 5
			'ids': {'Left_Blink', 'Right_Blink', 'Frown'}
			'func': (delta, timeOfAnim) =>
				return if not @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				@SetModifierWeight(1, 1)
				@SetModifierWeight(2, 1)
				@SetModifierWeight(3, 0.5)
		}

		{
			'name': 'body_idle'
			'autostart': true
			'repeat': true
			'time': 2
			'ids': {'Stomach_Out'}
			'func': (delta, timeOfAnim) =>
				return false if @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				return if timeOfAnim < 0 or timeOfAnim > 1
				@SetModifierWeight(1, math.tbezier(timeOfAnim, BREATH_CURVE) * 0.35)
		}

		{
			'name': 'health_idle'
			'autostart': true
			'repeat': true
			'time': 5
			'ids': {'Frown', 'Left_Blink', 'Right_Blink', 'Scrunch', 'Mouth_O', 'JawOpen', 'Grin'}
			'func': (delta, timeOfAnim) =>
				return false if not @GetEntity()\IsPlayer() and not @GetEntity()\IsNPC() and @GetEntity().Type ~= 'nextbot'
				frown = @GetModifierID(1)
				frownState = @GetFlexState(1)
				left, right = @GetModifierID(2), @GetModifierID(3)
				leftState, rightState = @GetFlexState(2), @GetFlexState(3)
				Mouth_O, Mouth_OState = @GetModifierID(4), @GetFlexState(4)
				Scrunch = @GetModifierID(4)
				ScrunchState = @GetFlexState(4)

				hp, mhp = @GetEntity()\Health(), @GetEntity()\GetMaxHealth()
				mhp = 1 if mhp == 0
				div = hp / mhp
				strength = math.Clamp(1.5 - div * 1.5, 0, 1)
				frownState\SetModifierWeight(frown, strength)
				ScrunchState\SetModifierWeight(Scrunch, strength * .5)
				leftState\SetModifierWeight(left, strength * .1)
				rightState\SetModifierWeight(right, strength * .1)
				Mouth_OState\SetModifierWeight(Mouth_O, strength * .8)

				JawOpen = @GetModifierID(6)
				JawOpenState = @GetFlexState(6)

				if strength > .75
					JawOpenState\SetModifierWeight(JawOpen, strength * .2 + math.sin(CurTimeL() * strength * 3) * .1)
				else
					JawOpenState\SetModifierWeight(JawOpen, 0)

				if div >= 2
					@SetModifierWeight(7, .5)
				else
					@SetModifierWeight(7, 0)
		}

		{
			'name': 'greeny'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Grin'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				strength = .5 + math.sin(CurTimeL() * 2) * .25
				GrinState\SetModifierWeight(Grin, strength)
		}

		{
			'name': 'big_grin'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Grin'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, 1)
		}

		{
			'name': 'o3o'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'o3o'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 1)
		}

		{
			'name': 'owo_alternative'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'o3o', 'JawOpen'}
			'reset': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.Rand(0.8, 1))
				@SetModifierWeight(2, math.Rand(0.05, 0.1))
		}

		{
			'name': 'xd'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Grin', 'Left_Blink', 'Right_Blink', 'JawOpen'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, .6)

				Left_Blink = @GetModifierID(2)
				Left_BlinkState = @GetFlexState(2)
				Left_BlinkState\SetModifierWeight(Left_Blink, .9)

				Right_Blink = @GetModifierID(3)
				Right_BlinkState = @GetFlexState(3)
				Right_BlinkState\SetModifierWeight(Right_Blink, .9)

				JawOpen = @GetModifierID(4)
				JawOpenState = @GetFlexState(4)
				JawOpenState\SetModifierScale(JawOpen, 2)
				JawOpenState\SetModifierWeight(JawOpen, (timeOfAnim % .1) * 2)
		}

		{
			'name': 'tongue'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'JawOpen', 'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, .1)
				@SetModifierWeight(2, 1)
		}

		{
			'name': 'angry_tongue'
			'autostart': false
			'repeat': false
			'time': 6
			'ids': {'Frown', 'Grin', 'angry_eyes', 'Scrunch', 'JawOpen', 'Tongue_Out'}
			'reset': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.random(40, 100) / 100)
				@SetModifierWeight(2, math.random(15, 40) / 100)
				@SetModifierWeight(3, math.random(30, 80) / 100)
				@SetModifierWeight(4, math.random(50, 100) / 100)
				@SetModifierWeight(5, math.random(10, 15) / 100)
				@SetModifierWeight(6, math.random(80, 100) / 100)
		}

		{
			'name': 'pffff'
			'autostart': false
			'repeat': false
			'time': 6
			'ids': {'Frown', 'Grin', 'angry_eyes', 'Scrunch', 'JawOpen', 'Tongue_Out', 'Tongue_Down', 'Tongue_Up'}
			'reset': =>
				@SetModifierWeight(1, math.random(40, 100) / 100)
				@SetModifierWeight(2, math.random(15, 40) / 100)
				@SetModifierWeight(3, math.random(30, 80) / 100)
				@SetModifierWeight(4, math.random(50, 100) / 100)
				@SetModifierWeight(5, math.random(10, 15) / 100)
				@SetModifierWeight(6, math.random(80, 100) / 100)
			'func': (delta, timeOfAnim) =>
				val = math.sin(CurTimeL() * 8) * .6
				if val > 0
					@SetModifierWeight(7, val)
					@SetModifierWeight(8, 0)
				else
					@SetModifierWeight(7, 0)
					@SetModifierWeight(8, -val)
		}

		{
			'name': 'cat'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'CatFace'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, 1)
		}

		{
			'name': 'ooo'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Mouth_O2', 'Mouth_O'}
			'func': (delta, timeOfAnim) =>
				timeOfAnim *= 2
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, timeOfAnim)
				Grin = @GetModifierID(2)
				GrinState = @GetFlexState(2)
				GrinState\SetModifierWeight(Grin, timeOfAnim)
		}

		{
			'name': 'talk'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
			'create': =>
				@talkAnim = for i = 0, 1, 0.05
					rand = math.random(1, 100) / 100
					if rand <= .25
						{1 * rand, 0.4 * rand, 2 * rand, 0}
					elseif rand >= .25 and rand < .4
						rand *= .8
						{2 * rand, .6 * rand, 0, 1 * rand}
					elseif rand >= .4 and rand < .75
						rand *= .6
						{1 * rand, 0, 1 * rand, 2 * rand}
					elseif rand >= .75
						rand *= .4
						{1.5 * rand, 0, 1 * rand, 0}
				@SetModifierSpeed(1, 2)
				@SetModifierSpeed(2, 2)
				@SetModifierSpeed(3, 2)
				@SetModifierSpeed(4, 2)
			'func': (delta, timeOfAnim) =>
				JawOpen = @GetModifierID(1)
				JawOpenState = @GetFlexState(1)
				Tongue_OutOpen = @GetModifierID(2)
				Tongue_OutOpenState = @GetFlexState(2)
				Tongue_UpOpen = @GetModifierID(3)
				Tongue_UpOpenState = @GetFlexState(3)
				Tongue_DownOpen = @GetModifierID(4)
				Tongue_DownOpenState = @GetFlexState(4)
				cPos = math.floor(timeOfAnim * 20) + 1
				data = @talkAnim[cPos]
				return if not data
				{jaw, out, up, down} = data
				JawOpenState\SetModifierWeight(JawOpen, jaw)
				Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
				Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
				Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
		}

		{
			'name': 'talk_endless'
			'autostart': false
			'repeat': true
			'time': 4
			'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
			'create': =>
				@talkAnim = for i = 0, 1, 0.05
					rand = math.random(1, 100) / 100
					if rand <= .25
						{1 * rand, 0.4 * rand, 2 * rand, 0}
					elseif rand >= .25 and rand < .4
						rand *= .8
						{2 * rand, .6 * rand, 0, 1 * rand}
					elseif rand >= .4 and rand < .75
						rand *= .6
						{1 * rand, 0, 1 * rand, 2 * rand}
					elseif rand >= .75
						rand *= .4
						{1.5 * rand, 0, 1 * rand, 0}
				@SetModifierSpeed(1, 2)
				@SetModifierSpeed(2, 2)
				@SetModifierSpeed(3, 2)
				@SetModifierSpeed(4, 2)
			'func': (delta, timeOfAnim) =>
				JawOpen = @GetModifierID(1)
				JawOpenState = @GetFlexState(1)
				Tongue_OutOpen = @GetModifierID(2)
				Tongue_OutOpenState = @GetFlexState(2)
				Tongue_UpOpen = @GetModifierID(3)
				Tongue_UpOpenState = @GetFlexState(3)
				Tongue_DownOpen = @GetModifierID(4)
				Tongue_DownOpenState = @GetFlexState(4)
				cPos = math.floor(timeOfAnim * 20) + 1
				data = @talkAnim[cPos]
				return if not data
				{jaw, out, up, down} = data
				volume = @GetEntity()\VoiceVolume() * 6
				jaw *= volume
				out *= volume
				up *= volume
				down *= volume
				JawOpenState\SetModifierWeight(JawOpen, jaw)
				Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
				Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
				Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
		}

		{
			'name': 'eyes_blink'
			'autostart': true
			'repeat': true
			'time': 7
			'ids': {'Eyes_Blink_Lower'}
			'create': =>
				@SetUseLerp(1, false)

			'reset': =>
				@nextBlink = math.random(300, 600) / 1000
				@nextBlinkLength = math.random(15, 30) / 800
				@min, @max = @nextBlink, @nextBlink + @nextBlinkLength

			'func': (delta, timeOfAnim) =>
				return false if @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				if @min > timeOfAnim or @max < timeOfAnim
					if @blinkHit
						@blinkHit = false
						@SetModifierWeight(1, 0)

					return

				len = (timeOfAnim - @min) / @nextBlinkLength
				blink = math.tbezier(len\clamp(0, 1), BLINK_CURVE)

				@SetModifierWeight(1, blink)
				-- print(len, blink)

				@blinkHit = true
		}

		{
			'name': 'hurt'
			'autostart': false
			'repeat': false
			'time': 4
			'ids': {'JawOpen', 'Frown', 'Grin', 'Scrunch'}
			'reset': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.random(4, 16) / 100)
				@SetModifierWeight(2, math.random(60, 70) / 100)
				@SetModifierWeight(3, math.random(30, 40) / 100)
				@SetModifierWeight(4, math.random(70, 90) / 100)
		}

		{
			'name': 'kill_grin'
			'autostart': false
			'repeat': false
			'time': 8
			'ids': {'Smirk', 'Frown', 'Grin'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, .51)
				@SetModifierWeight(2, .38)
				@SetModifierWeight(3, .66)
		}

		{
			'name': 'sorry'
			'autostart': false
			'repeat': false
			'time': 8
			'ids': {'Frown', 'Stretch', 'Grin', 'Scrunch', 'sad_eyes'}
			'create': =>
				@SetModifierWeight(1, math.random(45, 75) / 100)
				@SetModifierWeight(2, math.random(45, 75) / 100)
				@SetModifierWeight(3, math.random(70, 100) / 100)
				@SetModifierWeight(4, math.random(7090, 100) / 100)
		}

		{
			'name': 'scrunch'
			'autostart': false
			'repeat': false
			'time': 6
			'ids': {'Scrunch'}
			'create': =>
				@SetModifierWeight(1, math.random(80, 100) / 100)
		}

		{
			'name': 'gulp'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Throat_Bulge', 'Frown', 'Grin'}
			'create': =>
				@SetModifierWeight(2, 1)
				@SetModifierWeight(3, math.random(35, 55) / 100)
			'func': (delta, timeOfAnim) =>
				if timeOfAnim > 0.5
					@SetModifierWeight(1, (1 - timeOfAnim) * 2)
				else
					@SetModifierWeight(1, timeOfAnim * 2)
		}

		{
			'name': 'blahblah'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'o3o', 'Mouth_O'}
			'create': =>
				@SetModifierWeight(1, 1)
				@talkAnim = [math.random(50, 70) / 100 for i = 0, 1, 0.05]
			'func': (delta, timeOfAnim) =>
				cPos = math.floor(timeOfAnim * 20) + 1
				data = @talkAnim[cPos]
				return if not data
				@SetModifierWeight(2, data)
		}

		{
			'name': 'wink_left'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Frown', 'Stretch', 'Grin', 'Left_Blink'}
			'create': =>
				@SetModifierWeight(1, math.random(40, 60) / 100)
				@SetModifierWeight(2, math.random(30, 50) / 100)
				@SetModifierWeight(3, math.random(60, 100) / 100)
				@SetModifierWeight(4, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'wink_right'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Frown', 'Stretch', 'Grin', 'Right_Blink'}
			'create': =>
				@SetModifierWeight(1, math.random(40, 60) / 100)
				@SetModifierWeight(2, math.random(30, 50) / 100)
				@SetModifierWeight(3, math.random(60, 100) / 100)
				@SetModifierWeight(4, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'happy_eyes'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Happy_Eyes'}
			'create': =>
				@SetModifierWeight(1, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'happy_grin'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Happy_Eyes', 'Grin'}
			'create': =>
				@SetModifierWeight(1, 1)
				@SetModifierWeight(2, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'duck'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Duck'}
			'create': =>
				@SetModifierWeight(1, math.random(70, 90) / 100)
		}

		{
			'name': 'duck_insanity'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Duck'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.abs(math.sin(timeOfAnim * @GetTime() * 4)))
		}

		{
			'name': 'duck_quack'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Duck', 'JawOpen'}
			'create': =>
				@talkAnim = for i = 0, 1, 0.1
					rand = math.random(1, 100)
					rand > 50 and 1 or 0
				@SetModifierWeight(1, math.random(70, 90) / 100)
			'func': (delta, timeOfAnim) =>
				cPos = math.floor(timeOfAnim * 10) + 1
				data = @talkAnim[cPos]
				return if not data
				@SetModifierWeight(2, data)
		}
	}

	@SetupFlexesTables: =>
		for i, flex in pairs @FLEX_LIST
			flex.id = i - 1
			flex.targetName = "target#{flex.flex}"
		@FLEX_IDS = {flex.id, flex for _, flex in ipairs @FLEX_LIST}
		@FLEX_TABLE = {flex.flex, flex for _, flex in ipairs @FLEX_LIST}

	@SetupFlexesTables()

	@NEXT_HOOK_ID = 0
	@SequenceObject = FlexSequence

	new: (data) =>
		super(data)
		@states = [FlexState(@, flex, id, scale, speed, active) for _, {:flex, :id, :scale, :speed, :active} in ipairs @@FLEX_LIST]
		@statesTable = {state\GetFlexName(), state for _, state in ipairs @states}
		@statesTable[state\GetFlexName()\lower()] = state for _, state in ipairs @states
		@statesTable[state\GetFlexID()] = state for _, state in ipairs @states
		@RebuildIterableList()
		ponyData = data\GetData()
		flex\SetUseLerp(ponyData\GetUseFlexLerp()) for _, flex in ipairs @states
		flex\SetLerpModify(ponyData\GetFlexLerpMultiplier()) for _, flex in ipairs @states
		@Hook('PlayerStartVoice', @PlayerStartVoice)
		@Hook('PlayerEndVoice', @PlayerEndVoice)
		@ResetSequences()
		PPM2.DebugPrint('Created new flex controller for ', @GetEntity(), ' as part of ', data, '; internal ID is ', @fid)

	IsValid: => @isValid

	GetFlexState: (name = '') => @statesTable[name]
	RebuildIterableList: =>
		return false if not @isValid
		@statesIterable = [state for _, state in ipairs @states when state\GetIsActive()]
	DataChanges: (state) =>
		return if not @isValid
		flexState\DataChanges(state) for _, flexState in ipairs @states
		if state\GetKey() == 'UseFlexLerp'
			flex\SetUseLerp(state\GetValue()) for _, flex in ipairs @states
		if state\GetKey() == 'FlexLerpMultiplier'
			flex\SetLerpModify(state\GetValue()) for _, flex in ipairs @states

	GetEntity: => @controller\GetEntity()
	GetData: => @controller
	GetController: => @controller

	PlayerStartVoice: (ply = NULL) =>
		return if ply ~= @GetEntity()
		@StartSequence('talk_endless')
	PlayerEndVoice: (ply = NULL) =>
		return if ply ~= @GetEntity()
		@EndSequence('talk_endless')

	ResetSequences: =>
		super()
		state\Reset(false) for _, state in ipairs @statesIterable

	Think: (ent = @GetEntity()) =>
		return if DISABLE_FLEXES\GetBool()
		delta = super(ent)
		return if not delta
		state\Think(ent, delta) for _, state in ipairs @statesIterable
		return delta

do
	ppm2_disable_flexes = (cvar, oldval, newval) ->
		for _, ply in ipairs player.GetAll()
			data = ply\GetPonyData()
			continue if not data
			renderer = data\GetRenderController()
			continue if not renderer
			flex = renderer\GetFlexController()
			continue if not flex
			flex\ResetSequences()
	cvars.AddChangeCallback 'ppm2_disable_flexes', ppm2_disable_flexes, 'ppm2_disable_flexes'

PPM2.PonyFlexController = PonyFlexController
PPM2.GetFlexController = (model = 'models/ppm/player_default_base_new.mdl') -> PonyFlexController.AVALIABLE_CONTROLLERS[model]

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

--
-- 0    eyes_updown
-- 1    eyes_rightleft
-- 2    JawOpen
-- 3    JawClose
-- 4    Smirk
-- 5    Frown
-- 6    Stretch
-- 7    Pucker
-- 8    Grin
-- 9    CatFace
-- 10   Mouth_O
-- 11   Mouth_O2
-- 12   Mouth_Full
-- 13   Tongue_Out
-- 14   Tongue_Up
-- 15   Tongue_Down
-- 16   NoEyelashes
-- 17   Eyes_Blink
-- 18   Left_Blink
-- 19   Right_Blink
-- 20   Scrunch
-- 21   FatButt
-- 22   Stomach_Out
-- 23   Stomach_In
-- 24   Throat_Bulge
-- 25   Male
-- 26   Hoof_Fluffers
-- 27   o3o
-- 28   Ear_Fluffers
-- 29   Fangs
-- 30   Claw_Teeth
-- 31   Fang_Test
-- 32   angry_eyes
-- 33   sad_eyes
-- 34   Eyes_Blink_Lower
-- 35   Male_2
-- 36   Buff_Body
-- 37   Manliest_Chin
-- 38   Lowerlid_Raise
-- 39   Happy_Eyes
-- 40   Duck

BLINK_CURVE = {0, 0.2, 0.45, 0.65, 0.9, 1, 1, 0.75, 0.55, 0.25, 0.15, 0}
BREATH_CURVE = {0, 0, 0, 0.1, 0.3, 0.55, 0.85, 1, 1, 0.85, 0.65, 0.4, 0.2, 0, 0, 0}

DISABLE_FLEXES = CreateConVar('ppm2_disable_flexes', '0', {FCVAR_ARCHIVE}, 'Disable pony flexes controllers. Saves some FPS.')

class FlexState extends PPM2.ModifierBase
	@SetupModifiers: =>
		@RegisterModifier('Speed', 0)
		@RegisterModifier('Scale', 0)
		@RegisterModifier('Weight', 0)

	new: (controller, flexName = '', flexID = 0, scale = 1, speed = 1, active = true, min = 0, max = 1, useModifiers = true) =>
		super()
		@controller = controller
		@name = flexName
		@flexName = flexName
		@flexID = flexID
		@id = flexID
		@scale = scale
		@speed = speed
		@originalscale = scale
		@originalspeed = speed
		@min = min
		@max = max
		@current = -1
		@target = 0
		@speedModify = 1
		@scaleModify = 1
		@modifiers = {}
		@useModifiers = useModifiers
		@active = active
		@useLerp = true
		@lerpMultiplier = 1
		@activeID = "DisableFlex#{@flexName}"

	__tostring: => "[#{@@__name}:#{@flexName}[#{@flexID}]|#{@GetData()}]"

	GetFlexID: => @flexID
	GetFlexName: => @flexName
	SetUseLerp: (val = true) => @useLerp = val
	GetUseLerp: => @useLerp
	UseLerp: => @useLerp
	SetLerpModify: (val = 1) => @lerpMultiplier = val
	GetLerpModify: => @lerpMultiplier
	LerpModify: => @lerpMultiplier

	GetEntity: => @controller\GetEntity()
	GetData: => @controller
	GetController: => @controller
	GetValue: => @current
	GetRealValue: => @target
	SetValue: (val = @target) =>
		@current = math.Clamp(val, @min, @max) * @scale * @scaleModify
		@target = @target
	SetRealValue: (val = @target) => @target = math.Clamp(val, @min, @max) * @scale * @scaleModify

	GetScale: => @scale
	GetSpeed: => @speed
	GetScaleModify: => @scaleModify
	GetSpeedModify: => @speedModify
	GetOriginalScale: => @originalscale
	GetOriginalSpeed: => @originalspeed

	SetScale: (val = @scale) => @scale = val
	GetSpeed: (val = @speed) => @speed = val
	SetScaleModify: (val = @scaleModify) => @scaleModify = val
	GetSpeedModify: (val = @speedModify) => @speedModify = val

	GetIsActive: => @active
	SetIsActive: (val = true) => @active = val

	AddValue: (val = 0) => @SetValue(@current + val)
	AddRealValue: (val = 0) => @SetRealValue(@target + val)
	Think: (ent = @GetEntity(), delta = 0) =>
		return if not @active

		if @useModifiers
			@current = 0
			@scale = @originalscale * @scaleModify
			@speed = @originalspeed * @speedModify

			if @useLerp
				for i = 1, #@WeightModifiers
					@modifiers[i] = Lerp(delta * 15 * @speed * @speedModify * @lerpMultiplier, @modifiers[i] or 0, @WeightModifiers[i])
					@current += @modifiers[i]
			else
				@current += @WeightModifiers[i] for i = 1, #@WeightModifiers

			@scale += modif for _, modif in ipairs @ScaleModifiers
			@speed += modif for _, modif in ipairs @SpeedModifiers
			@current = math.Clamp(@current, @min, @max) * @scale

		ent\SetFlexWeight(@flexID, @current)
	DataChanges: (state) =>
		return if state\GetKey() ~= @activeID
		@SetIsActive(not state\GetValue())
		@GetController()\RebuildIterableList()
		@Reset()
	Reset: (resetVars = true) =>
		for name, id in pairs @modifiersNames
			@ResetModifiers(name)
		if resetVars
			@scaleModify = 1
			@speedModify = 1
		@scale = @originalscale * @scaleModify
		@speed = @originalspeed * @speedModify
		@target = 0
		@current = 0
		@GetEntity()\SetFlexWeight(@flexID, 0) if IsValid(@GetEntity())

PPM2.FlexState = FlexState

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

		{
			'ids': @flexIDsIterable
			'numid': @numid
		} = data

		@flexIDS = {}
		@flexStates = {}
		i = 1
		for _, id in ipairs data.ids
			state = controller\GetFlexState(id)
			num = state\GetModifierID(@name)
			@["flex_#{id}"] = num
			@flexIDS[id] = num
			@flexStates[id] = state
			@flexStates[i] = state
			@flexIDS[i] = num
			i += 1

		@controller = controller
		@Launch()

	GetController: => @controller
	GetModifierID: (id = '') => @flexIDS[id]
	GetFlexState: (id = '') => @flexStates[id]

	Think: (delta = 0) =>
		return false if not IsValid(@GetEntity())
		super(delta)

	Stop: =>
		super()
		return unless @parent
		@parent\GetFlexState(id)\ResetModifiers(@name) for _, id in ipairs @flexIDsIterable

	SetModifierWeight: (id = '', val = 0) => @GetFlexState(id)\SetModifierWeight(@GetModifierID(id), val)
	SetModifierSpeed: (id = '', val = 0) => @GetFlexState(id)\SetModifierSpeed(@GetModifierID(id), val)
	SetUseLerp: (id = '', status = true) => @GetFlexState(id)\SetUseLerp(status)
	GetUseLerp: (id = '') => @GetFlexState(id)\GetUseLerp()

PPM2.FlexSequence = FlexSequence

class PonyFlexController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

	@FLEX_LIST = {
		{flex: 'eyes_updown',       scale: 1, speed: 1, active: false}
		{flex: 'eyes_rightleft',    scale: 1, speed: 1, active: false}
		{flex: 'JawOpen',           scale: 1, speed: 1, active: true}
		{flex: 'JawClose',          scale: 1, speed: 1, active: true}
		{flex: 'Smirk',             scale: 1, speed: 1, active: true}
		{flex: 'Frown',             scale: 1, speed: 1, active: true}
		{flex: 'Stretch',           scale: 1, speed: 1, active: false}
		{flex: 'Pucker',            scale: 1, speed: 1, active: false}
		{flex: 'Grin',              scale: 1, speed: 1, active: true}
		{flex: 'CatFace',           scale: 1, speed: 1, active: true}
		{flex: 'Mouth_O',           scale: 1, speed: 1, active: true}
		{flex: 'Mouth_O2',          scale: 1, speed: 1, active: true}
		{flex: 'Mouth_Full',        scale: 1, speed: 1, active: false}
		{flex: 'Tongue_Out',        scale: 1, speed: 1, active: true}
		{flex: 'Tongue_Up',         scale: 1, speed: 1, active: true}
		{flex: 'Tongue_Down',       scale: 1, speed: 1, active: true}
		{flex: 'NoEyelashes',       scale: 1, speed: 1, active: false}
		{flex: 'Eyes_Blink',        scale: 1, speed: 1, active: false}
		{flex: 'Left_Blink',        scale: 1, speed: 1, active: true}
		{flex: 'Right_Blink',       scale: 1, speed: 1, active: true}
		{flex: 'Scrunch',           scale: 1, speed: 1, active: true}
		{flex: 'FatButt',           scale: 1, speed: 1, active: false}
		{flex: 'Stomach_Out',       scale: 1, speed: 1, active: true}
		{flex: 'Stomach_In',        scale: 1, speed: 1, active: true}
		{flex: 'Throat_Bulge',      scale: 1, speed: 1, active: true}
		{flex: 'Male',              scale: 1, speed: 1, active: false}
		{flex: 'Hoof_Fluffers',     scale: 1, speed: 1, active: false}
		{flex: 'o3o',               scale: 1, speed: 1, active: true}
		{flex: 'Ear_Fluffers',      scale: 1, speed: 1, active: false}
		{flex: 'Fangs',             scale: 1, speed: 1, active: false}
		{flex: 'Claw_Teeth',        scale: 1, speed: 1, active: false}
		{flex: 'Fang_Test',         scale: 1, speed: 1, active: false}
		{flex: 'angry_eyes',        scale: 1, speed: 1, active: true}
		{flex: 'sad_eyes',          scale: 1, speed: 1, active: true}
		{flex: 'Eyes_Blink_Lower',  scale: 1, speed: 1, active: true}
		{flex: 'Male_2',            scale: 1, speed: 1, active: false}
		{flex: 'Buff_Body',         scale: 1, speed: 1, active: false}
		{flex: 'Manliest_Chin',     scale: 1, speed: 1, active: false}
		{flex: 'Lowerlid_Raise',    scale: 1, speed: 1, active: false}
		{flex: 'Happy_Eyes',        scale: 1, speed: 1, active: true}
		{flex: 'Duck',              scale: 1, speed: 1, active: true}

	}

	@SEQUENCES = {
		{
			'name': 'anger'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Frown', 'Grin', 'angry_eyes', 'Scrunch'}
			'reset': =>
				@SetTime(math.random(15, 45) / 10)
				@lastStrengthUpdate = @lastStrengthUpdate or 0
				if @lastStrengthUpdate < CurTimeL()
					@lastStrengthUpdate = CurTimeL() + 2
					@frownStrength = math.random(40, 100) / 100
					@grinStrength = math.random(15, 40) / 100
					@angryStrength = math.random(30, 80) / 100
					@scrunchStrength = math.random(50, 100) / 100
					@SetModifierWeight(1, @frownStrength)
					@SetModifierWeight(2, @grinStrength)
					@SetModifierWeight(3, @angryStrength)
					@SetModifierWeight(4, @scrunchStrength)
		}

		{
			'name': 'sad'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Frown', 'Grin', 'sad_eyes'}
			'reset': =>
				@SetTime(math.random(15, 45) / 10)
				@lastStrengthUpdate = @lastStrengthUpdate or 0
				if @lastStrengthUpdate < CurTimeL()
					@lastStrengthUpdate = CurTimeL() + 2
					@frownStrength = math.random(40, 100) / 100
					@grinStrength = math.random(15, 40) / 100
					@angryStrength = math.random(30, 80) / 100
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, @frownStrength)
				@SetModifierWeight(2, @grinStrength)
				@SetModifierWeight(3, @angryStrength)
		}

		{
			'name': 'ugh'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'sad_eyes', 'Eyes_Blink_Lower'}
			'reset': =>
				@SetModifierWeight(1, math.Rand(0.27, 0.34))
				@SetModifierWeight(2, math.Rand(0.3, 0.35))
				@PauseSequence('eyes_blink')
				@PauseSequence('eyes_idle')
		}

		{
			'name': 'suggestive_eyes'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'sad_eyes', 'Eyes_Blink_Lower'}
			'reset': =>
				@SetModifierWeight(1, 0.28)
				@SetModifierWeight(2, 0.4)
				@PauseSequence('eyes_blink')
				@PauseSequence('eyes_idle')
		}

		{
			'name': 'lips_lick'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out', 'Tongue_Up'}
			'reset': =>
				@SetModifierWeight(1, 0.9)
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(2, 0.75 + math.sin(CurTimeL() * 7) * 0.25)
		}

		{
			'name': 'tongue_pullout'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 0.15 + math.sin(CurTimeL() * 10) * 0.1)
		}

		{
			'name': 'tongue_pullout_twitch'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 0.5 + math.sin(CurTimeL() * 4) * 0.5)
		}

		{
			'name': 'tongue_pullout_twitch_fast'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 0.5 + math.sin(CurTimeL() * 8) * 0.5)
		}

		{
			'name': 'suggestive_open'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Pucker', 'JawOpen', 'Scrunch'}
			'reset': =>
				@SetModifierWeight(1, math.Rand(0.28, 0.34))
				@SetModifierWeight(2, math.Rand(0.35, 0.40))
				@SetModifierWeight(3, math.Rand(0.45, 0.50))
		}

		{
			'name': 'suggestive_open_anim'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Pucker', 'JawOpen', 'Scrunch'}
			'reset': =>
				@SetModifierWeight(1, math.Rand(0.28, 0.34))
				@SetModifierWeight(3, math.Rand(0.45, 0.50))
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(2, 0.2 + math.sin(CurTimeL() * 16) * 0.07)
		}

		{
			'name': 'face_smirk'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Smirk', 'Frown'}
			'reset': =>
				@SetModifierWeight(1, 0.78)
				@SetModifierWeight(2, 0.61)
		}

		{
			'name': 'eyes_idle'
			'autostart': true
			'repeat': true
			'time': 5
			'ids': {'Left_Blink', 'Right_Blink'}
			'func': (delta, timeOfAnim) =>
				return false if @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				value = math.abs(math.sin(CurTimeL() * .5) * .15)
				@SetModifierWeight(1, value)
				@SetModifierWeight(2, value)
		}

		{
			'name': 'eyes_close'
			'autostart': true
			'repeat': true
			'time': 5
			'ids': {'Left_Blink', 'Right_Blink', 'Frown'}
			'func': (delta, timeOfAnim) =>
				return if not @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				@SetModifierWeight(1, 1)
				@SetModifierWeight(2, 1)
				@SetModifierWeight(3, 0.5)
		}

		{
			'name': 'body_idle'
			'autostart': true
			'repeat': true
			'time': 2
			'ids': {'Stomach_Out'}
			'func': (delta, timeOfAnim) =>
				return false if @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				return if timeOfAnim < 0 or timeOfAnim > 1
				@SetModifierWeight(1, math.tbezier(timeOfAnim, BREATH_CURVE) * 0.35)
		}

		{
			'name': 'health_idle'
			'autostart': true
			'repeat': true
			'time': 5
			'ids': {'Frown', 'Left_Blink', 'Right_Blink', 'Scrunch', 'Mouth_O', 'JawOpen', 'Grin'}
			'func': (delta, timeOfAnim) =>
				return false if not @GetEntity()\IsPlayer() and not @GetEntity()\IsNPC() and @GetEntity().Type ~= 'nextbot'
				frown = @GetModifierID(1)
				frownState = @GetFlexState(1)
				left, right = @GetModifierID(2), @GetModifierID(3)
				leftState, rightState = @GetFlexState(2), @GetFlexState(3)
				Mouth_O, Mouth_OState = @GetModifierID(4), @GetFlexState(4)
				Scrunch = @GetModifierID(4)
				ScrunchState = @GetFlexState(4)

				hp, mhp = @GetEntity()\Health(), @GetEntity()\GetMaxHealth()
				mhp = 1 if mhp == 0
				div = hp / mhp
				strength = math.Clamp(1.5 - div * 1.5, 0, 1)
				frownState\SetModifierWeight(frown, strength)
				ScrunchState\SetModifierWeight(Scrunch, strength * .5)
				leftState\SetModifierWeight(left, strength * .1)
				rightState\SetModifierWeight(right, strength * .1)
				Mouth_OState\SetModifierWeight(Mouth_O, strength * .8)

				JawOpen = @GetModifierID(6)
				JawOpenState = @GetFlexState(6)

				if strength > .75
					JawOpenState\SetModifierWeight(JawOpen, strength * .2 + math.sin(CurTimeL() * strength * 3) * .1)
				else
					JawOpenState\SetModifierWeight(JawOpen, 0)

				if div >= 2
					@SetModifierWeight(7, .5)
				else
					@SetModifierWeight(7, 0)
		}

		{
			'name': 'greeny'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Grin'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				strength = .5 + math.sin(CurTimeL() * 2) * .25
				GrinState\SetModifierWeight(Grin, strength)
		}

		{
			'name': 'big_grin'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Grin'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, 1)
		}

		{
			'name': 'o3o'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'o3o'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, 1)
		}

		{
			'name': 'owo_alternative'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'o3o', 'JawOpen'}
			'reset': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.Rand(0.8, 1))
				@SetModifierWeight(2, math.Rand(0.05, 0.1))
		}

		{
			'name': 'xd'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Grin', 'Left_Blink', 'Right_Blink', 'JawOpen'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, .6)

				Left_Blink = @GetModifierID(2)
				Left_BlinkState = @GetFlexState(2)
				Left_BlinkState\SetModifierWeight(Left_Blink, .9)

				Right_Blink = @GetModifierID(3)
				Right_BlinkState = @GetFlexState(3)
				Right_BlinkState\SetModifierWeight(Right_Blink, .9)

				JawOpen = @GetModifierID(4)
				JawOpenState = @GetFlexState(4)
				JawOpenState\SetModifierScale(JawOpen, 2)
				JawOpenState\SetModifierWeight(JawOpen, (timeOfAnim % .1) * 2)
		}

		{
			'name': 'tongue'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'JawOpen', 'Tongue_Out'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, .1)
				@SetModifierWeight(2, 1)
		}

		{
			'name': 'angry_tongue'
			'autostart': false
			'repeat': false
			'time': 6
			'ids': {'Frown', 'Grin', 'angry_eyes', 'Scrunch', 'JawOpen', 'Tongue_Out'}
			'reset': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.random(40, 100) / 100)
				@SetModifierWeight(2, math.random(15, 40) / 100)
				@SetModifierWeight(3, math.random(30, 80) / 100)
				@SetModifierWeight(4, math.random(50, 100) / 100)
				@SetModifierWeight(5, math.random(10, 15) / 100)
				@SetModifierWeight(6, math.random(80, 100) / 100)
		}

		{
			'name': 'pffff'
			'autostart': false
			'repeat': false
			'time': 6
			'ids': {'Frown', 'Grin', 'angry_eyes', 'Scrunch', 'JawOpen', 'Tongue_Out', 'Tongue_Down', 'Tongue_Up'}
			'reset': =>
				@SetModifierWeight(1, math.random(40, 100) / 100)
				@SetModifierWeight(2, math.random(15, 40) / 100)
				@SetModifierWeight(3, math.random(30, 80) / 100)
				@SetModifierWeight(4, math.random(50, 100) / 100)
				@SetModifierWeight(5, math.random(10, 15) / 100)
				@SetModifierWeight(6, math.random(80, 100) / 100)
			'func': (delta, timeOfAnim) =>
				val = math.sin(CurTimeL() * 8) * .6
				if val > 0
					@SetModifierWeight(7, val)
					@SetModifierWeight(8, 0)
				else
					@SetModifierWeight(7, 0)
					@SetModifierWeight(8, -val)
		}

		{
			'name': 'cat'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'CatFace'}
			'func': (delta, timeOfAnim) =>
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, 1)
		}

		{
			'name': 'ooo'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Mouth_O2', 'Mouth_O'}
			'func': (delta, timeOfAnim) =>
				timeOfAnim *= 2
				Grin = @GetModifierID(1)
				GrinState = @GetFlexState(1)
				GrinState\SetModifierWeight(Grin, timeOfAnim)
				Grin = @GetModifierID(2)
				GrinState = @GetFlexState(2)
				GrinState\SetModifierWeight(Grin, timeOfAnim)
		}

		{
			'name': 'talk'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
			'create': =>
				@talkAnim = for i = 0, 1, 0.05
					rand = math.random(1, 100) / 100
					if rand <= .25
						{1 * rand, 0.4 * rand, 2 * rand, 0}
					elseif rand >= .25 and rand < .4
						rand *= .8
						{2 * rand, .6 * rand, 0, 1 * rand}
					elseif rand >= .4 and rand < .75
						rand *= .6
						{1 * rand, 0, 1 * rand, 2 * rand}
					elseif rand >= .75
						rand *= .4
						{1.5 * rand, 0, 1 * rand, 0}
				@SetModifierSpeed(1, 2)
				@SetModifierSpeed(2, 2)
				@SetModifierSpeed(3, 2)
				@SetModifierSpeed(4, 2)
			'func': (delta, timeOfAnim) =>
				JawOpen = @GetModifierID(1)
				JawOpenState = @GetFlexState(1)
				Tongue_OutOpen = @GetModifierID(2)
				Tongue_OutOpenState = @GetFlexState(2)
				Tongue_UpOpen = @GetModifierID(3)
				Tongue_UpOpenState = @GetFlexState(3)
				Tongue_DownOpen = @GetModifierID(4)
				Tongue_DownOpenState = @GetFlexState(4)
				cPos = math.floor(timeOfAnim * 20) + 1
				data = @talkAnim[cPos]
				return if not data
				{jaw, out, up, down} = data
				JawOpenState\SetModifierWeight(JawOpen, jaw)
				Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
				Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
				Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
		}

		{
			'name': 'talk_endless'
			'autostart': false
			'repeat': true
			'time': 4
			'ids': {'JawOpen', 'Tongue_Out', 'Tongue_Up', 'Tongue_Down'}
			'create': =>
				@talkAnim = for i = 0, 1, 0.05
					rand = math.random(1, 100) / 100
					if rand <= .25
						{1 * rand, 0.4 * rand, 2 * rand, 0}
					elseif rand >= .25 and rand < .4
						rand *= .8
						{2 * rand, .6 * rand, 0, 1 * rand}
					elseif rand >= .4 and rand < .75
						rand *= .6
						{1 * rand, 0, 1 * rand, 2 * rand}
					elseif rand >= .75
						rand *= .4
						{1.5 * rand, 0, 1 * rand, 0}
				@SetModifierSpeed(1, 2)
				@SetModifierSpeed(2, 2)
				@SetModifierSpeed(3, 2)
				@SetModifierSpeed(4, 2)
			'func': (delta, timeOfAnim) =>
				JawOpen = @GetModifierID(1)
				JawOpenState = @GetFlexState(1)
				Tongue_OutOpen = @GetModifierID(2)
				Tongue_OutOpenState = @GetFlexState(2)
				Tongue_UpOpen = @GetModifierID(3)
				Tongue_UpOpenState = @GetFlexState(3)
				Tongue_DownOpen = @GetModifierID(4)
				Tongue_DownOpenState = @GetFlexState(4)
				cPos = math.floor(timeOfAnim * 20) + 1
				data = @talkAnim[cPos]
				return if not data
				{jaw, out, up, down} = data
				volume = @GetEntity()\VoiceVolume() * 6
				jaw *= volume
				out *= volume
				up *= volume
				down *= volume
				JawOpenState\SetModifierWeight(JawOpen, jaw)
				Tongue_OutOpenState\SetModifierWeight(Tongue_OutOpen, out)
				Tongue_UpOpenState\SetModifierWeight(Tongue_UpOpen, up)
				Tongue_DownOpenState\SetModifierWeight(Tongue_DownOpen, down)
		}

		{
			'name': 'eyes_blink'
			'autostart': true
			'repeat': true
			'time': 7
			'ids': {'Eyes_Blink_Lower'}
			'create': =>
				@SetUseLerp(1, false)

			'reset': =>
				@nextBlink = math.random(300, 600) / 1000
				@nextBlinkLength = math.random(15, 30) / 800
				@min, @max = @nextBlink, @nextBlink + @nextBlinkLength

			'func': (delta, timeOfAnim) =>
				return false if @GetEntity()\GetNWBool('PPM2.IsDeathRagdoll')
				if @min > timeOfAnim or @max < timeOfAnim
					if @blinkHit
						@blinkHit = false
						@SetModifierWeight(1, 0)

					return

				len = (timeOfAnim - @min) / @nextBlinkLength
				blink = math.tbezier(len\clamp(0, 1), BLINK_CURVE)

				@SetModifierWeight(1, blink)
				-- print(len, blink)

				@blinkHit = true
		}

		{
			'name': 'hurt'
			'autostart': false
			'repeat': false
			'time': 4
			'ids': {'JawOpen', 'Frown', 'Grin', 'Scrunch'}
			'reset': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.random(4, 16) / 100)
				@SetModifierWeight(2, math.random(60, 70) / 100)
				@SetModifierWeight(3, math.random(30, 40) / 100)
				@SetModifierWeight(4, math.random(70, 90) / 100)
		}

		{
			'name': 'kill_grin'
			'autostart': false
			'repeat': false
			'time': 8
			'ids': {'Smirk', 'Frown', 'Grin'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, .51)
				@SetModifierWeight(2, .38)
				@SetModifierWeight(3, .66)
		}

		{
			'name': 'sorry'
			'autostart': false
			'repeat': false
			'time': 8
			'ids': {'Frown', 'Stretch', 'Grin', 'Scrunch', 'sad_eyes'}
			'create': =>
				@SetModifierWeight(1, math.random(45, 75) / 100)
				@SetModifierWeight(2, math.random(45, 75) / 100)
				@SetModifierWeight(3, math.random(70, 100) / 100)
				@SetModifierWeight(4, math.random(7090, 100) / 100)
		}

		{
			'name': 'scrunch'
			'autostart': false
			'repeat': false
			'time': 6
			'ids': {'Scrunch'}
			'create': =>
				@SetModifierWeight(1, math.random(80, 100) / 100)
		}

		{
			'name': 'gulp'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Throat_Bulge', 'Frown', 'Grin'}
			'create': =>
				@SetModifierWeight(2, 1)
				@SetModifierWeight(3, math.random(35, 55) / 100)
			'func': (delta, timeOfAnim) =>
				if timeOfAnim > 0.5
					@SetModifierWeight(1, (1 - timeOfAnim) * 2)
				else
					@SetModifierWeight(1, timeOfAnim * 2)
		}

		{
			'name': 'blahblah'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'o3o', 'Mouth_O'}
			'create': =>
				@SetModifierWeight(1, 1)
				@talkAnim = [math.random(50, 70) / 100 for i = 0, 1, 0.05]
			'func': (delta, timeOfAnim) =>
				cPos = math.floor(timeOfAnim * 20) + 1
				data = @talkAnim[cPos]
				return if not data
				@SetModifierWeight(2, data)
		}

		{
			'name': 'wink_left'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Frown', 'Stretch', 'Grin', 'Left_Blink'}
			'create': =>
				@SetModifierWeight(1, math.random(40, 60) / 100)
				@SetModifierWeight(2, math.random(30, 50) / 100)
				@SetModifierWeight(3, math.random(60, 100) / 100)
				@SetModifierWeight(4, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'wink_right'
			'autostart': false
			'repeat': false
			'time': 2
			'ids': {'Frown', 'Stretch', 'Grin', 'Right_Blink'}
			'create': =>
				@SetModifierWeight(1, math.random(40, 60) / 100)
				@SetModifierWeight(2, math.random(30, 50) / 100)
				@SetModifierWeight(3, math.random(60, 100) / 100)
				@SetModifierWeight(4, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'happy_eyes'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Happy_Eyes'}
			'create': =>
				@SetModifierWeight(1, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'happy_grin'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Happy_Eyes', 'Grin'}
			'create': =>
				@SetModifierWeight(1, 1)
				@SetModifierWeight(2, 1)
				@PauseSequence('eyes_blink')
		}

		{
			'name': 'duck'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Duck'}
			'create': =>
				@SetModifierWeight(1, math.random(70, 90) / 100)
		}

		{
			'name': 'duck_insanity'
			'autostart': false
			'repeat': false
			'time': 3
			'ids': {'Duck'}
			'func': (delta, timeOfAnim) =>
				@SetModifierWeight(1, math.abs(math.sin(timeOfAnim * @GetTime() * 4)))
		}

		{
			'name': 'duck_quack'
			'autostart': false
			'repeat': false
			'time': 5
			'ids': {'Duck', 'JawOpen'}
			'create': =>
				@talkAnim = for i = 0, 1, 0.1
					rand = math.random(1, 100)
					rand > 50 and 1 or 0
				@SetModifierWeight(1, math.random(70, 90) / 100)
			'func': (delta, timeOfAnim) =>
				cPos = math.floor(timeOfAnim * 10) + 1
				data = @talkAnim[cPos]
				return if not data
				@SetModifierWeight(2, data)
		}
	}

	@SetupFlexesTables: =>
		for i, flex in pairs @FLEX_LIST
			flex.id = i - 1
			flex.targetName = "target#{flex.flex}"
		@FLEX_IDS = {flex.id, flex for _, flex in ipairs @FLEX_LIST}
		@FLEX_TABLE = {flex.flex, flex for _, flex in ipairs @FLEX_LIST}

	@SetupFlexesTables()

	@NEXT_HOOK_ID = 0
	@SequenceObject = FlexSequence

	new: (data) =>
		super(data)
		@states = [FlexState(@, flex, id, scale, speed, active) for _, {:flex, :id, :scale, :speed, :active} in ipairs @@FLEX_LIST]
		@statesTable = {state\GetFlexName(), state for _, state in ipairs @states}
		@statesTable[state\GetFlexName()\lower()] = state for _, state in ipairs @states
		@statesTable[state\GetFlexID()] = state for _, state in ipairs @states
		@RebuildIterableList()
		ponyData = data\GetData()
		flex\SetUseLerp(ponyData\GetUseFlexLerp()) for _, flex in ipairs @states
		flex\SetLerpModify(ponyData\GetFlexLerpMultiplier()) for _, flex in ipairs @states
		@Hook('PlayerStartVoice', @PlayerStartVoice)
		@Hook('PlayerEndVoice', @PlayerEndVoice)
		@ResetSequences()
		PPM2.DebugPrint('Created new flex controller for ', @GetEntity(), ' as part of ', data, '; internal ID is ', @fid)

	IsValid: => @isValid

	GetFlexState: (name = '') => @statesTable[name]
	RebuildIterableList: =>
		return false if not @isValid
		@statesIterable = [state for _, state in ipairs @states when state\GetIsActive()]
	DataChanges: (state) =>
		return if not @isValid
		flexState\DataChanges(state) for _, flexState in ipairs @states
		if state\GetKey() == 'UseFlexLerp'
			flex\SetUseLerp(state\GetValue()) for _, flex in ipairs @states
		if state\GetKey() == 'FlexLerpMultiplier'
			flex\SetLerpModify(state\GetValue()) for _, flex in ipairs @states

	GetEntity: => @controller\GetEntity()
	GetData: => @controller
	GetController: => @controller

	PlayerStartVoice: (ply = NULL) =>
		return if ply ~= @GetEntity()
		@StartSequence('talk_endless')
	PlayerEndVoice: (ply = NULL) =>
		return if ply ~= @GetEntity()
		@EndSequence('talk_endless')

	ResetSequences: =>
		super()
		state\Reset(false) for _, state in ipairs @statesIterable

	Think: (ent = @GetEntity()) =>
		return if DISABLE_FLEXES\GetBool()
		delta = super(ent)
		return if not delta
		state\Think(ent, delta) for _, state in ipairs @statesIterable
		return delta

do
	ppm2_disable_flexes = (cvar, oldval, newval) ->
		for _, ply in ipairs player.GetAll()
			data = ply\GetPonyData()
			continue if not data
			renderer = data\GetRenderController()
			continue if not renderer
			flex = renderer\GetFlexController()
			continue if not flex
			flex\ResetSequences()
	cvars.AddChangeCallback 'ppm2_disable_flexes', ppm2_disable_flexes, 'ppm2_disable_flexes'

PPM2.PonyFlexController = PonyFlexController
PPM2.GetFlexController = (model = 'models/ppm/player_default_base_new.mdl') -> PonyFlexController.AVALIABLE_CONTROLLERS[model]