Return if no table is avaliable

Return if no table is avaliable

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

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

wUInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteUInt(arg, size)

wInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteInt(arg, size)

rUInt = (size = 8, min = 0, max = 255) ->
	return -> math.Clamp(net.ReadUInt(size), min, max)

rInt = (size = 8, min = -128, max = 127) ->
	return -> math.Clamp(net.ReadInt(size), min, max)

rFloat = (min = 0, max = 255) ->
	return -> math.Clamp(net.ReadFloat(), min, max)

wFloat = net.WriteFloat
rSEnt = net.ReadStrongEntity
wSEnt = net.WriteStrongEntity
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

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

	@Setup()
	@NetworkVar('Entity',           rSEnt, wSEnt, StrongEntity(-1), ((newValue) => IsValid(@GetOwner()) and StrongEntity(@GetOwner()\EntIndex()) or newValue))
	@NetworkVar('UpperManeModel',   rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('LowerManeModel',   rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('TailModel',        rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('SocksModel',       rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('NewSocksModel',    rSEnt, wSEnt, StrongEntity(-1), nil, false)

	@NetworkVar('Fly',                  rBool,   wBool,                 false)
	@NetworkVar('DisableTask',          rBool,   wBool,                 false)
	@NetworkVar('UseFlexLerp',          rBool,   wBool,                  true)
	@NetworkVar('FlexLerpMultiplier',   rFloat(0, 10),  wFloat,             1)

	@SetupModifiers: =>
		for key, value in pairs PPM2.PonyDataRegistry
			if value.modifiers
				@RegisterModifier(value.getFunc, 0, 0)
				@SetModifierMinMaxFinal(value.getFunc, value.min, value.max) if value.min or value.max
				@SetupLerpTables(value.getFunc)
				strName = '_NW_' .. value.getFunc
				funcLerp = 'Calculate' .. value.getFunc
				@__base['Get' .. value.getFunc] = => @[funcLerp](@, @[strName])

	for key, value in pairs PPM2.PonyDataRegistry
		@NetworkVar(value.getFunc, value.read, value.write, value.default)

	new: (netID, ent) =>
		@recomputeTextures = true
		@isValid = true
		super(netID)
		if ent
			@modelCached = ent\GetModel()
			@SetEntity(ent)
			@SetupEntity(ent)

	IsValid: => @isValid
	GetModel: => @modelCached
	EntIndex: => @entID

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

	SetupEntity: (ent) =>
		if ent.__PPM2_PonyData
			return if ent.__PPM2_PonyData\GetOwner() and IsValid(ent.__PPM2_PonyData\GetOwner()) and StrongEntity(ent.__PPM2_PonyData\GetOwner()) ~= StrongEntity(@GetOwner())
			ent.__PPM2_PonyData\Remove() if ent.__PPM2_PonyData.Remove and ent.__PPM2_PonyData ~= @
		ent.__PPM2_PonyData = @
		@ent = ent
		@entTable = @ent\GetTable()
		return unless IsValid(ent)
		@modelCached = ent\GetModel()
		@ent = ent
		@flightController = PPM2.PonyflyController(@)
		@entID = ent\EntIndex()
		@lastLerpThink = RealTime()
		@ModelChanges(@modelCached, @modelCached)
		@Reset()
		timer.Simple(0, -> @GetRenderController()\CompileTextures() if @GetRenderController()) if CLIENT
		PPM2.DebugPrint('Ponydata ', @, ' was updated to use for ', @ent)
		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

	ModelChanges: (old = @ent\GetModel(), new = old) =>
		@modelCached = new
		@SetFly(false) if SERVER
		timer.Simple 0.5, ->
			return unless IsValid(@ent)
			@Reset()

	GenericDataChange: (state) =>
		hook.Run 'PPM2_PonyDataChanges', @ent, @, state
		if state\GetKey() == 'Entity' and IsValid(@GetEntity())
			@SetupEntity(@GetEntity())

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

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

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

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

	ResetScale: =>
		if scale = @GetSizeController()
			scale\ResetScale()

	ModifyScale: =>
		if scale = @GetSizeController()
			scale\ModifyScale()

	Reset: =>
		if scale = @GetSizeController()
			scale\Reset()
		if CLIENT
			@GetWeightController()\Reset() if @GetWeightController().Reset
			@GetRenderController()\Reset() if @GetRenderController().Reset
			@GetBodygroupController()\Reset() if @GetBodygroupController().Reset

	PlayerRespawn: =>
		return if not IsValid(@ent)
		@entTable.__cachedIsPony = @ent\IsPony()
		if not @entTable.__cachedIsPony
			return if @alreadyCalledRespawn
			@alreadyCalledRespawn = true
			@alreadyCalledDeath = true
		else
			@alreadyCalledRespawn = false
			@alreadyCalledDeath = false
		@ApplyBodygroups(CLIENT, true)
		@SetFly(false) if SERVER

		if scale = @GetSizeController()
			scale\PlayerRespawn()

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

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

		if scale = @GetSizeController()
			scale\PlayerDeath()

		if CLIENT
			@DoRagdollMerge()
			@GetRenderController()\PlayerDeath() if @GetRenderController()

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

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

	SlowUpdate: =>
		@GetBodygroupController()\SlowUpdate() if @GetBodygroupController()
		@GetWeightController()\SlowUpdate() if @GetWeightController()
		if scale = @GetSizeController()
			scale\SlowUpdate()

	Think: =>
		time = RealTime()
		delta = time - @lastLerpThink
		@lastLerpThink = time
		if @isValid and IsValid(@ent)
			for change in *@TriggerLerpAll(delta * 5)
				state = PPM2.NetworkChangeState('_NW_' .. change[1], change[1], change[2] + @['_NW_' .. change[1]], @)
				state\SetCantApply(true)
				@GenericDataChange(state)

	GetFlightController: => @flightController

	GetRenderController: =>
		return if SERVER
		return @renderController if not @isValid
		if not @renderController or @modelCached ~= @modelRender
			@modelRender = @modelCached
			cls = PPM2.GetRenderController(@modelCached)
			if @renderController and cls == @renderController.__class
				@renderController.ent = @ent
				PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
				return @renderController
			@renderController\Remove() if @renderController
			@renderController = cls(@)
		@renderController.ent = @ent
		return @renderController

	GetWeightController: =>
		return if SERVER
		return @weightController if not @isValid
		if not @weightController or @modelCached ~= @modelWeight
			@modelCached = @modelCached or @ent\GetModel()
			@modelWeight = @modelCached
			cls = PPM2.GetPonyWeightController(@modelCached)
			if @weightController and cls == @weightController.__class
				@weightController.ent = @ent
				PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
				return @weightController
			@weightController\Remove() if @weightController
			@weightController = cls(@)
		@weightController.ent = @ent
		return @weightController

	GetSizeController: =>
		return @scaleController if not @isValid
		if not @scaleController or @modelCached ~= @modelScale
			@modelCached = @modelCached or @ent\GetModel()
			@modelScale = @modelCached
			cls = PPM2.GetSizeController(@modelCached)
			if @scaleController and cls == @scaleController.__class
				@scaleController.ent = @ent
				PPM2.DebugPrint('Skipping size controller recreation for ', @ent, ' as part of ', @)
				return @scaleController
			@scaleController\Remove() if @scaleController
			@scaleController = cls(@)
		@scaleController.ent = @ent
		return @scaleController
	GetScaleController: => @GetSizeController()

	GetBodygroupController: =>
		return @bodygroups if not @isValid
		if not @bodygroups or @modelBodygroups ~= @modelCached
			@modelCached = @modelCached or @ent\GetModel()
			@modelBodygroups = @modelCached
			cls = PPM2.GetBodygroupController(@modelCached)
			if @bodygroups and cls == @bodygroups.__class
				@bodygroups.ent = @ent
				PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
				return @bodygroups
			@bodygroups\Remove() if @bodygroups
			@bodygroups = cls(@)
		@bodygroups.ent = @ent
		return @bodygroups

	Remove: (byClient = false) =>
		@@NW_Objects[@netID] = nil if @NETWORKED
		@isValid = false
		@ent = @GetEntity() if not IsValid(@ent)
		@entTable.__PPM2_PonyData = nil if IsValid(@ent) and @ent.__PPM2_PonyData == @
		if CLIENT
			@GetWeightController()\Remove() if @GetWeightController()
			@GetRenderController()\Remove() if @GetRenderController()
			if IsValid(@ent) and @ent.__ppm2_task_hit
				@entTable.__ppm2_task_hit = false
				@ent\SetNoDraw(false)
		@GetBodygroupController()\Remove() if @GetBodygroupController()
		@GetSizeController()\Remove() if @GetSizeController()
		@flightController\Switch(false) if @flightController
		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

	__tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

PPM2.NetworkedPonyData = NetworkedPonyData

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

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

	hook.Add 'StrongEntityLinkUpdates', 'PPM2.NetworkedObjectCheck', =>
		if @__PPM2_PonyData
			@__PPM2_PonyData\SetEntity(@GetEntity())
			@__PPM2_PonyData\SetupEntity(@)
else
	hook.Add 'PlayerJoinTeam', 'PPM2.TeamWaypoint', (ply, new) ->
		ply.__ppm2_modified_jump = false
	hook.Add 'OnPlayerChangedTeam', 'PPM2.TeamWaypoint', (ply, old, new) ->
		ply.__ppm2_modified_jump = false

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

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

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

wUInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteUInt(arg, size)

wInt = (def = 0, size = 8) ->
	return (arg = def) -> net.WriteInt(arg, size)

rUInt = (size = 8, min = 0, max = 255) ->
	return -> math.Clamp(net.ReadUInt(size), min, max)

rInt = (size = 8, min = -128, max = 127) ->
	return -> math.Clamp(net.ReadInt(size), min, max)

rFloat = (min = 0, max = 255) ->
	return -> math.Clamp(net.ReadFloat(), min, max)

wFloat = net.WriteFloat
rSEnt = net.ReadStrongEntity
wSEnt = net.WriteStrongEntity
rBool = net.ReadBool
wBool = net.WriteBool
rColor = net.ReadColor
wColor = net.WriteColor
rString = net.ReadString
wString = net.WriteString

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

	@Setup()
	@NetworkVar('Entity',           rSEnt, wSEnt, StrongEntity(-1), ((newValue) => IsValid(@GetOwner()) and StrongEntity(@GetOwner()\EntIndex()) or newValue))
	@NetworkVar('UpperManeModel',   rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('LowerManeModel',   rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('TailModel',        rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('SocksModel',       rSEnt, wSEnt, StrongEntity(-1), nil, false)
	@NetworkVar('NewSocksModel',    rSEnt, wSEnt, StrongEntity(-1), nil, false)

	@NetworkVar('Fly',                  rBool,   wBool,                 false)
	@NetworkVar('DisableTask',          rBool,   wBool,                 false)
	@NetworkVar('UseFlexLerp',          rBool,   wBool,                  true)
	@NetworkVar('FlexLerpMultiplier',   rFloat(0, 10),  wFloat,             1)

	@SetupModifiers: =>
		for key, value in pairs PPM2.PonyDataRegistry
			if value.modifiers
				@RegisterModifier(value.getFunc, 0, 0)
				@SetModifierMinMaxFinal(value.getFunc, value.min, value.max) if value.min or value.max
				@SetupLerpTables(value.getFunc)
				strName = '_NW_' .. value.getFunc
				funcLerp = 'Calculate' .. value.getFunc
				@__base['Get' .. value.getFunc] = => @[funcLerp](@, @[strName])

	for key, value in pairs PPM2.PonyDataRegistry
		@NetworkVar(value.getFunc, value.read, value.write, value.default)

	new: (netID, ent) =>
		@recomputeTextures = true
		@isValid = true
		super(netID)
		if ent
			@modelCached = ent\GetModel()
			@SetEntity(ent)
			@SetupEntity(ent)

	IsValid: => @isValid
	GetModel: => @modelCached
	EntIndex: => @entID

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

	SetupEntity: (ent) =>
		if ent.__PPM2_PonyData
			return if ent.__PPM2_PonyData\GetOwner() and IsValid(ent.__PPM2_PonyData\GetOwner()) and StrongEntity(ent.__PPM2_PonyData\GetOwner()) ~= StrongEntity(@GetOwner())
			ent.__PPM2_PonyData\Remove() if ent.__PPM2_PonyData.Remove and ent.__PPM2_PonyData ~= @
		ent.__PPM2_PonyData = @
		@ent = ent
		@entTable = @ent\GetTable()
		return unless IsValid(ent)
		@modelCached = ent\GetModel()
		@ent = ent
		@flightController = PPM2.PonyflyController(@)
		@entID = ent\EntIndex()
		@lastLerpThink = RealTime()
		@ModelChanges(@modelCached, @modelCached)
		@Reset()
		timer.Simple(0, -> @GetRenderController()\CompileTextures() if @GetRenderController()) if CLIENT
		PPM2.DebugPrint('Ponydata ', @, ' was updated to use for ', @ent)
		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

	ModelChanges: (old = @ent\GetModel(), new = old) =>
		@modelCached = new
		@SetFly(false) if SERVER
		timer.Simple 0.5, ->
			return unless IsValid(@ent)
			@Reset()

	GenericDataChange: (state) =>
		hook.Run 'PPM2_PonyDataChanges', @ent, @, state
		if state\GetKey() == 'Entity' and IsValid(@GetEntity())
			@SetupEntity(@GetEntity())

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

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

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

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

	ResetScale: =>
		if scale = @GetSizeController()
			scale\ResetScale()

	ModifyScale: =>
		if scale = @GetSizeController()
			scale\ModifyScale()

	Reset: =>
		if scale = @GetSizeController()
			scale\Reset()
		if CLIENT
			@GetWeightController()\Reset() if @GetWeightController().Reset
			@GetRenderController()\Reset() if @GetRenderController().Reset
			@GetBodygroupController()\Reset() if @GetBodygroupController().Reset

	PlayerRespawn: =>
		return if not IsValid(@ent)
		@entTable.__cachedIsPony = @ent\IsPony()
		if not @entTable.__cachedIsPony
			return if @alreadyCalledRespawn
			@alreadyCalledRespawn = true
			@alreadyCalledDeath = true
		else
			@alreadyCalledRespawn = false
			@alreadyCalledDeath = false
		@ApplyBodygroups(CLIENT, true)
		@SetFly(false) if SERVER

		if scale = @GetSizeController()
			scale\PlayerRespawn()

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

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

		if scale = @GetSizeController()
			scale\PlayerDeath()

		if CLIENT
			@DoRagdollMerge()
			@GetRenderController()\PlayerDeath() if @GetRenderController()

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

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

	SlowUpdate: =>
		@GetBodygroupController()\SlowUpdate() if @GetBodygroupController()
		@GetWeightController()\SlowUpdate() if @GetWeightController()
		if scale = @GetSizeController()
			scale\SlowUpdate()

	Think: =>
		time = RealTime()
		delta = time - @lastLerpThink
		@lastLerpThink = time
		if @isValid and IsValid(@ent)
			for change in *@TriggerLerpAll(delta * 5)
				state = PPM2.NetworkChangeState('_NW_' .. change[1], change[1], change[2] + @['_NW_' .. change[1]], @)
				state\SetCantApply(true)
				@GenericDataChange(state)

	GetFlightController: => @flightController

	GetRenderController: =>
		return if SERVER
		return @renderController if not @isValid
		if not @renderController or @modelCached ~= @modelRender
			@modelRender = @modelCached
			cls = PPM2.GetRenderController(@modelCached)
			if @renderController and cls == @renderController.__class
				@renderController.ent = @ent
				PPM2.DebugPrint('Skipping render controller recreation for ', @ent, ' as part of ', @)
				return @renderController
			@renderController\Remove() if @renderController
			@renderController = cls(@)
		@renderController.ent = @ent
		return @renderController

	GetWeightController: =>
		return if SERVER
		return @weightController if not @isValid
		if not @weightController or @modelCached ~= @modelWeight
			@modelCached = @modelCached or @ent\GetModel()
			@modelWeight = @modelCached
			cls = PPM2.GetPonyWeightController(@modelCached)
			if @weightController and cls == @weightController.__class
				@weightController.ent = @ent
				PPM2.DebugPrint('Skipping weight controller recreation for ', @ent, ' as part of ', @)
				return @weightController
			@weightController\Remove() if @weightController
			@weightController = cls(@)
		@weightController.ent = @ent
		return @weightController

	GetSizeController: =>
		return @scaleController if not @isValid
		if not @scaleController or @modelCached ~= @modelScale
			@modelCached = @modelCached or @ent\GetModel()
			@modelScale = @modelCached
			cls = PPM2.GetSizeController(@modelCached)
			if @scaleController and cls == @scaleController.__class
				@scaleController.ent = @ent
				PPM2.DebugPrint('Skipping size controller recreation for ', @ent, ' as part of ', @)
				return @scaleController
			@scaleController\Remove() if @scaleController
			@scaleController = cls(@)
		@scaleController.ent = @ent
		return @scaleController
	GetScaleController: => @GetSizeController()

	GetBodygroupController: =>
		return @bodygroups if not @isValid
		if not @bodygroups or @modelBodygroups ~= @modelCached
			@modelCached = @modelCached or @ent\GetModel()
			@modelBodygroups = @modelCached
			cls = PPM2.GetBodygroupController(@modelCached)
			if @bodygroups and cls == @bodygroups.__class
				@bodygroups.ent = @ent
				PPM2.DebugPrint('Skipping bodygroup controller recreation for ', @ent, ' as part of ', @)
				return @bodygroups
			@bodygroups\Remove() if @bodygroups
			@bodygroups = cls(@)
		@bodygroups.ent = @ent
		return @bodygroups

	Remove: (byClient = false) =>
		@@NW_Objects[@netID] = nil if @NETWORKED
		@isValid = false
		@ent = @GetEntity() if not IsValid(@ent)
		@entTable.__PPM2_PonyData = nil if IsValid(@ent) and @ent.__PPM2_PonyData == @
		if CLIENT
			@GetWeightController()\Remove() if @GetWeightController()
			@GetRenderController()\Remove() if @GetRenderController()
			if IsValid(@ent) and @ent.__ppm2_task_hit
				@entTable.__ppm2_task_hit = false
				@ent\SetNoDraw(false)
		@GetBodygroupController()\Remove() if @GetBodygroupController()
		@GetSizeController()\Remove() if @GetSizeController()
		@flightController\Switch(false) if @flightController
		@@RenderTasks = [task for i, task in pairs @@NW_Objects when task\IsValid() and IsValid(task.ent) and not task.ent\IsPlayer() and not task\GetDisableTask()]

	__tostring: => "[#{@@__name}:#{@netID}|#{@ent}]"

PPM2.NetworkedPonyData = NetworkedPonyData

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

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

	hook.Add 'StrongEntityLinkUpdates', 'PPM2.NetworkedObjectCheck', =>
		if @__PPM2_PonyData
			@__PPM2_PonyData\SetEntity(@GetEntity())
			@__PPM2_PonyData\SetupEntity(@)
else
	hook.Add 'PlayerJoinTeam', 'PPM2.TeamWaypoint', (ply, new) ->
		ply.__ppm2_modified_jump = false
	hook.Add 'OnPlayerChangedTeam', 'PPM2.TeamWaypoint', (ply, old, new) ->
		ply.__ppm2_modified_jump = false

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


Remove weird 't' option from debug command

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- 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.

NAME, T = ...

import Logger, log from T
import decorate from T.misc

{database: db} = T

commands = {}

T.command_manager =
    handle: (message) =>
        name, arg = message\match '^([^%s]+)(.*)'
        return @help! unless name
        name = name\lower!
        local command

        for _, cmd in pairs commands
            for _, alias in pairs cmd[1]
                if name\match '^' .. alias
                    command = cmd
                    break

        return log\error 'Invalid command.' unless command

        args = {}

        if arg
            arg = arg\trim!
            if arg == ""
                arg = nil
            else
                for token in arg\gmatch '[^%s]+'
                    table.insert args, token

        command[2] arg, unpack args

    help: =>
        log\info 'TODO: Add help'

    register: (names, handler) =>
        names = {names} unless type(names) == 'table'
        entry = {names, handler}
        table.insert commands, entry

cm = T.command_manager
register = cm\register

register 'd', (arg) ->
    if arg then T\set_debug arg\match '^[ye]' and true or false else T\toggle_debug!
    log\notice "Debugging #{T\is_debug_enabled! and 'ENABLED' or 'DISABLED'}!"

register 'l', (arg, section, s_arg, ss_arg) ->
    if not arg
        return log\notice 'Logging is %s.', db('log') and 'enabled' or 'disabled'
    elseif arg\match '^[ts]' -- Log [t]oggle/[s]witch
        return db\set 'log', (not db 'log', true)
    elseif arg\match '^[ye]' or arg\match '^on' -- Log [y]es, [e]nable, [on]
        return db\set 'log', true
    elseif arg\match '^[nd]' or arg\match '^of' -- Log [n]o, [d]isable, [of]f
        return db\set 'log', false

    return unless section

    -- If user log level threshold is set high enough (notice)
    -- some log statements may not appear

    if section\match '^l' -- Log [l]evel
        if not s_arg
            level = db 'log.level', Logger.levels.INFO
            return log\notice 'Current log level is %s (%d)',
                Logger\level_to_prefix level, level
        level = s_arg\match '^%d+'
        level = tonumber level
        if not level -- Attempt to parse a text value
            prefix = s_arg\match('^.')\upper!
            for pre, lvl in Logger.levels
                if prefix == pre\match '^%w'
                    level = lvl
                    break
        if type(level) == 'number'
            db\set 'log.level', level
    elseif section\match '^c' -- Log [c]olor
        if not s_arg or not ss_arg
            name_color = Logger.colors.name!
            log\notice decorate('Current name color is %s', name_color), name_color
            for level, func in pairs Logger.colors.levels
                color = func!
                prefix = Logger\level_to_prefix level
                log\notice decorate('Current %s color is %s', color), prefix, color
        elseif s_arg\match '^na' -- [na]me
            db\set 'log.color.name', ss_arg
        else
            prefix_letter = s_arg\match '^.'\upper!
            local level, prefix
            for lvl, pre in pairs Logger.prefixes
                if prefix_letter == pre\match '^%w'
                    level = lvl
                    prefix = pre\lower!
                    break
            db\set 'log.color.level.' .. prefix, ss_arg

for i, v in ipairs {'sharexp', 'sxp'}
    _G['SLASH_' .. NAME\upper! .. i] = '/' .. v

SlashCmdList[NAME\upper!] = (msg, editBox) ->
    cm\handle msg

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- 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.

NAME, T = ...

import Logger, log from T
import decorate from T.misc

{database: db} = T

commands = {}

T.command_manager =
    handle: (message) =>
        name, arg = message\match '^([^%s]+)(.*)'
        return @help! unless name
        name = name\lower!
        local command

        for _, cmd in pairs commands
            for _, alias in pairs cmd[1]
                if name\match '^' .. alias
                    command = cmd
                    break

        return log\error 'Invalid command.' unless command

        args = {}

        if arg
            arg = arg\trim!
            if arg == ""
                arg = nil
            else
                for token in arg\gmatch '[^%s]+'
                    table.insert args, token

        command[2] arg, unpack args

    help: =>
        log\info 'TODO: Add help'

    register: (names, handler) =>
        names = {names} unless type(names) == 'table'
        entry = {names, handler}
        table.insert commands, entry

cm = T.command_manager
register = cm\register

register 'd', (arg) ->
    if arg then T\set_debug arg\match '^[yet]' and true or false else T\toggle_debug!
    log\notice "Debugging #{T\is_debug_enabled! and 'ENABLED' or 'DISABLED'}!"

register 'l', (arg, section, s_arg, ss_arg) ->
    if not arg
        return log\notice 'Logging is %s.', db('log') and 'enabled' or 'disabled'
    elseif arg\match '^[ts]' -- Log [t]oggle/[s]witch
        return db\set 'log', (not db 'log', true)
    elseif arg\match '^[ye]' or arg\match '^on' -- Log [y]es, [e]nable, [on]
        return db\set 'log', true
    elseif arg\match '^[nd]' or arg\match '^of' -- Log [n]o, [d]isable, [of]f
        return db\set 'log', false

    return unless section

    -- If user log level threshold is set high enough (notice)
    -- some log statements may not appear

    if section\match '^l' -- Log [l]evel
        if not s_arg
            level = db 'log.level', Logger.levels.INFO
            return log\notice 'Current log level is %s (%d)',
                Logger\level_to_prefix level, level
        level = s_arg\match '^%d+'
        level = tonumber level
        if not level -- Attempt to parse a text value
            prefix = s_arg\match('^.')\upper!
            for pre, lvl in Logger.levels
                if prefix == pre\match '^%w'
                    level = lvl
                    break
        if type(level) == 'number'
            db\set 'log.level', level
    elseif section\match '^c' -- Log [c]olor
        if not s_arg or not ss_arg
            name_color = Logger.colors.name!
            log\notice decorate('Current name color is %s', name_color), name_color
            for level, func in pairs Logger.colors.levels
                color = func!
                prefix = Logger\level_to_prefix level
                log\notice decorate('Current %s color is %s', color), prefix, color
        elseif s_arg\match '^na' -- [na]me
            db\set 'log.color.name', ss_arg
        else
            prefix_letter = s_arg\match '^.'\upper!
            local level, prefix
            for lvl, pre in pairs Logger.prefixes
                if prefix_letter == pre\match '^%w'
                    level = lvl
                    prefix = pre\lower!
                    break
            db\set 'log.color.level.' .. prefix, ss_arg

for i, v in ipairs {'sharexp', 'sxp'}
    _G['SLASH_' .. NAME\upper! .. i] = '/' .. v

SlashCmdList[NAME\upper!] = (msg, editBox) ->
    cm\handle msg


Utilize function stub feature in events.moon

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- 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.

NAME, T = ...

L = T.localization

EVENT_NAME_PATTERN = '^[A-Z_]+$'

frame = CreateFrame 'Frame'

T.event_manager = do
    class EventManager extends T.CallbackManager
        handle: (frame, event, ...) =>
            @fire event, ...

        get_handlers: (event) =>
            if @handlers[event] then [h for h in *@handlers[event]] else nil

        callback_initialized: (event) =>
            frame\RegisterEvent event unless frame\IsEventRegistered event

        callback_cleared: (event) =>
            frame\UnregisterEvent event if frame\IsEventRegistered event

        callbacks_cleared: =>
            frame\UnregisterAllEvents!

    EventManager!

em = T.event_manager

frame\SetScript 'OnEvent', em\handle

mt =
    __index: (tbl, key) ->
        if key\match EVENT_NAME_PATTERN
            em\get_handlers key
        else
            rawget tbl, key

    __newindex: (tbl, key, value) ->
        if key\match EVENT_NAME_PATTERN
            if value == nil
                em\unregister_callbacks key
            else
                em\register_callback key, value
        else
            rawset tbl, key, value

setmetatable T, mt

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- 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.

NAME, T = ...

L = T.localization

EVENT_NAME_PATTERN = '^[A-Z_]+$'

frame = CreateFrame 'Frame'

T.event_manager = do
    class EventManager extends T.CallbackManager
        handle: (frame, event, ...) =>
            @fire event, ...

        get_handlers: (event) =>
            if @handlers[event] then [h for h in *@handlers[event]] else nil

        callback_initialized: (event) =>
            frame\RegisterEvent event unless frame\IsEventRegistered event

        callback_cleared: (event) =>
            frame\UnregisterEvent event if frame\IsEventRegistered event

        callbacks_cleared: =>
            frame\UnregisterAllEvents!

    EventManager!

em = T.event_manager

frame\SetScript 'OnEvent', (f, e, ...) -> em\handle f, e, ...

mt =
    __index: (tbl, key) ->
        if key\match EVENT_NAME_PATTERN
            em\get_handlers key
        else
            rawget tbl, key

    __newindex: (tbl, key, value) ->
        if key\match EVENT_NAME_PATTERN
            if value == nil
                em\unregister_callbacks key
            else
                em\register_callback key, value
        else
            rawset tbl, key, value

setmetatable T, mt


Return nothing in PlayerDeath

Return nothing in PlayerDeath

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

hook.Add 'PlayerSpawn', 'PPM2.Hooks', =>
	if IsValid(@__ppm2_ragdoll)
		@__ppm2_ragdoll\Remove()
		@UnSpectate()
	timer.Simple 0, ->
		return unless @IsValid()
		if @GetPonyData()
			@GetPonyData()\PlayerRespawn()
			net.Start('PPM2.PlayerRespawn')
			net.WriteEntity(@)
			net.Broadcast()

		if @IsPony()
			return if @GetPonyData()
			timer.Simple 0.5, ->
				net.Start('PPM2.RequestPonyData')
				net.Send(@)

hook.Add 'PlayerInitialSpawn', 'PPM2.Hooks', =>
	timer.Simple 0, ->
		return unless @IsValid()
		return unless @IsPony()

		timer.Simple 10, ->
			net.Start('PPM2.RequestPonyData')
			net.Send(@)

do
	REQUIRE_CLIENTS = {}

	safeSendFunction = ->
		for ply, data in pairs REQUIRE_CLIENTS
			if not IsValid(ply)
				REQUIRE_CLIENTS[ply] = nil
				continue

			ent = table.remove(data, 1)
			if not ent
				REQUIRE_CLIENTS[ply] = nil
				continue

			data = ent\GetPonyData()
			continue if not data
			data\NetworkTo(ply)

	errorTrack = (err) ->
		PPM2.Message 'Networking Error: ', err
		PPM2.Message debug.traceback()

	timer.Create 'PPM2.Require', 0.25, 0, -> xpcall(safeSendFunction, errorTrack)
	net.Receive 'PPM2.Require', (len = 0, ply = NULL) ->
		return if not IsValid(ply)
		REQUIRE_CLIENTS[ply] = for ent in *ents.GetAll()
			continue if ent == ply
			data = ent\GetPonyData()
			continue if not data
			ent

net.Receive 'PPM2.EditorStatus', (len = 0, ply = NULL) ->
	return if not IsValid(ply)
	ply\SetNWBool('PPM2.InEditor', net.ReadBool())

ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')

createPlayerRagdoll = =>
	@__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
	@__ppm2_ragdoll = ents.Create('prop_ragdoll')
	rag = @GetRagdollEntity()
	rag\Remove() if IsValid(rag)
	with @__ppm2_ragdoll
		\SetModel(@GetModel())
		\SetPos(@GetPos())
		\SetAngles(@EyeAngles())
		\SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
		\Spawn()
		\Activate()
		hook.Run 'PlayerSpawnedRagdoll', @, @GetModel(), @__ppm2_ragdoll
		.__ppm2_ragdoll_parent = @
		\SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
		\SetNWBool('PPM2.IsDeathRagdoll', true)
		vel = @GetVelocity()
		\SetVelocity(vel)
		\SetAngles(@EyeAngles())
		@Spectate(OBS_MODE_CHASE)
		@SpectateEntity(@__ppm2_ragdoll)
		for boneID = 0, @__ppm2_ragdoll\GetBoneCount() - 1
			physobjID = @__ppm2_ragdoll\TranslateBoneToPhysBone(boneID)
			pos, ang = @GetBonePosition(boneID)
			physobj = @__ppm2_ragdoll\GetPhysicsObjectNum(physobjID)
			physobj\SetVelocity(vel)
			physobj\SetMass(300) -- lol
			physobj\SetPos(pos, true) if pos
			physobj\SetAngles(ang) if ang
		copy = @GetPonyData()\Clone(@__ppm2_ragdoll)
		timer.Simple 0.5, -> copy\Create() if IsValid(@__ppm2_ragdoll)

ALLOW_RAGDOLL_DAMAGE = CreateConVar('ppm2_sv_ragdoll_damage', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Should death ragdoll cause damage?')

hook.Add 'EntityTakeDamage', 'PPM2.DeathRagdoll', (dmg) =>
	attacker = dmg\GetAttacker()
	return if not IsValid(attacker)
	if attacker.__ppm2_ragdoll_parent
		dmg\SetAttacker(attacker.__ppm2_ragdoll_parent)
		if not ALLOW_RAGDOLL_DAMAGE\GetBool()
			dmg\SetDamage(0)
			dmg\SetMaxDamage(0)

hook.Add 'PostPlayerDeath', 'PPM2.Hooks', =>
	return if not @GetPonyData()
	@GetPonyData()\PlayerDeath()
	net.Start('PPM2.PlayerDeath')
	net.WriteEntity(@)
	net.Broadcast()
	if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
		createPlayerRagdoll(@)

hook.Add 'PlayerDeath', 'PPM2.Hooks', =>
	return if not @GetPonyData()
	if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
		createPlayerRagdoll(@)
	return nil

hook.Add 'EntityRemoved', 'PPM2.PonyDataRemove', =>
	return if @IsPlayer()
	return if not @GetPonyData()
	with @GetPonyData()
		net.Start('PPM2.PonyDataRemove')
		net.WriteUInt(.netID, 16)
		net.Broadcast()
		\Remove()

hook.Add 'PlayerDisconnected', 'PPM2.NotifyClients', =>
	@__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
	data = @GetPonyData()
	return if not data
	net.Start('PPM2.NotifyDisconnect')
	net.WriteUInt(data.netID, 16)
	net.Broadcast()

BOTS_ARE_PONIES = CreateConVar('ppm2_bots', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Whatever spawn bots as ponies')

hook.Add 'PlayerSetModel', 'PPM2.Bots', =>
	return if not BOTS_ARE_PONIES\GetBool()
	return if not @IsBot()
	@SetModel('models/ppm/player_default_base_new.mdl')
	return true

PlayerSpawnBot = =>
	return if not BOTS_ARE_PONIES\GetBool()
	return if not @IsBot()
	timer.Simple 1, ->
		return if not IsValid(@)
		@SetViewOffset(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET))
		@SetViewOffsetDucked(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET_DUCK))
		if not @GetPonyData()
			data = PPM2.NetworkedPonyData(nil, @)
			PPM2.Randomize(data)
			data\Create()

hook.Add 'PlayerSpawn', 'PPM2.Bots', PlayerSpawnBot
timer.Simple 0, -> PlayerSpawnBot(ply) for ply in *player.GetAll()

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

hook.Add 'PlayerSpawn', 'PPM2.Hooks', =>
	if IsValid(@__ppm2_ragdoll)
		@__ppm2_ragdoll\Remove()
		@UnSpectate()
	timer.Simple 0, ->
		return unless @IsValid()
		if @GetPonyData()
			@GetPonyData()\PlayerRespawn()
			net.Start('PPM2.PlayerRespawn')
			net.WriteEntity(@)
			net.Broadcast()

		if @IsPony()
			return if @GetPonyData()
			timer.Simple 0.5, ->
				net.Start('PPM2.RequestPonyData')
				net.Send(@)

hook.Add 'PlayerInitialSpawn', 'PPM2.Hooks', =>
	timer.Simple 0, ->
		return unless @IsValid()
		return unless @IsPony()

		timer.Simple 10, ->
			net.Start('PPM2.RequestPonyData')
			net.Send(@)

do
	REQUIRE_CLIENTS = {}

	safeSendFunction = ->
		for ply, data in pairs REQUIRE_CLIENTS
			if not IsValid(ply)
				REQUIRE_CLIENTS[ply] = nil
				continue

			ent = table.remove(data, 1)
			if not ent
				REQUIRE_CLIENTS[ply] = nil
				continue

			data = ent\GetPonyData()
			continue if not data
			data\NetworkTo(ply)

	errorTrack = (err) ->
		PPM2.Message 'Networking Error: ', err
		PPM2.Message debug.traceback()

	timer.Create 'PPM2.Require', 0.25, 0, -> xpcall(safeSendFunction, errorTrack)
	net.Receive 'PPM2.Require', (len = 0, ply = NULL) ->
		return if not IsValid(ply)
		REQUIRE_CLIENTS[ply] = for ent in *ents.GetAll()
			continue if ent == ply
			data = ent\GetPonyData()
			continue if not data
			ent

net.Receive 'PPM2.EditorStatus', (len = 0, ply = NULL) ->
	return if not IsValid(ply)
	ply\SetNWBool('PPM2.InEditor', net.ReadBool())

ENABLE_NEW_RAGDOLLS = CreateConVar('ppm2_sv_new_ragdolls', '1', {FCVAR_NOTIFY, FCVAR_REPLICATED}, 'Enable new ragdolls')

createPlayerRagdoll = =>
	@__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
	@__ppm2_ragdoll = ents.Create('prop_ragdoll')
	rag = @GetRagdollEntity()
	rag\Remove() if IsValid(rag)
	with @__ppm2_ragdoll
		\SetModel(@GetModel())
		\SetPos(@GetPos())
		\SetAngles(@EyeAngles())
		\SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
		\Spawn()
		\Activate()
		hook.Run 'PlayerSpawnedRagdoll', @, @GetModel(), @__ppm2_ragdoll
		.__ppm2_ragdoll_parent = @
		\SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
		\SetNWBool('PPM2.IsDeathRagdoll', true)
		vel = @GetVelocity()
		\SetVelocity(vel)
		\SetAngles(@EyeAngles())
		@Spectate(OBS_MODE_CHASE)
		@SpectateEntity(@__ppm2_ragdoll)
		for boneID = 0, @__ppm2_ragdoll\GetBoneCount() - 1
			physobjID = @__ppm2_ragdoll\TranslateBoneToPhysBone(boneID)
			pos, ang = @GetBonePosition(boneID)
			physobj = @__ppm2_ragdoll\GetPhysicsObjectNum(physobjID)
			physobj\SetVelocity(vel)
			physobj\SetMass(300) -- lol
			physobj\SetPos(pos, true) if pos
			physobj\SetAngles(ang) if ang
		copy = @GetPonyData()\Clone(@__ppm2_ragdoll)
		timer.Simple 0.5, -> copy\Create() if IsValid(@__ppm2_ragdoll)

ALLOW_RAGDOLL_DAMAGE = CreateConVar('ppm2_sv_ragdoll_damage', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Should death ragdoll cause damage?')

hook.Add 'EntityTakeDamage', 'PPM2.DeathRagdoll', (dmg) =>
	attacker = dmg\GetAttacker()
	return if not IsValid(attacker)
	if attacker.__ppm2_ragdoll_parent
		dmg\SetAttacker(attacker.__ppm2_ragdoll_parent)
		if not ALLOW_RAGDOLL_DAMAGE\GetBool()
			dmg\SetDamage(0)
			dmg\SetMaxDamage(0)

hook.Add 'PostPlayerDeath', 'PPM2.Hooks', =>
	return if not @GetPonyData()
	@GetPonyData()\PlayerDeath()
	net.Start('PPM2.PlayerDeath')
	net.WriteEntity(@)
	net.Broadcast()
	if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
		createPlayerRagdoll(@)

hook.Add 'PlayerDeath', 'PPM2.Hooks', =>
	return if not @GetPonyData()
	if ENABLE_NEW_RAGDOLLS\GetBool() and @IsPony()
		createPlayerRagdoll(@)

hook.Add 'EntityRemoved', 'PPM2.PonyDataRemove', =>
	return if @IsPlayer()
	return if not @GetPonyData()
	with @GetPonyData()
		net.Start('PPM2.PonyDataRemove')
		net.WriteUInt(.netID, 16)
		net.Broadcast()
		\Remove()

hook.Add 'PlayerDisconnected', 'PPM2.NotifyClients', =>
	@__ppm2_ragdoll\Remove() if IsValid(@__ppm2_ragdoll)
	data = @GetPonyData()
	return if not data
	net.Start('PPM2.NotifyDisconnect')
	net.WriteUInt(data.netID, 16)
	net.Broadcast()

BOTS_ARE_PONIES = CreateConVar('ppm2_bots', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Whatever spawn bots as ponies')

hook.Add 'PlayerSetModel', 'PPM2.Bots', =>
	return if not BOTS_ARE_PONIES\GetBool()
	return if not @IsBot()
	@SetModel('models/ppm/player_default_base_new.mdl')
	return true

PlayerSpawnBot = =>
	return if not BOTS_ARE_PONIES\GetBool()
	return if not @IsBot()
	timer.Simple 1, ->
		return if not IsValid(@)
		@SetViewOffset(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET))
		@SetViewOffsetDucked(Vector(0, 0, PPM2.PLAYER_VIEW_OFFSET_DUCK))
		if not @GetPonyData()
			data = PPM2.NetworkedPonyData(nil, @)
			PPM2.Randomize(data)
			data\Create()

hook.Add 'PlayerSpawn', 'PPM2.Bots', PlayerSpawnBot
timer.Simple 0, -> PlayerSpawnBot(ply) for ply in *player.GetAll()


Added tweens.motion.CubicMotion

Added tweens.motion.CubicMotion

require "LunoPunk.tweens.motion.Motion"
require "LunoPunk.geometry.Point"

-- Determines motion along a cubic curve.
class CubicMotion extends Motion
	-- Constructor.
	-- @param	complete	Optional completion callback.
	-- @param	type		Tween type.
	new: (complete, type) =>
		-- Curve information.
		@__fromX, @__fromY = 0, 0
		@__toX, @__toY = 0, 0
		@__aX, @__aY = 0, 0
		@__bX, @__bY = 0, 0

		super 0, complete, type, nil

	-- Starts moving along the curve.
	-- @param	fromX		X start.
	-- @param	fromY		Y start.
	-- @param	aX			First control x.
	-- @param	aY			First control y.
	-- @param	bX			Second control x.
	-- @param	bY			Second control y.
	-- @param	toX			X finish.
	-- @param	toY			Y finish.
	-- @param	duration	Duration of the movement.
	-- @param	ease		Optional easer function.
	setMotion: (fromX, fromY, aX, aY, bX, bY, toX, toY, duration, ease = nil) =>
		@x, @__fromX = fromX, fromX
		@y, @__fromY = fromY, fromY
		@__aX, @__aY = aX, aY
		@__bX, @__bY = bX, bY
		@__toX, @__toY = toX, toY
		@__target = duration
		@__ease = ease
		@start!

	-- @private Updates the Tween.
	update: =>
		super!
		ttt = @t^3
		tt = @t^2
		@x = ttt * (@__toX + 3 * (@__aX - @__bX) - @__fromX) + 3 * tt * (@__fromX - 2 * @__aX + @__bX) + 3 * @t * (@__aX - @__fromX) + @__fromX
		@y = ttt * (@__toY + 3 * (@__aY - @__bY) - @__fromY) + 3 * tt * (@__fromY - 2 * @__aY + @__bY) + 3 * @t * (@__aY - @__fromY) + @__fromY


entur: use coords, more improvs

entur: use coords, more improvs

util = require'util'
simplehttp = util.simplehttp
json = util.json
urlEncode = util.urlEncode

client_name = 'ivar2 - entur_irc'
access_token = 'jwt bearer token'

--getToken = (name) ->
--	data = simplehttp{
--		method: 'POST',
--		url: 'https://partner.entur.org/oauth/token',
--		headers: 'content-type: application/json',
--		data: '{
--			"client_id": "ivar2",
--			"client_secret": "raviravi",
--			"audience":"https://api.entur.io",
--			"grant_type":"client_credentials"
--		}'
--	}
--
--	info = json.decode data
--	acess_token = info.access_token
--
formatLeg = (leg) ->
  if leg == 'foot'
    return '👟'
  if leg == 'bus'
    return '🚌'
  if leg == 'water'
    return '🚤'
  if leg == 'metro'
    return '🚇'
  if leg == 'tram'
    return '🚊'
  if leg == 'air'
    return '✈'
  if leg == 'rail'
    return '🚄'
  if leg == 'coach'
    return '🚍'
  return leg

parseDate = (datestr) ->
	year, month, day, hour, min, sec = datestr\match "([^-]+)%-([^-]+)%-([^T]+)T([^:]+):([^:]+):(%d%d)"
	return os.time{
		year: year,
		month: month,
		day: day,
		hour: hour,
		min: min,
		sec: sec,
	}

getStop = (name) ->
  data = simplehttp{
    url:"https://api.entur.io/geocoder/v1/autocomplete?text=#{urlEncode name}&lang=nn",-- nynorsk!
    headers: {
        ['Et-Client-Name']: client_name
    }
  }
  info = json.decode data
  if info
    _, feature = next info.features
    return feature

getRoutes = (s, d, frm, to) =>

  frm = getStop(frm)
  to = getStop(to)

  print(json.encode(frm))
  print(json.encode(to))

  graphql = '{
    trip(
      from: {
        coordinates: {
          latitude: '..frm.geometry.coordinates[2]..',
          longitude:' ..frm.geometry.coordinates[1]..',
        }
      },
      to: {
        coordinates: {
          latitude: '..to.geometry.coordinates[2]..',
          longitude:' ..to.geometry.coordinates[1]..',
        }
      },
      numTripPatterns: 3,
    ) {
   dateTime
   fromPlace {
      name

    }
    toPlace {
      name

    }
    tripPatterns {
      startTime
      endTime
      duration
      waitingTime
      distance
      walkTime
      walkDistance
      legs {
        mode
        fromPlace {
          name
        }
        toPlace {
          name
        }
      }
    }
    }
  }
  '
  graphql = graphql\gsub '\n', ''
  post_data = '{"query":"'..graphql..'","variables":null}'
  data, uri, response = simplehttp {url:'https://api.entur.io/journey-planner/v2/graphql', method:'POST', data:post_data , headers:{['Et-Client-Name']: client_name, ['Content-Type']:'application/json'}}
  if response.status_code != 200
    say data
  else
    out = {}
    print data
    data = json.decode(data).data
    for i, trip in ipairs data.trip.tripPatterns
      time = parseDate trip.startTime
      datetime = os.date '*t', time
      relative = (time - os.time!) / 60

      a_time = parseDate trip.endTime
      a_datetime = os.date '*t', a_time
      legs = {}
      for leg in *trip.legs
        -- don't include the walking to the station, just ugly
        if #legs == 0 and leg.mode == 'foot'
          continue
        legs[#legs+1] = formatLeg(leg.mode)

      -- don't include walking from the station to the destiation, just ugly
      if legs[#legs] == formatLeg('foot') and #legs > 1
        legs[#legs] = nil
      if #legs == 0
        legs[#legs+1] = formatLeg('foot')
      legs = table.concat legs, ''
      out[#out+1] = "#{datetime.hour}:#{string.format('%02d', datetime.min)} (#{string.format('%d', relative)}m) [#{legs}]-> #{a_datetime.hour}:#{string.format('%02d', a_datetime.min)}"

    say table.concat(out, ', ')

PRIVMSG:
  '^%pstop (.*)$': (s, d, a) =>
    say json.encode(getStop(a))
  '^%pentur (.+),(.+)$': getRoutes

util = require'util'
simplehttp = util.simplehttp
json = util.json
urlEncode = util.urlEncode

client_name = 'ivar2 - entur_irc'
access_token = 'jwt bearer token'

--getToken = (name) ->
--	data = simplehttp{
--		method: 'POST',
--		url: 'https://partner.entur.org/oauth/token',
--		headers: 'content-type: application/json',
--		data: '{
--			"client_id": "ivar2",
--			"client_secret": "raviravi",
--			"audience":"https://api.entur.io",
--			"grant_type":"client_credentials"
--		}'
--	}
--
--	info = json.decode data
--	acess_token = info.access_token

parseDate = (datestr) ->
	year, month, day, hour, min, sec = datestr\match "([^-]+)%-([^-]+)%-([^T]+)T([^:]+):([^:]+):(%d%d)"
	return os.time{
		year: year,
		month: month,
		day: day,
		hour: hour,
		min: min,
		sec: sec,
	}

getStop = (name) ->
  data = simplehttp{
    url:"https://api.entur.io/geocoder/v1/autocomplete?text=#{urlEncode name}&lang=nn",-- nynorsk!
    headers: {
        ['Et-Client-Name']: client_name
    }
  }
  info = json.decode data
  if info
    _, feature = next info.features
    return feature

getRoutes = (s, d, frm, to) =>

  frm = getStop(frm)
  to = getStop(to)

  frm_id = frm.properties.id
  to_id = to.properties.id

  graphql = '{
    trip(
      from: {
        place: \\"'..frm_id..'\\",
      },
      to: {
        place: \\"'..to_id..'\\",
      },
      numTripPatterns: 3,
    ) {
   dateTime
   fromPlace {
      name

    }
    toPlace {
      name

    }
    tripPatterns {
      startTime
      endTime
      duration
      waitingTime
      distance
      walkTime
      walkDistance
      weight
    }
    }
  }
  '
  graphql = graphql\gsub '\n', ''
  post_data = '{"query":"'..graphql..'","variables":null}'
  data, uri, response = simplehttp {url:'https://api.entur.io/journey-planner/v2/graphql', method:'POST', data:post_data , headers:{['Et-Client-Name']: client_name, ['Content-Type']:'application/json'}}
  if response.status_code != 200
    say data
  else
    out = {}
    data = json.decode(data).data
    for i, trip in ipairs data.trip.tripPatterns
      time = parseDate trip.startTime
      datetime = os.date '*t', time
      relative = (time - os.time!) / 60

      a_time = parseDate trip.endTime
      a_datetime = os.date '*t', a_time
      out[#out+1] = "#{datetime.hour}:#{string.format('%02d', datetime.min)} (#{string.format('%d', relative)}m) -> #{a_datetime.hour}:#{string.format('%02d', a_datetime.min)}"

    say table.concat(out, ', ')

PRIVMSG:
  '^%pstop (.*)$': (s, d, a) =>
    say json.encode(getStop(a))
  '^%pentur (.+),(.+)$': getRoutes