lib/std: Add Map\unwrap

lib/std: Add Map\unwrap

local Result, Option, Ok, Err

expect_class = (value, expected)->
	assert value.__class == expected,
		"expected type #{expected.__name}<#{expected}>, got " ..
		"#{value.__class.__name}<#{value.__class}>"

class Result
	Ok: 0
	Err: 1

	new: (variant, content)=>
		@variant = variant
		@content = content

	is_ok: => @variant == Ok
	is_err: => @variant == Err

	--- Unwrap a result, returning the content or otherwise erroring
	expect: (error_value)=>
		@content if @is_ok! else error "#{error_value}: #{@content}"

	--- Raise error if is_err!, otherwise return content
	unwrap: =>
		@content if @is_ok! else error @content

	--- Raises error if is_ok!, otherwise returns error value
	unwrap_err: =>
		@content if @is_err! else error @content

	--- Returns content if is_ok!, otherwise `fallback`
	-- @param fallback
	unwrap_or: (fallback)=>
		@content if @is_ok! else fallback

	--- Returns content if is_ok!, otherwise the called output of `fallback_fn`
	-- @tparam fallback_fn
	unwrap_or_else: (fallback_fn)=>
		@content if @is_ok! else fallback_fn!

	ok: => Option(@content) if @is_ok! else Option!
	err: => Option(@content) if @is_err! else Option!

	--- Map Result<T, E> to Result<U, E> - Error can be kept if is_err!
	-- @tparam function fn Function to use for mapping content
	map: (fn)=>
		Ok fn @content if @is_ok else self

	--- Map Result<T, E> to Result<T, F> - Content can be kept if is_ok!
	-- @tparam function fn Function to use for mapping error
	map_err: (fn)=>
		if @is_err
			return Err fn @content
		self

	--- Return `right` if the variant is `Ok`, otherwise return self's `Err`
	-- @param right
	and_also: (right)=>
		self if @is_error! else right

	--- Return evaluated output of `right_fn` if the variant is `Ok`, otherwise
	-- return self's `Err`
	-- @tparam function right_fn
	and_then: (right_fn)=>
		self if @is_error! else right_fn @content

	--- Return `right` if the variant is `Err`, otherwise return self's `Ok`
	-- @param right
	or_other: (right)=>
		self if @is_ok! else right

	--- Return evaluated output of  `right_fn` if the variant is `Err`,
	-- otherwise return self's `Ok`
	-- @tparam function right_fn
	or_else: (right_fn)=>
		self if @is_ok! else right_fn @content

	--- Transpose a Result of an Option into an Option of a Result
	transpose: =>
		expect_class @content, Result
		if @content\is_some!
			return @variant @content.data -- Some
		Option! -- None

import Ok, Err from Result

result_variant_mt = {
	__call: (...)=> Result self, ...
}

debug.setmetatable Ok, result_variant_mt
debug.setmetatable Err, result_variant_mt

class Option
	new: (some=nil)=>
		if some
			@data = some

	is_some: => @data != nil
	is_none: => @data == nil

	--- Transform an Option<Option<T>> into an Option<T>
	flatten: =>
		expect_class @data, Option
		Option @data\expect!

	--- Unwrap an option, returning the content or otherwise erroring
	-- @param error_value Value raised if variant is None
	expect: (error_value)=>
		@data if @is_some! else Err(error_value)\unwrap!

	--- Moves a value `v` out of `Option<T>` if it is a `Some(v)` variant
	unwrap: =>
		@expect "called Option.unwrap() on a None variant"

	--- Return the contained value if is_some! or a fallback value
	-- @param fallback
	unwrap_or: (fallback)=>
		@data if @is_some! else fallback

	--- Return the contained value if is_some!, otherwise the called output of
	-- `fallback_fn`
	-- @tparam function fallback_fn
	unwrap_or_else: (fallback_fn)=>
		@data if @is_some! else fallback_fn!

	--- Applies a function to the contained value
	-- @tparam function fn Function to use for mapping content
	map: (fn)=>
		Option fn @data if @is_some! else self

	--- Applies a function to the contained value, or returns a fallback
	-- @param fallback
	-- @tparam function fn Function to use for mapping content
	map_or: (fallback, fn)=>
		Option fn @data if @is_some! else fallback

	--- Applies a function to the contained value, or calls fallback function
	-- @tparam function fallback_fn
	-- @tparam function fn Function to use for mapping content
	map_or_else: (fallback_fn, fn)=>
		Option fn @data if @is_some! else fallback_fn!

	--- Transform an `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to
	-- `Ok(v)` and `None` to `Err(error_value)`
	-- @param error_value
	ok_or: (error_value)=>
		if @is_some!
			Ok @data
		else -- None
			Err error_value

	--- Transform an `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to
	-- `Ok(v)` and `None` to `Err(error_fn!)`
	-- @tparam function error_fn
	ok_or_else: (error_fn)=>
		if @is_some!
			Ok @data
		else -- None
			Err error_fn!

	--- Return `None` if already `None`, otherwise call `predicate` function
	-- and returns `Some(v)` if the predicate function returns true, otherwise
	-- None if `predicate` returns false
	-- @tparam function predicate
	filter: (predicate)=>
		if @is_some! and predicate @data
			return @data
		Option!

	--- Return `right` if the variant is `Some`
	-- @param right
	and_also: (right)=>
		self if @is_none! else right

	--- Return evaluated output of `right_fn` if the variant is `Some`,
	-- otherwise return None
	-- @tparam function right_fn
	and_then: (right_fn)=>
		self if @is_none! else right_fn @data

	--- Return `right` if the variant is `None`, else return self's `Some`
	-- @param right
	or_other: (right)=>
		self if @is_some! else right

	--- Returns evaluated output of `right_fn` if the variant is `None`,
	-- otherwise returns self's `Some`
	-- @tparam function right_fn
	or_else: (right_fn)=>
		self if @is_some! else right_fn!

	--- Returns a `Some(value)` if either the self or the `right` have a
	-- `Some(value)`, but not both
	xor: (right)=>
		if (@is_some! and right\is_none!) or (@is_none! and right\is_some!)
			return @or_other right
		Option! -- None

	--- Transpose an Option of a Result into a Result of an Option
	transpose: =>
		if @is_none!
			return Ok self -- Result(None)
		@data.variant Option(@data.content)

class Entry
	new: (source, key)=>
		@source = source
		@key = key

	--- Determine whether or not a `key` exists currently in the table
	exists: => @source[@key] != nil

	--- Create a `default` value in the source if the key doesn't have an
	-- associated value already
	-- @param default
	or_insert: (default)=>
		source, key = @source, @key
		source[key] = default unless source[key]
		source[key]

	--- Create a default value from the result of `default_fn` in the source if
	-- the key doesn't have an associated value already
	-- @tparam function default_fn
	or_insert_with: (default_fn)=>
		source, key = @source, @key
		source[key] = default_fn! unless source[key]
		source[key]

	--- Apply a function `fn` to the entry, if it exists; should be called
	-- before returning the value, and can be chained
	-- @tparam function fn
	and_modify: (fn)=>
		source, key = @source, @key
		if source[key]
			source[key] = fn source[key]
		self

class Map
	new: (data={})=>
		@data = data

	--- Return an `Entry` to the potential value in the Map, which can be used
	-- to add a default value. If a default value isn't available or desired,
	-- instead use get()
	-- @param name
	entry: (key)=> Entry @data, key

	--- Returns `true` if a key exists within the table, or otherwise `false`
	contains_key: (key)=> @data[key] != nil

	--- Return an `Option<T>` to the potential value in the Map, which can be
	-- used to see if a value exists. If you want to insert a default value in
	-- the case it doesn't exist, use entry()
	get: (key)=> Option @data[key]

	--- Return a value expected to exist in the Map
	expect: (key, message)=>
		@get(key)\expect message

	--- Return a value expected to exist in the Map
	unwrap: (key)=>
		@get(key)\unwrap!

	--- Set a `value` in the Map to an associated `key`
	-- @param key
	-- @param value
	set: (key, value)=>
		@data[key] = value

	--- Remove a `key` from a Map
	remove: (key)=> @data[key] = nil

	--- Iterate over the Map
	iter: => pairs @data

return :Result, :Ok, :Err, :Option, :Entry, :Map

local Result, Option, Ok, Err

expect_class = (value, expected)->
	assert value.__class == expected,
		"expected type #{expected.__name}<#{expected}>, got " ..
		"#{value.__class.__name}<#{value.__class}>"

class Result
	Ok: 0
	Err: 1

	new: (variant, content)=>
		@variant = variant
		@content = content

	is_ok: => @variant == Ok
	is_err: => @variant == Err

	--- Unwrap a result, returning the content or otherwise erroring
	expect: (error_value)=>
		@content if @is_ok! else error "#{error_value}: #{@content}"

	--- Raise error if is_err!, otherwise return content
	unwrap: =>
		@content if @is_ok! else error @content

	--- Raises error if is_ok!, otherwise returns error value
	unwrap_err: =>
		@content if @is_err! else error @content

	--- Returns content if is_ok!, otherwise `fallback`
	-- @param fallback
	unwrap_or: (fallback)=>
		@content if @is_ok! else fallback

	--- Returns content if is_ok!, otherwise the called output of `fallback_fn`
	-- @tparam fallback_fn
	unwrap_or_else: (fallback_fn)=>
		@content if @is_ok! else fallback_fn!

	ok: => Option(@content) if @is_ok! else Option!
	err: => Option(@content) if @is_err! else Option!

	--- Map Result<T, E> to Result<U, E> - Error can be kept if is_err!
	-- @tparam function fn Function to use for mapping content
	map: (fn)=>
		Ok fn @content if @is_ok else self

	--- Map Result<T, E> to Result<T, F> - Content can be kept if is_ok!
	-- @tparam function fn Function to use for mapping error
	map_err: (fn)=>
		if @is_err
			return Err fn @content
		self

	--- Return `right` if the variant is `Ok`, otherwise return self's `Err`
	-- @param right
	and_also: (right)=>
		self if @is_error! else right

	--- Return evaluated output of `right_fn` if the variant is `Ok`, otherwise
	-- return self's `Err`
	-- @tparam function right_fn
	and_then: (right_fn)=>
		self if @is_error! else right_fn @content

	--- Return `right` if the variant is `Err`, otherwise return self's `Ok`
	-- @param right
	or_other: (right)=>
		self if @is_ok! else right

	--- Return evaluated output of  `right_fn` if the variant is `Err`,
	-- otherwise return self's `Ok`
	-- @tparam function right_fn
	or_else: (right_fn)=>
		self if @is_ok! else right_fn @content

	--- Transpose a Result of an Option into an Option of a Result
	transpose: =>
		expect_class @content, Result
		if @content\is_some!
			return @variant @content.data -- Some
		Option! -- None

import Ok, Err from Result

result_variant_mt = {
	__call: (...)=> Result self, ...
}

debug.setmetatable Ok, result_variant_mt
debug.setmetatable Err, result_variant_mt

class Option
	new: (some=nil)=>
		if some
			@data = some

	is_some: => @data != nil
	is_none: => @data == nil

	--- Transform an Option<Option<T>> into an Option<T>
	flatten: =>
		expect_class @data, Option
		Option @data\expect!

	--- Unwrap an option, returning the content or otherwise erroring
	-- @param error_value Value raised if variant is None
	expect: (error_value)=>
		@data if @is_some! else Err(error_value)\unwrap!

	--- Moves a value `v` out of `Option<T>` if it is a `Some(v)` variant
	unwrap: =>
		@expect "called Option.unwrap() on a None variant"

	--- Return the contained value if is_some! or a fallback value
	-- @param fallback
	unwrap_or: (fallback)=>
		@data if @is_some! else fallback

	--- Return the contained value if is_some!, otherwise the called output of
	-- `fallback_fn`
	-- @tparam function fallback_fn
	unwrap_or_else: (fallback_fn)=>
		@data if @is_some! else fallback_fn!

	--- Applies a function to the contained value
	-- @tparam function fn Function to use for mapping content
	map: (fn)=>
		Option fn @data if @is_some! else self

	--- Applies a function to the contained value, or returns a fallback
	-- @param fallback
	-- @tparam function fn Function to use for mapping content
	map_or: (fallback, fn)=>
		Option fn @data if @is_some! else fallback

	--- Applies a function to the contained value, or calls fallback function
	-- @tparam function fallback_fn
	-- @tparam function fn Function to use for mapping content
	map_or_else: (fallback_fn, fn)=>
		Option fn @data if @is_some! else fallback_fn!

	--- Transform an `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to
	-- `Ok(v)` and `None` to `Err(error_value)`
	-- @param error_value
	ok_or: (error_value)=>
		if @is_some!
			Ok @data
		else -- None
			Err error_value

	--- Transform an `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to
	-- `Ok(v)` and `None` to `Err(error_fn!)`
	-- @tparam function error_fn
	ok_or_else: (error_fn)=>
		if @is_some!
			Ok @data
		else -- None
			Err error_fn!

	--- Return `None` if already `None`, otherwise call `predicate` function
	-- and returns `Some(v)` if the predicate function returns true, otherwise
	-- None if `predicate` returns false
	-- @tparam function predicate
	filter: (predicate)=>
		if @is_some! and predicate @data
			return @data
		Option!

	--- Return `right` if the variant is `Some`
	-- @param right
	and_also: (right)=>
		self if @is_none! else right

	--- Return evaluated output of `right_fn` if the variant is `Some`,
	-- otherwise return None
	-- @tparam function right_fn
	and_then: (right_fn)=>
		self if @is_none! else right_fn @data

	--- Return `right` if the variant is `None`, else return self's `Some`
	-- @param right
	or_other: (right)=>
		self if @is_some! else right

	--- Returns evaluated output of `right_fn` if the variant is `None`,
	-- otherwise returns self's `Some`
	-- @tparam function right_fn
	or_else: (right_fn)=>
		self if @is_some! else right_fn!

	--- Returns a `Some(value)` if either the self or the `right` have a
	-- `Some(value)`, but not both
	xor: (right)=>
		if (@is_some! and right\is_none!) or (@is_none! and right\is_some!)
			return @or_other right
		Option! -- None

	--- Transpose an Option of a Result into a Result of an Option
	transpose: =>
		if @is_none!
			return Ok self -- Result(None)
		@data.variant Option(@data.content)

class Entry
	new: (source, key)=>
		@source = source
		@key = key

	--- Determine whether or not a `key` exists currently in the table
	exists: => @source[@key] != nil

	--- Create a `default` value in the source if the key doesn't have an
	-- associated value already
	-- @param default
	or_insert: (default)=>
		source, key = @source, @key
		source[key] = default unless source[key]
		source[key]

	--- Create a default value from the result of `default_fn` in the source if
	-- the key doesn't have an associated value already
	-- @tparam function default_fn
	or_insert_with: (default_fn)=>
		source, key = @source, @key
		source[key] = default_fn! unless source[key]
		source[key]

	--- Apply a function `fn` to the entry, if it exists; should be called
	-- before returning the value, and can be chained
	-- @tparam function fn
	and_modify: (fn)=>
		source, key = @source, @key
		if source[key]
			source[key] = fn source[key]
		self

class Map
	new: (data={})=>
		@data = data

	--- Return an `Entry` to the potential value in the Map, which can be used
	-- to add a default value. If a default value isn't available or desired,
	-- instead use get()
	-- @param name
	entry: (key)=> Entry @data, key

	--- Returns `true` if a key exists within the table, or otherwise `false`
	contains_key: (key)=> @data[key] != nil

	--- Return an `Option<T>` to the potential value in the Map, which can be
	-- used to see if a value exists. If you want to insert a default value in
	-- the case it doesn't exist, use entry()
	get: (key)=> Option @data[key]

	--- Return a value expected to exist in the Map
	expect: (key, message)=>
		@get(key)\expect message

	--- Set a `value` in the Map to an associated `key`
	-- @param key
	-- @param value
	set: (key, value)=>
		@data[key] = value

	--- Remove a `key` from a Map
	remove: (key)=> @data[key] = nil

	--- Iterate over the Map
	iter: => pairs @data

return :Result, :Ok, :Err, :Option, :Entry, :Map


Remove call to removeChild from Engine for now

* This was pulled over directly from HaxePunk, but LovePunk engine
  doesn't extend anything.

require "LovePunk.Entity"
require "LovePunk.RenderMode"
require "LovePunk.Screen"
require "LovePunk.geometry.Rectangle"
require "LovePunk.utils.Draw"

export ^

---
-- The main class which manages the game loop
class Engine
	---
	-- Constructor. Defines startup information about your game.
	-- @param width      The width of your game.
	-- @param height     The height of your game.
	-- @param frameRate  The game framerate, in frames per second.
	-- @param fixed      If a fixed-framerate should be used.
	-- @param renderMode Overrides the default render mode for this target
	new: (width = 0, height = 0, frameRate = 0, fixed = false, renderMode) =>
		LP.bounds = Rectangle(0, 0, width, height)
		LP.assignedFrameRate = frameRate
		LP.fixed = fixed

		LP.engine = @
		LP.screen = Screen!
		LP.width = width
		LP.height = height

		LP.randomizeSeed! unless LP.randomSeed

		LP.entity = Entity!
		LP.time = love.timer.getTime!

		---
		-- Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10).
		@maxElapsed = 0.0333
		---
		-- The max amount of frames that can be skipped in fixed framerate mode.
		@maxFrameSkip = 0
		---
		-- If the game should stop updating/rendering.
		@paused = false
		---
		-- The amount of milliseconds between ticks in fixed framerate mode.
		@tickRate = 4

		@__frameList = {}
		@__frameLast = 0
		@__frameListSum = 0

		-- Do something with the Löve functions...
		love.draw = @\render
		love.update = @\update

	focusGained: =>
	focusLost: =>
	init: =>
	render: =>
		LP.resize LP.windowWidth, LP.windowHeight if LP.screen.needsResize

		-- timing stuff
		t = love.timer.getDelta!

		@__frameLast = t if @__frameLast == 0

		-- render loop
		if LP.renderMode == RenderMode.Buffer
			LP.screen.swap!
			LP.screen.refresh!

		Draw.resetTarget!

		LP.scene.render! if LP.scene.visible

		LP.screen.redraw! if LP.renderMode == RenderMode.Buffer

		-- more timing stuff
		t = love.timer.getTime!
		@__frameList[#@__frameList] = t - @__frameLast
		@__frameListSum += @__frameList[#@__frameList]
		if #@__frameList > 10
			@__frameListSum -= @__frameList[#@__frameList]
			@__frameList = [frame for frame in *@__frameList[2,]]

		LP.frameRate = 1000 / (@__frameListSum / #@__frameList)
		@__frameLast = t

	update: =>
		LP.scene.updateLists!
		checkScene! if LP\sceneIsNull!
		LP.tweener\updateTweens! if LP.tweener.active and LP.tweener\hasTween!

		if LP.scene.active
			LP.scene\updateTweens! if LP.scene\hasTween!
			LP.scene\update!

		LP.scene\updateLists false

	checkScene = =>
		return if not LP\sceneIsNull!

		if LP.scene != nil
			LP.scene\endScene!
			LP.scene\updateLists!
			LP.scene\clearTweens! if LP.scene.autoClear and LP.scene\hasTween!
			-- TODO
			-- removeChild(LP.scene.sprite) if LP.scene.sprite
			LP\swapScene!
			-- TODO
			-- addChild LP.scene.sprite
			LP.camera = LP.scene.camera
			LP.scene\begin!
			LP.scene\updateLists!

require "LovePunk.Entity"
require "LovePunk.RenderMode"
require "LovePunk.Screen"
require "LovePunk.geometry.Rectangle"
require "LovePunk.utils.Draw"

export ^

---
-- The main class which manages the game loop
class Engine
	---
	-- Constructor. Defines startup information about your game.
	-- @param width      The width of your game.
	-- @param height     The height of your game.
	-- @param frameRate  The game framerate, in frames per second.
	-- @param fixed      If a fixed-framerate should be used.
	-- @param renderMode Overrides the default render mode for this target
	new: (width = 0, height = 0, frameRate = 0, fixed = false, renderMode) =>
		LP.bounds = Rectangle(0, 0, width, height)
		LP.assignedFrameRate = frameRate
		LP.fixed = fixed

		LP.engine = @
		LP.screen = Screen!
		LP.width = width
		LP.height = height

		LP.randomizeSeed! unless LP.randomSeed

		LP.entity = Entity!
		LP.time = love.timer.getTime!

		---
		-- Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10).
		@maxElapsed = 0.0333
		---
		-- The max amount of frames that can be skipped in fixed framerate mode.
		@maxFrameSkip = 0
		---
		-- If the game should stop updating/rendering.
		@paused = false
		---
		-- The amount of milliseconds between ticks in fixed framerate mode.
		@tickRate = 4

		@__frameList = {}
		@__frameLast = 0
		@__frameListSum = 0

		-- Do something with the Löve functions...
		love.draw = @\render
		love.update = @\update

	focusGained: =>
	focusLost: =>
	init: =>
	render: =>
		LP.resize LP.windowWidth, LP.windowHeight if LP.screen.needsResize

		-- timing stuff
		t = love.timer.getDelta!

		@__frameLast = t if @__frameLast == 0

		-- render loop
		if LP.renderMode == RenderMode.Buffer
			LP.screen.swap!
			LP.screen.refresh!

		Draw.resetTarget!

		LP.scene.render! if LP.scene.visible

		LP.screen.redraw! if LP.renderMode == RenderMode.Buffer

		-- more timing stuff
		t = love.timer.getTime!
		@__frameList[#@__frameList] = t - @__frameLast
		@__frameListSum += @__frameList[#@__frameList]
		if #@__frameList > 10
			@__frameListSum -= @__frameList[#@__frameList]
			@__frameList = [frame for frame in *@__frameList[2,]]

		LP.frameRate = 1000 / (@__frameListSum / #@__frameList)
		@__frameLast = t

	update: =>
		LP.scene.updateLists!
		checkScene! if LP\sceneIsNull!
		LP.tweener\updateTweens! if LP.tweener.active and LP.tweener\hasTween!

		if LP.scene.active
			LP.scene\updateTweens! if LP.scene\hasTween!
			LP.scene\update!

		LP.scene\updateLists false

	checkScene = =>
		return if not LP\sceneIsNull!

		if LP.scene != nil
			LP.scene\endScene!
			LP.scene\updateLists!
			LP.scene\clearTweens! if LP.scene.autoClear and LP.scene\hasTween!
			removeChild(LP.scene.sprite) if LP.scene.sprite
			LP\swapScene!
			-- TODO
			-- addChild LP.scene.sprite
			LP.camera = LP.scene.camera
			LP.scene\begin!
			LP.scene\updateLists!


Split to functions

Split to functions

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

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

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

class PonySizeController
    @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'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)

    @NEXT_OBJ_ID = 0

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        PPM2.DebugPrint('Created new size controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

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

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

    DataChanges: (state) =>
        if state\GetKey() == 'PonySize'
            @ModifyScale()

    ResetViewOffset: =>
        @ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET_ORIGINAL) if @ent.SetViewOffset
        @ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK_ORIGINAL) if @ent.SetViewOffsetDucked

    ResetHulls: =>
        @ent\ResetHull() if @ent.ResetHull
        @ent\SetStepSize(@@STEP_SIZE) if @ent.SetStepSize
        @ent.__ppm2_modified_hull = false

    ResetJumpHeight: =>
        return if CLIENT
        return if not @ent.SetJumpPower
        return if not @ent.__ppm2_modified_jump
        @ent\SetJumpPower(@ent\GetJumpPower() / PPM2.PONY_JUMP_MODIFIER)
        @ent.__ppm2_modified_jump = false

    ResetDrawMatrix: =>
        return if SERVER
        mat = Matrix()
        mat\Scale(@@DEF_SCALE)
        @ent\EnableMatrix('RenderMultiply', mat)

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

        if USE_NEW_HULL\GetBool() or @ent.__ppm2_modified_hull
            @ResetHulls()
            @ResetJumpHeight()

        @ResetViewOffset()
        @ResetDrawMatrix()

    Remove: => @ResetScale()
    Reset: =>
        @ResetScale()
        @ModifyScale()

    GetPonySize: => @GetData()\GetPonySize()
    GetPonyScale: => @GetData()\GetPonySize()

    PlayerDeath: => @ResetScale()
    PlayerRespawn: =>
        @ResetScale()
        @ModifyScale()

    SlowUpdate: => @ModifyScale()

    ModifyHull: =>
        @ent.__ppm2_modified_hull = true
        @ent\SetHull(@@HULL_MINS * @GetPonySize(), @@HULL_MAXS * @GetPonySize()) if @ent.SetHull
        @ent\SetHullDuck(@@HULL_MINS * @GetPonySize(), @@HULL_MAXS_DUCK * @GetPonySize()) if @ent.SetHullDuck
        @ent\SetStepSize(@@STEP_SIZE * @GetPonySize()) if @ent.SetStepSize
    ModifyJumpHeight: =>
        return if CLIENT
        return if not @ent.SetJumpPower
        return if @ent.__ppm2_modified_jump
        @ent\SetJumpPower(@ent\GetJumpPower() * PPM2.PONY_JUMP_MODIFIER)
        @ent.__ppm2_modified_jump = true
    ModifyViewOffset: =>
        @ent\SetViewOffset(PPM2.PLAYER_VIEW_OFFSET * @GetPonySize()) if @ent.SetViewOffset
        @ent\SetViewOffsetDucked(PPM2.PLAYER_VIEW_OFFSET_DUCK * @GetPonySize()) if @ent.SetViewOffsetDucked
    ModifyDrawMatrix: =>
        return if SERVER
        mat = Matrix()
        mat\Scale(@@DEF_SCALE * @GetPonySize())
        @ent\EnableMatrix('RenderMultiply', mat)

    ModifyScale: =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        return if @ent.Alive and not @ent\Alive()
        size = @GetPonySize()

        if USE_NEW_HULL\GetBool()
            @ModifyHull()
            @ModifyJumpHeight()

        @ModifyViewOffset()
        @ModifyDrawMatrix()

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

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

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

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

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

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

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

class PonySizeController
    @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'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)

    @NEXT_OBJ_ID = 0

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        PPM2.DebugPrint('Created new size controller for ', @ent, ' as part of ', controller, '; internal ID is ', @objID)

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

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

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

    DataChanges: (state) =>
        if state\GetKey() == 'PonySize'
            @ModifyScale()

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

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

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

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

        if CLIENT
            mat = Matrix()
            mat\Scale(@@DEF_SCALE)
            @ent\EnableMatrix('RenderMultiply', mat)

    Remove: => @ResetScale()
    Reset: =>
        @ResetScale()
        @ModifyScale()

    GetPonySize: => @GetData()\GetPonySize()
    GetPonyScale: => @GetData()\GetPonySize()

    PlayerDeath: => @ResetScale()
    PlayerRespawn: =>
        @ResetScale()
        @ModifyScale()

    SlowUpdate: => @ModifyScale()

    ModifyScale: =>
        return if not IsValid(@ent)
        return if not @ent\IsPony()
        return if @ent.Alive and not @ent\Alive()
        size = @GetPonySize()

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

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

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

        if CLIENT
            mat = Matrix()
            mat\Scale(@@DEF_SCALE * size)
            @ent\EnableMatrix('RenderMultiply', mat)

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

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

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

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


Added LP lerp and colorLerp

Added LP lerp and colorLerp

require "LunoPunk.geometry.Point"
require "LunoPunk.geometry.Rectangle"
require "LunoPunk.geometry.Matrix"
require "LunoPunk.Scene"
require "LunoPunk.Tweener"

export ^

class lp
	-- Private
	BASELAYER = 10
	DEG = -180 / math.pi
	INT_MAX_VALUE = nil
	NUMBER_MAX_VALUE = nil
	RAD = math.pi / -180
	VERSION = "0.0.1"
	__scene = nil

	_gameTime: nil
	_renderTime: nil
	_systemTime: nil
	_updateTime: nil
	assignedFrameRate: nil
	blackColor: 0x00000000
	bounds: nil
	buffer: nil
	camera: Point!
	console: nil
	defaultFont: "default"
	elapsed: 0
	engine: nil
	entity: nil
	fixed: false
	focused: nil
	frameRate: 0
	log: nil
	matrix: Matrix!
	orientations: nil
	pan: nil
	point: Point!
	point2: Point!
	random: -> math.random!
	randomSeed: nil
	rate: 0
	rect: Rectangle!
	renderMode = (value) ->
		return __renderMode if value == nil or __renderMode == value
		__renderMode = value

		-- recreate screen for buffer rendering
		if LP.screen == nil
			LP.screen = Screen!
		else
			LP.screen.init!

		value

	scene: Scene!
	screen: nil
	sprite: nil
	tweener: Tweener!
	volume: 1
	watch: nil
	world: nil
	zero: Point!

	-- Window/resolution related fields
	fullscreen: false
	halfHeight: -> LP.height/2
	halfWidth: -> LP.width/2
	height: 0
	width: 0
	windowHeight: 0
	windowWidth: 0

	alarm: (delay, complete, typeweenType, tweener) ->
	anchorTo: (object, anchor, distance) ->
	angle: (x1, y1, x2, y2) ->
	angleDifference: (angle1, angle2) ->
	angleXY: (object, angle, length, x, y) ->

	-- Approaches the value towards the target, by the specified amount, without overshooting the target.
	-- @param	value	The starting value.
	-- @param	target	The target that you want value to approach.
	-- @param	amount	How much you want the value to approach target by.
	-- @return	The new value.
	approach: (value, target, amount) ->
		if value < target - amount
			value + amount
		else if value > target + amount
			value - amount
		else
			target

	choose: (objs) ->
	clamp: (value, min, max) -> if value < min
			min
		else if value > max
			max
		else
			value
	clampInRect: (object, x, y, width, height, padding) ->
	clear: (t) -> while #t > 0 do table.remove t
	consoleEnabled: ->
	createBitmap: (width, height, transparent, color) ->
	distance: (x1, y1, x2, y2) ->
	istanceRectPoint: (px, py, rx, ry, rw, rh) ->
	distanceRects: (x1, y1, w1, h1, x2, y2, w2, h2) ->
	distanceSquared: (x1, y1, xprint2, y2) ->
	frames: (from_frame, to_frame, skip) ->
	getImage: (source) -> love.graphics.newImage source
	getColorHSV: (h, s, v) ->
	getColorHue: (color) ->

	getColorSaturation: (color) ->
	getColorValue: (color) ->

	convertColor: (color) -> { lp.getRed(color), lp.getGreen(color), lp.getBlue(color) }
	getColorRGB: (r, g, b) -> r * 0x10000 + g * 0x100 + b
	getRed: (color) -> math.floor color / 0x10000
	getGreen: (color) -> math.floor color / 0x100 % 0x100
	getBlue: (color) -> math.floor color % 0x100

	indexOf: (tbl, item) -> for i, v in ipairs tbl do return i if item == v

	-- read-only
	DEG: -> DEG

	-- read-only
	NUMBER_MAX_VALUE: -> NUMBER_MAX_VALUE

	-- read-only
	RAD: -> RAD

	-- read-only
	VERSION: -> VERSION

	-- read-only
	BASELAYER: -> BASELAYER

	sceneIsNull: => @scene != @__scene or @__scene == nil

	-- Linear interpolation between two values.
	-- @param	a		First value.
	-- @param	b		Second value.
	-- @param	t		Interpolation factor.
	-- @return	When t=0, returns a. When t=1, returns b. When t=0.5, will return halfway between a and b. Etc.
	lerp: (a, b, t = 1) -> a + (b - a) * t

	-- Linear interpolation between two colors.
	-- @param	fromColor		First color.
	-- @param	toColor			Second color.
	-- @param	t				Interpolation value. Clamped to the range [0, 1].
	-- return	RGB component-interpolated color value.
	colorLerp: (fromColor, toColor, t = 1) ->
		if t <= 0
			fromColor
		else if t >= 1
			toColor
		else
			f = LP.convertColor fromColor
			d = LP.convertColor toColor
			d.r -= f.r
			d.g -= f.g
			d.b -= f.b
			f.r = d.r * t
			f.g = d.g * t
			f.b = d.b * t
			LP.getColorRGB unpack f

	next: (current, options, loop) ->
	prev: (current, options, loop) ->

	rand: (amount) -> math.random(amount)
	randomizeSeed: -> math.randomseed os.time()

	resetCamera: -> camera.x, camera.y = 0, 0

	resize: (width, height) ->
		-- resize scene to scale
		width /= LP.screen\fullScaleX!
		height = height / LP.screen\fullScaleY!
		LP.width, LP.height = width, height
		LP.bounds.width, LP.bounds.height = width, height
		LP.screen\resize!

	rotateAround: (object, anchor, angle, relative) ->
	round: (num, precision) ->
	scale: (value, min, max, min2, max2) ->
	scaleClamp: (value, min, max, min2, max2) ->
	setCamera: (x, y) -> LP.camera.x, LP.camera.y = x, y
	shuffle: (a) ->

	-- Finds the sign of the provided value.
	-- @param	value		The Float to evaluate.
	-- @return	1 if value > 0, -1 if value < 0, and 0 when value == 0.
	sign: (value) -> if value < 0 then -1 else if value == 0 then 0 else 1

	stepTowards: (object, x, y, distance) ->
	swap: (current, a, b) ->
	swapScene: => @__scene = @scene
	timeFlag: ->
	tween: (object, values, duration, options) ->

	-- Used to determine the Löve version that's being used
	__love: (version) -> version == string.format "%d.%d", love._version_major, love._version_minor

LP = lp!

-- Don't allow unknown Löve versions
if not (LP.__love("0.8") or LP.__love("0.9"))
	assert false, "Unsupported Löve version"

require "LunoPunk.geometry.Point"
require "LunoPunk.geometry.Rectangle"
require "LunoPunk.geometry.Matrix"
require "LunoPunk.Scene"
require "LunoPunk.Tweener"

export ^

class lp
	-- Private
	BASELAYER = 10
	DEG = -180 / math.pi
	INT_MAX_VALUE = nil
	NUMBER_MAX_VALUE = nil
	RAD = math.pi / -180
	VERSION = "0.0.1"
	__scene = nil

	_gameTime: nil
	_renderTime: nil
	_systemTime: nil
	_updateTime: nil
	assignedFrameRate: nil
	blackColor: 0x00000000
	bounds: nil
	buffer: nil
	camera: Point!
	console: nil
	defaultFont: "default"
	elapsed: 0
	engine: nil
	entity: nil
	fixed: false
	focused: nil
	frameRate: 0
	log: nil
	matrix: Matrix!
	orientations: nil
	pan: nil
	point: Point!
	point2: Point!
	random: -> math.random!
	randomSeed: nil
	rate: 0
	rect: Rectangle!
	renderMode = (value) ->
		return __renderMode if value == nil or __renderMode == value
		__renderMode = value

		-- recreate screen for buffer rendering
		if LP.screen == nil
			LP.screen = Screen!
		else
			LP.screen.init!

		value

	scene: Scene!
	screen: nil
	sprite: nil
	tweener: Tweener!
	volume: 1
	watch: nil
	world: nil
	zero: Point!

	-- Window/resolution related fields
	fullscreen: false
	halfHeight: -> LP.height/2
	halfWidth: -> LP.width/2
	height: 0
	width: 0
	windowHeight: 0
	windowWidth: 0

	alarm: (delay, complete, typeweenType, tweener) ->
	anchorTo: (object, anchor, distance) ->
	angle: (x1, y1, x2, y2) ->
	angleDifference: (angle1, angle2) ->
	angleXY: (object, angle, length, x, y) ->

	-- Approaches the value towards the target, by the specified amount, without overshooting the target.
	-- @param	value	The starting value.
	-- @param	target	The target that you want value to approach.
	-- @param	amount	How much you want the value to approach target by.
	-- @return	The new value.
	approach: (value, target, amount) ->
		if value < target - amount
			value + amount
		else if value > target + amount
			value - amount
		else
			target

	choose: (objs) ->
	clamp: (value, min, max) -> if value < min
			min
		else if value > max
			max
		else
			value
	clampInRect: (object, x, y, width, height, padding) ->
	clear: (t) -> while #t > 0 do table.remove t
	colorLerp: (fromColor, toColor, t) ->
	consoleEnabled: ->
	createBitmap: (width, height, transparent, color) ->
	distance: (x1, y1, x2, y2) ->
	istanceRectPoint: (px, py, rx, ry, rw, rh) ->
	distanceRects: (x1, y1, w1, h1, x2, y2, w2, h2) ->
	distanceSquared: (x1, y1, xprint2, y2) ->
	frames: (from_frame, to_frame, skip) ->
	getImage: (source) -> love.graphics.newImage source
	getColorHSV: (h, s, v) ->
	getColorHue: (color) ->

	getColorSaturation: (color) ->
	getColorValue: (color) ->

	convertColor: (color) -> { lp.getRed(color), lp.getGreen(color), lp.getBlue(color) }
	getColorRGB: (r, g, b) -> r * 0x10000 + g * 0x100 + b
	getRed: (color) -> math.floor color / 0x10000
	getGreen: (color) -> math.floor color / 0x100 % 0x100
	getBlue: (color) -> math.floor color % 0x100

	indexOf: (tbl, item) -> for i, v in ipairs tbl do return i if item == v

	-- read-only
	DEG: -> DEG

	-- read-only
	NUMBER_MAX_VALUE: -> NUMBER_MAX_VALUE

	-- read-only
	RAD: -> RAD

	-- read-only
	VERSION: -> VERSION

	-- read-only
	BASELAYER: -> BASELAYER

	sceneIsNull: => @scene != @__scene or @__scene == nil

	lerp: (a, b, t) ->
	next: (current, options, loop) ->
	prev: (current, options, loop) ->

	rand: (amount) -> math.random(amount)
	randomizeSeed: -> math.randomseed os.time()

	resetCamera: -> camera.x, camera.y = 0, 0

	resize: (width, height) ->
		-- resize scene to scale
		width /= LP.screen\fullScaleX!
		height = height / LP.screen\fullScaleY!
		LP.width, LP.height = width, height
		LP.bounds.width, LP.bounds.height = width, height
		LP.screen\resize!

	rotateAround: (object, anchor, angle, relative) ->
	round: (num, precision) ->
	scale: (value, min, max, min2, max2) ->
	scaleClamp: (value, min, max, min2, max2) ->
	setCamera: (x, y) -> LP.camera.x, LP.camera.y = x, y
	shuffle: (a) ->

	-- Finds the sign of the provided value.
	-- @param	value		The Float to evaluate.
	-- @return	1 if value > 0, -1 if value < 0, and 0 when value == 0.
	sign: (value) -> if value < 0 then -1 else if value == 0 then 0 else 1

	stepTowards: (object, x, y, distance) ->
	swap: (current, a, b) ->
	swapScene: => @__scene = @scene
	timeFlag: ->
	tween: (object, values, duration, options) ->

	-- Used to determine the Löve version that's being used
	__love: (version) -> version == string.format "%d.%d", love._version_major, love._version_minor

LP = lp!

-- Don't allow unknown Löve versions
if not (LP.__love("0.8") or LP.__love("0.9"))
	assert false, "Unsupported Löve version"


netutils: add !whereis

netutils: add !whereis

cqueues = require 'cqueues'
dns = require'cqueues.dns'
packet = require'cqueues.dns.packet'
record = require'cqueues.dns.record'
errno = require'cqueues.errno'
socket = require'cqueues.socket'
context = require'openssl.ssl.context'
http_tls = require'http.tls'
pkey = require'openssl.pkey'
auxlib = require"openssl.auxlib"
util = require'util'
{:simplehttp, :bold, :split, :green, :red, :simplehttp, :json, :urlEncode} = require'util'

cache = {}

HasValue = (table, value) ->
  if(type(table) ~= 'table') then return
  for _, v in next, table
    if(v == value) then return true

Good = ->
  "#{green '🔒'} TLS #{green 'ok'}"

Bad = ->
  "#{red '🔓 '} #{red 'Bad'} TLS"

Query = (host, types='AAAA,A', timeout=15) ->
  out = {}

  for type in *split(types, ',')
    answer = dns.query(host, type, 'IN', timeout)

    for rec in answer\grep{section:packet.section.ANSWER}
      out[#out+1] = tostring(rec)

  return out

{
  PRIVMSG: {
    '^%pttls (.*)$': (source, destination, arg) =>
      host = arg\match'^%s*(.-)[:%s].*$' or arg
      port = arg\match'^.*[: ](.*)%s*$' or 443
      port = tonumber(port) or 0
      if port <= 0 or port > 65535
        return reply "Think you're clever?"
      hosts = Query(host)
      if #hosts == 0
        -- assume IP
        hosts = {host}
      found = false
      for ip in *hosts
        sock = socket.connect{host:ip, :port, verify:false, tls_sendname:host}
        ok, sock, err = pcall -> sock\connect 5
        if not ok
          return say "Error connecting to #{ip}:#{port} : #{sock}"
        if ip\match':'
          ip = '[' .. ip .. ']'
        if not sock
          reply "Error #{err} connecting to #{ip}:#{port} : #{errno.strerror err}"
        else
          ctx = http_tls.new_client_context()
          --ctx = context.new('SSL3', false)
          --ctx\setOptions(context.OP_NO_COMPRESSION+contextopenssl_ctx.OP_SINGLE_ECDH_USE)
          --ctx\setEphemeralKey(pkey.new{ type = "EC", curve = "prime256v1" }
          ok, tls, err = pcall -> sock\starttls 5
          if not ok
            return say "TLS negotiation error : #{tls}"
          if tls
            ssl = tls\checktls!
            if sock
              sock\close!
            cipher = ssl\getCipherInfo!
            if http_tls.banned_ciphers[cipher.name]
              return say "Banned cipher: #{cipher.name} when negotiating TLS #{ip}:#{port}"
            cert = ssl\getPeerCertificate!
            before, after = cert\getLifetime!
            cn = tostring cert\getSubject!
            san = cert\getSubjectAlt!
            validhosts = {cn\match'CN=(.*)'}
            for type, sanhost in auxlib.pairs(san)
              table.insert(validhosts, sanhost)
            -- cheat and parse text
            text = cert\text!
            @Log 'debug', text
            expires = text\match('Not After : (.-)%s*Sub')
            verdict = Good!
            reason = ''
            unless HasValue(validhosts, host)
              verdict = Bad!
              reason = "Hostname #{host} not in list #{table.concat validhosts, ', '}"
            if after < os.time!
              verdict = Bad!
              reason ..= " #{red 'Expired'} cert!"

            say "#{verdict} #{reason} #{ip}:#{port}. Expires #{expires} Cipher: #{cipher.bits} bits #{cipher.name}."
          else
            reply "Error #{err} when negotiating TLS #{ip}:#{port} : #{err}"
          found = true
        if found
          break
    '^%pconnect (.*)[:%s]([0-9]+)$': (source, destination, host, port) =>
      port = tonumber port
      if port < 0 or port > 65535
        return reply "Think you're clever?"
      hosts = Query(host)
      if #hosts == 0
        -- assume IP
        hosts = {host}
      for ip in *hosts
        sock = socket.connect(ip, port)
        ok, sock, err = pcall -> sock\connect 5
        if not ok
          return say "Error connecting to #{ip}:#{port} : #{sock}"
        if ok and sock
          sock\close!
        if ip\match':'
          ip = '[' .. ip .. ']'
        if not sock
          reply "Error #{err} connecting to #{ip}:#{port} : #{errno.strerror err}"
        else
          say "Yup, could connect to #{ip}:#{port}"
          break
    '^%pdns (.*)$': (source, destination, arg) =>
      args = split arg, ' '
      argc = #args
      host = args[1]
      types = 'AAAA,A'
      if argc == 2
        types = args[1]
        host = args[2]

      out = Query(host, types)

      if #out == 0
        say "No answers"
      say table.concat(out, ' ')

    '^%pipv6 stats$': (source, destination) =>
      @Send "WHO #{destination}"
    '^%pipv6 stats (.*)$': (source, destination, dest) =>
      @Send "WHO #{dest}"
    '^%phttp (.*)$': (source, destination, arg) =>
      args = split arg, ' '
      argc = #args
      url = args[1]
      field = ''
      if argc == 2
        field = args[1]
        url = args[2]
      unless url\match '^http'
        url = 'http://' .. url
      data, uri, response = simplehttp(url)
      unless data
        reply 'Error: '..tostring(uri)
      unless response
        reply data..uri
      status = response.status_code
      headers = {}
      if argc == 1
        for k, v in pairs response.headers
          headers[#headers+1] = "#{k}:#{v}"
        table.sort(headers)
        headers = table.concat(headers, ', ')
        say "HTTP v#{response.version} #{headers}"
      elseif argc == 2
        field = field\lower!
        value = response.headers[field]
        if field == 'version'
          value = response.version

        say "#{field} : #{value or 'Header not found'}"
    '^%pwhereis (.*)$': (source, destination, arg) =>
      -- Download from http://dev.maxmind.com/geoip/geoip2/geolite2/
      geodb = (require "mmdb").open("GeoLite2-City.mmdb")
      -- if space, use first
      if arg\match'(.-) .*'
        arg = arg\match'(.-) .*'
      p = (obj) ->
        out = {}
        if obj.city and obj.city.names and obj.city.names.en
          out[#out+1] = obj.city.names.en
        if obj.subdivisions and obj.subdivisions[1] and obj.subdivisions[1].names and obj.subdivisions[1].names.en
          out[#out+1] = obj.subdivisions[1].names.en
        if obj.country and obj.country.names and obj.country.names.en
          out[#out+1] = obj.country.names.en
        say table.concat(out, ', ')
      if arg\match '%d+%.%d+%.%d+%.%d+'
        obj = geodb\search_ipv4 arg
        p obj
      elseif arg\match ':'
        obj = geodb\search_ipv6 arg
        p obj
      else
        reply 'Needs valid IPv4'

  }
  ['352']: {
    (source, destination, input) =>
      --:efnet.port80.se 352 xt #jensaskaret xt 2a02:cc41:100f::1 efnet.port80.se xt H :0 Tor H.
      unless cache[destination]
        cache[destination] = {}

      nick = input.nick
      host = input.host
      --if @channels[destination]
      cache[destination][nick] = host

  }
  ['315']: {
    (source, destination, input) =>
      -- nondeterministic order.. hey ho here we go
      -- TODO use condition?
      cqueues.sleep(2)
      ipv4 = 0
      ipv6 = 0
      cloak = 0
      for nick, host in pairs(cache[destination])
        if host\match ':'
          ipv6 += 1
        elseif host\match '/' -- freenode cloak
          cloak += 1
        elseif host\match '%d+%.%d+%.%d+%.%d+'
          ipv4 += 1
        else
          timeout = 360
          ok, packet, err = pcall(dns.query, host, 'AAAA', 'IN', timeout)
          if not ok
            print 'pack', ok, packet, err
            print(debug.traceback())
          elseif not packet
            print 'no packet for host', host
            ipv4 += 1
          elseif packet\count() >= 2 -- 1 for question and atleast 1 for answer
            ipv6 = ipv6 + 1
          else
            ipv4 = ipv4 + 1
      -- Empty cache
      cache[destination] = nil
      percent = math.floor(100*ipv6/(ipv4+ipv6+cloak))
      out = "IP stats: #{bold percent}% IPv6. #{ipv6} IPv6 clients. #{ipv4} IPv4 clients. "
      if cloak > 0
        out = out .. "#{cloak} cloaked hosts."
      say out
  }
}

cqueues = require 'cqueues'
dns = require'cqueues.dns'
packet = require'cqueues.dns.packet'
record = require'cqueues.dns.record'
errno = require'cqueues.errno'
socket = require'cqueues.socket'
context = require'openssl.ssl.context'
http_tls = require'http.tls'
pkey = require'openssl.pkey'
auxlib = require"openssl.auxlib"
util = require'util'
{:simplehttp, :bold, :split, :green, :red, :simplehttp, :json, :urlEncode} = require'util'

cache = {}

HasValue = (table, value) ->
  if(type(table) ~= 'table') then return
  for _, v in next, table
    if(v == value) then return true

Good = ->
  "#{green '🔒'} TLS #{green 'ok'}"

Bad = ->
  "#{red '🔓 '} #{red 'Bad'} TLS"

Query = (host, types='AAAA,A', timeout=15) ->
  out = {}

  for type in *split(types, ',')
    answer = dns.query(host, type, 'IN', timeout)

    for rec in answer\grep{section:packet.section.ANSWER}
      out[#out+1] = tostring(rec)

  return out

{
  PRIVMSG: {
    '^%pttls (.*)$': (source, destination, arg) =>
      host = arg\match'^%s*(.-)[:%s].*$' or arg
      port = arg\match'^.*[: ](.*)%s*$' or 443
      port = tonumber(port) or 0
      if port <= 0 or port > 65535
        return reply "Think you're clever?"
      hosts = Query(host)
      if #hosts == 0
        -- assume IP
        hosts = {host}
      found = false
      for ip in *hosts
        sock = socket.connect{host:ip, :port, verify:false, tls_sendname:host}
        ok, sock, err = pcall -> sock\connect 5
        if not ok
          return say "Error connecting to #{ip}:#{port} : #{sock}"
        if ip\match':'
          ip = '[' .. ip .. ']'
        if not sock
          reply "Error #{err} connecting to #{ip}:#{port} : #{errno.strerror err}"
        else
          ctx = http_tls.new_client_context()
          --ctx = context.new('SSL3', false)
          --ctx\setOptions(context.OP_NO_COMPRESSION+contextopenssl_ctx.OP_SINGLE_ECDH_USE)
          --ctx\setEphemeralKey(pkey.new{ type = "EC", curve = "prime256v1" }
          ok, tls, err = pcall -> sock\starttls 5
          if not ok
            return say "TLS negotiation error : #{tls}"
          if tls
            ssl = tls\checktls!
            if sock
              sock\close!
            cipher = ssl\getCipherInfo!
            if http_tls.banned_ciphers[cipher.name]
              return say "Banned cipher: #{cipher.name} when negotiating TLS #{ip}:#{port}"
            cert = ssl\getPeerCertificate!
            before, after = cert\getLifetime!
            cn = tostring cert\getSubject!
            san = cert\getSubjectAlt!
            validhosts = {cn\match'CN=(.*)'}
            for type, sanhost in auxlib.pairs(san)
              table.insert(validhosts, sanhost)
            -- cheat and parse text
            text = cert\text!
            @Log 'debug', text
            expires = text\match('Not After : (.-)%s*Sub')
            verdict = Good!
            reason = ''
            unless HasValue(validhosts, host)
              verdict = Bad!
              reason = "Hostname #{host} not in list #{table.concat validhosts, ', '}"
            if after < os.time!
              verdict = Bad!
              reason ..= " #{red 'Expired'} cert!"

            say "#{verdict} #{reason} #{ip}:#{port}. Expires #{expires} Cipher: #{cipher.bits} bits #{cipher.name}."
          else
            reply "Error #{err} when negotiating TLS #{ip}:#{port} : #{err}"
          found = true
        if found
          break
    '^%pconnect (.*)[:%s]([0-9]+)$': (source, destination, host, port) =>
      port = tonumber port
      if port < 0 or port > 65535
        return reply "Think you're clever?"
      hosts = Query(host)
      if #hosts == 0
        -- assume IP
        hosts = {host}
      for ip in *hosts
        sock = socket.connect(ip, port)
        ok, sock, err = pcall -> sock\connect 5
        if not ok
          return say "Error connecting to #{ip}:#{port} : #{sock}"
        if ok and sock
          sock\close!
        if ip\match':'
          ip = '[' .. ip .. ']'
        if not sock
          reply "Error #{err} connecting to #{ip}:#{port} : #{errno.strerror err}"
        else
          say "Yup, could connect to #{ip}:#{port}"
          break
    '^%pdns (.*)$': (source, destination, arg) =>
      args = split arg, ' '
      argc = #args
      host = args[1]
      types = 'AAAA,A'
      if argc == 2
        types = args[1]
        host = args[2]

      out = Query(host, types)

      if #out == 0
        say "No answers"
      say table.concat(out, ' ')

    '^%pipv6 stats$': (source, destination) =>
      @Send "WHO #{destination}"
    '^%pipv6 stats (.*)$': (source, destination, dest) =>
      @Send "WHO #{dest}"
    '^%phttp (.*)$': (source, destination, arg) =>
      args = split arg, ' '
      argc = #args
      url = args[1]
      field = ''
      if argc == 2
        field = args[1]
        url = args[2]
      unless url\match '^http'
        url = 'http://' .. url
      data, uri, response = simplehttp(url)
      unless data
        reply 'Error: '..tostring(uri)
      unless response
        reply data..uri
      status = response.status_code
      headers = {}
      if argc == 1
        for k, v in pairs response.headers
          headers[#headers+1] = "#{k}:#{v}"
        table.sort(headers)
        headers = table.concat(headers, ', ')
        say "HTTP v#{response.version} #{headers}"
      elseif argc == 2
        field = field\lower!
        value = response.headers[field]
        if field == 'version'
          value = response.version

        say "#{field} : #{value or 'Header not found'}"

  }
  ['352']: {
    (source, destination, input) =>
      --:efnet.port80.se 352 xt #jensaskaret xt 2a02:cc41:100f::1 efnet.port80.se xt H :0 Tor H.
      unless cache[destination]
        cache[destination] = {}

      nick = input.nick
      host = input.host
      --if @channels[destination]
      cache[destination][nick] = host

  }
  ['315']: {
    (source, destination, input) =>
      -- nondeterministic order.. hey ho here we go
      -- TODO use condition?
      cqueues.sleep(2)
      ipv4 = 0
      ipv6 = 0
      cloak = 0
      for nick, host in pairs(cache[destination])
        if host\match ':'
          ipv6 += 1
        elseif host\match '/' -- freenode cloak
          cloak += 1
        elseif host\match '%d+%.%d+%.%d+%.%d+'
          ipv4 += 1
        else
          timeout = 360
          ok, packet, err = pcall(dns.query, host, 'AAAA', 'IN', timeout)
          if not ok
            print 'pack', ok, packet, err
            print(debug.traceback())
          elseif not packet
            print 'no packet for host', host
            ipv4 += 1
          elseif packet\count() >= 2 -- 1 for question and atleast 1 for answer
            ipv6 = ipv6 + 1
          else
            ipv4 = ipv4 + 1
      -- Empty cache
      cache[destination] = nil
      percent = math.floor(100*ipv6/(ipv4+ipv6+cloak))
      out = "IP stats: #{bold percent}% IPv6. #{ipv6} IPv6 clients. #{ipv4} IPv4 clients. "
      if cloak > 0
        out = out .. "#{cloak} cloaked hosts."
      say out
  }
}


googleapi: add image search

googleapi: add image search

Former-commit-id: a185ef3839229efc884bcb1f563c35d6dcaab62f [formerly d90f4a4dd3b9f8fb21796189a249e7320ab7cc28]
Former-commit-id: 3c9daf9622c710428d8e3d07af0e3e27ec1eb331

-- ported from luabot's google module. Thanks!
html2unicode = require'html'

imagesearch = (source, destination, term, num=1) =>
    data, err = ivar2.util.simplehttp 'http://ajax.googleapis.com/ajax/services/search/images?safe=off&v=1.0&q='..ivar2.util.urlEncode(term), (data) ->
      res = ivar2.util.json.decode(data)
      results = res.responseData.results

      total = #results

      if num >= total or num < 0
          say "Couldn't find anything matching \002#{term}\002."
          return

      url = results[num]['unescapedUrl']
      say "(#{num} of #{total}) #{url}"

PRIVMSG:
  '^%pimage (.+)': imagesearch
  '^%pimage (.+) ([0-9]+)': imagesearch
  '^%pg (.+)': (source, destination, query) =>
    data, err = ivar2.util.simplehttp 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q='..ivar2.util.urlEncode(query), (data) ->

      if query == 'google'
        return reply "wow. That's really clever."

      unless data
        return reply "no response"

      if data\sub(1, 1) ~= '{' then
        return false, 'invalid reply?'

      resp = ivar2.util.json.decode(data)
      unless resp.responseData
        return reply 'no response data'

      res = resp.responseData.results
      if res[1]
        say(html2unicode(res[1].titleNoFormatting)..': '..res[1].unescapedUrl)
      else
        return reply 'no results'

-- ported from luabot's google module. Thanks!
html2unicode = require'html'

PRIVMSG:
  '^%pg (.+)': (source, destination, query) =>
    data, err = ivar2.util.simplehttp 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q='..ivar2.util.urlEncode(query), (data) ->

      if query == 'google'
        return reply "wow. That's really clever."

      unless data
        return reply "no response"

      if data\sub(1, 1) ~= '{' then
        return false, 'invalid reply?'

      resp = ivar2.util.json.decode(data)
      unless resp.responseData
        return reply 'no response data'

      res = resp.responseData.results
      if res[1]
        say(html2unicode(res[1].titleNoFormatting)..': '..res[1].unescapedUrl)
      else
        return reply 'no results'


Fixed hands texturing

Fixed hands texturing

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

TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')

timer.Create 'PPM2.ModelChecks', 1, 0, ->
    for task in *PPM2.NetworkedPonyData.RenderTasks
        ent = task.ent
        if IsValid(ent)
            ent.__cachedIsPony = ent\IsPony()

    for ply in *player.GetAll()
        ply.__cachedIsPony = ply\IsPony()

        if ply.__cachedIsPony
            for wep in *ply\GetWeapons()
                continue if not wep
                wep\SetNoDraw(true)
                wep.__ppm2_weapon_hit = true
        else
            for wep in *ply\GetWeapons()
                continue if not wep
                continue if not wep.__ppm2_weapon_hit
                wep\SetNoDraw(false)
                ply.__ppm2_weapon_hit = false

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

PPM2.PostDrawOpaqueRenderables = (a, b) ->
    return if a or b

    for task in *PPM2.NetworkedPonyData.RenderTasks
        ent = task.ent
        if IsValid(ent)
            if ent.__cachedIsPony
                if not TASK_RENDER_TYPE\GetBool()
                    ent\SetNoDraw(true)
                    ent.__ppm2_task_hit = true
                    renderController = task\GetRenderController()
                    renderController\PreDraw(ent)
                    ent\DrawModel()
                    renderController\PostDraw(ent)
                else
                    if ent.__ppm2_task_hit
                        ent.__ppm2_task_hit = false
                        ent\SetNoDraw(false)
                    renderController = task\GetRenderController()
                    renderController\PreDraw(ent)
                    renderController\PostDraw(ent)
            else
                if ent.__ppm2_task_hit
                    ent.__ppm2_task_hit = false
                    ent\SetNoDraw(false)
                    task\Reset()

    if not ENABLE_NEW_RAGDOLLS\GetBool()
        for ply in *player.GetAll()
            alive = ply\Alive()
            ply.__ppm2_last_dead = RealTime() + 2 if not alive
            if ply.__cachedIsPony
                if ply\GetPonyData() and not alive
                    data = ply\GetPonyData()
                    rag = ply\GetRagdollEntity()
                    if IsValid(rag)
                        renderController = data\GetRenderController()
                        data\DoRagdollMerge()
                        if renderController
                            renderController\PreDraw(rag)
                            rag\DrawModel()
                            renderController\PostDraw(rag)

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController
    data\GetWeightController()\UpdateWeight() if data\GetOverrideBones() and data\GetWeightController()
PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, 2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, 2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, 2
hook.Add 'RenderScreenspaceEffects', 'PPM2.RenderScreenspaceEffects', ->
    self = LocalPlayer()

    if @__cachedIsPony and @GetPonyData() and @Alive()
        @GetPonyData()\GetRenderController()\DrawLegs()

SHOULD_DRAW_VIEWMODEL = CreateConVar('cl_ppm2_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
    return unless IsValid(arms)
    return unless ply.__cachedIsPony
    return unless ply\Alive()
    arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
    wep = ply\GetActiveWeapon()
    if IsValid(wep) and wep.UseHands == false
        return true -- Dafuck?
    return if arms\GetModel() ~= 'models/cppm/pony_arms.mdl'
    data = ply\GetPonyData()
    return unless data
    status = data\GetRenderController()\PreDrawArms(arms)
    return status if status ~= nil
    arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return unless IsValid(arms)
    return unless arms.__ppm2_draw
    data = ply\GetPonyData()
    return unless data
    data\GetRenderController()\PostDrawArms(arms)
    arms.__ppm2_draw = false

PlayerRespawn = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0
net.Receive 'PPM2.RequestPonyData', ->
    return if lastDataReceived > RealTime()
    lastDataReceived = RealTime() + 10
    RunConsoleCommand('ppm2_reload')

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
    net.Start('PPM2.Require')
    net.SendToServer()
    PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
    return if lastDataSend > RealTime()
    lastDataSend = RealTime() + 10
    instance = PPM2.GetMainData()
    newData = instance\CreateNetworkObject()
    newData\Create()
    instance\SetNetworkData(newData)
    PPM2.Message 'Sending pony data to server...'

hook.Add 'KeyPress', 'PPM2.RequireData', ->
    hook.Remove 'KeyPress', 'PPM2.RequireData'
    RunConsoleCommand('ppm2_reload')
    timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

hook.Add 'HUDPaint', 'PPM2.EditorStatus', ->
    lply = LocalPlayer()
    lpos = lply\EyePos()
    for ply in *player.GetAll()
        if ply ~= lply
            if ply\GetNWBool('PPM2.InEditor')
                pos = ply\EyePos()
                dist = pos\Distance(lpos)
                if dist < 250
                    pos.z += 10
                    alpha = math.Clamp(1 - dist / 250, 0.1, 1)
                    {:x, :y} = pos\ToScreen()
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x + 1, y + 1, PPM_HINT_COLOR_SECOND, TEXT_ALIGN_CENTER)
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x, y, PPM_HINT_COLOR_FIRST, TEXT_ALIGN_CENTER)

concommand.Add 'ppm2_cleanup', ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()
    PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()

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

TASK_RENDER_TYPE = CreateConVar('ppm2_task_render_type', '1', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Task rendering type (e.g. pony ragdolls and NPCs). 1 - better render; less conflicts; more FPS. 0 - "old-style" render; possible conflicts;')

timer.Create 'PPM2.ModelChecks', 1, 0, ->
    for task in *PPM2.NetworkedPonyData.RenderTasks
        ent = task.ent
        if IsValid(ent)
            ent.__cachedIsPony = ent\IsPony()

    for ply in *player.GetAll()
        ply.__cachedIsPony = ply\IsPony()

        if ply.__cachedIsPony
            for wep in *ply\GetWeapons()
                continue if not wep
                wep\SetNoDraw(true)
                wep.__ppm2_weapon_hit = true
        else
            for wep in *ply\GetWeapons()
                continue if not wep
                continue if not wep.__ppm2_weapon_hit
                wep\SetNoDraw(false)
                ply.__ppm2_weapon_hit = false

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

PPM2.PostDrawOpaqueRenderables = (a, b) ->
    return if a or b

    for task in *PPM2.NetworkedPonyData.RenderTasks
        ent = task.ent
        if IsValid(ent)
            if ent.__cachedIsPony
                if not TASK_RENDER_TYPE\GetBool()
                    ent\SetNoDraw(true)
                    ent.__ppm2_task_hit = true
                    renderController = task\GetRenderController()
                    renderController\PreDraw(ent)
                    ent\DrawModel()
                    renderController\PostDraw(ent)
                else
                    if ent.__ppm2_task_hit
                        ent.__ppm2_task_hit = false
                        ent\SetNoDraw(false)
                    renderController = task\GetRenderController()
                    renderController\PreDraw(ent)
                    renderController\PostDraw(ent)
            else
                if ent.__ppm2_task_hit
                    ent.__ppm2_task_hit = false
                    ent\SetNoDraw(false)
                    task\Reset()

    if not ENABLE_NEW_RAGDOLLS\GetBool()
        for ply in *player.GetAll()
            alive = ply\Alive()
            ply.__ppm2_last_dead = RealTime() + 2 if not alive
            if ply.__cachedIsPony
                if ply\GetPonyData() and not alive
                    data = ply\GetPonyData()
                    rag = ply\GetRagdollEntity()
                    if IsValid(rag)
                        renderController = data\GetRenderController()
                        data\DoRagdollMerge()
                        if renderController
                            renderController\PreDraw(rag)
                            rag\DrawModel()
                            renderController\PostDraw(rag)

PPM2.PrePlayerDraw = =>
    return unless @GetPonyData()
    @__cachedIsPony = @IsPony()
    return if not @__cachedIsPony
    return if @__ppm2_last_draw == FrameNumber()
    @__ppm2_last_draw = FrameNumber()
    return if @IsDormant()
    @__ppm2_last_dead = @__ppm2_last_dead or 0
    return if @__ppm2_last_dead > RealTime()
    data = @GetPonyData()
    renderController = data\GetRenderController()
    status = renderController\PreDraw() if renderController
    data\GetWeightController()\UpdateWeight() if data\GetOverrideBones() and data\GetWeightController()
PPM2.PostPlayerDraw = =>
    return unless @GetPonyData()
    return unless @__cachedIsPony
    data = @GetPonyData()
    renderController = data\GetRenderController()
    renderController\PostDraw() if renderController

hook.Add 'PrePlayerDraw', 'PPM2.PlayerDraw', PPM2.PrePlayerDraw, 2
hook.Add 'PostPlayerDraw', 'PPM2.PostPlayerDraw', PPM2.PostPlayerDraw, 2
hook.Add 'PostDrawOpaqueRenderables', 'PPM2.PostDrawOpaqueRenderables', PPM2.PostDrawOpaqueRenderables, 2
hook.Add 'RenderScreenspaceEffects', 'PPM2.RenderScreenspaceEffects', ->
    self = LocalPlayer()

    if @__cachedIsPony and @GetPonyData() and @Alive()
        @GetPonyData()\GetRenderController()\DrawLegs()

SHOULD_DRAW_VIEWMODEL = CreateConVar('cl_ppm2_draw_hands', '1', {FCVAR_ARCHIVE}, 'Should draw hooves as viewmodel')

hook.Add 'PreDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return true unless SHOULD_DRAW_VIEWMODEL\GetBool()
    return unless IsValid(arms)
    return unless ply.__cachedIsPony
    return unless ply\Alive()
    arms\SetPos(LocalPlayer()\EyePos() + Vector(0, 0, 100))
    wep = ply\GetActiveWeapon()
    if IsValid(wep) and wep.UseHands == false
        return true -- Dafuck?
    data = ply\GetPonyData()
    return unless data
    status = data\GetRenderController()\PreDrawArms(arms)
    return status if status ~= nil
    arms.__ppm2_draw = true

hook.Add 'PostDrawPlayerHands', 'PPM2.ViewModel', (arms = NULL, viewmodel = NULL, ply = LocalPlayer(), weapon = NULL) ->
    return unless IsValid(arms)
    return unless arms.__ppm2_draw
    data = ply\GetPonyData()
    return unless data
    data\GetRenderController()\PostDrawArms(arms)
    arms.__ppm2_draw = false

PlayerRespawn = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerRespawn()

PlayerDeath = ->
    ent = net.ReadEntity()
    return if not IsValid(ent)
    return if not ent\GetPonyData()
    ent\GetPonyData()\PlayerDeath()

lastDataSend = 0
lastDataReceived = 0
net.Receive 'PPM2.RequestPonyData', ->
    return if lastDataReceived > RealTime()
    lastDataReceived = RealTime() + 10
    RunConsoleCommand('ppm2_reload')

net.Receive 'PPM2.PlayerRespawn', PlayerRespawn
net.Receive 'PPM2.PlayerDeath', PlayerDeath

concommand.Add 'ppm2_require', ->
    net.Start('PPM2.Require')
    net.SendToServer()
    PPM2.Message 'Requesting pony data...'

concommand.Add 'ppm2_reload', ->
    return if lastDataSend > RealTime()
    lastDataSend = RealTime() + 10
    instance = PPM2.GetMainData()
    newData = instance\CreateNetworkObject()
    newData\Create()
    instance\SetNetworkData(newData)
    PPM2.Message 'Sending pony data to server...'

hook.Add 'KeyPress', 'PPM2.RequireData', ->
    hook.Remove 'KeyPress', 'PPM2.RequireData'
    RunConsoleCommand('ppm2_reload')
    timer.Simple 3, -> RunConsoleCommand('ppm2_require')

PPM_HINT_COLOR_FIRST = Color(255, 255, 255)
PPM_HINT_COLOR_SECOND = Color(0, 0, 0)

hook.Add 'HUDPaint', 'PPM2.EditorStatus', ->
    lply = LocalPlayer()
    lpos = lply\EyePos()
    for ply in *player.GetAll()
        if ply ~= lply
            if ply\GetNWBool('PPM2.InEditor')
                pos = ply\EyePos()
                dist = pos\Distance(lpos)
                if dist < 250
                    pos.z += 10
                    alpha = math.Clamp(1 - dist / 250, 0.1, 1)
                    {:x, :y} = pos\ToScreen()
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x + 1, y + 1, PPM_HINT_COLOR_SECOND, TEXT_ALIGN_CENTER)
                    draw.DrawText('In PPM/2 Editor', 'HudHintTextLarge', x, y, PPM_HINT_COLOR_FIRST, TEXT_ALIGN_CENTER)

concommand.Add 'ppm2_cleanup', ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()
    PPM2.Message('All unused models were removed')

timer.Create 'PPM2.ModelCleanup', 60, 0, ->
    for ent in *ents.GetAll()
        if ent.isPonyPropModel and not IsValid(ent.manePlayer)
            ent\Remove()


plugins/command: Add support for @config.strip_colors

Useful for servers like Twitch which don't properly support IRC colors.

import IRCClient from require "lib.irc"
import sleep from require "cqueues"

unpack = unpack or table.unpack

handle_error = (target, command, err)=>
	@send "COMMAND_ERR", target, command, "Error: #{err}"

IRCClient\add_sender 'COMMAND_OK', (target, command_name, message, msgid)=>
	if msgid
		if @config.strip_colors
			@send "PRIVMSG", target, "[#{command_name}] #{message}", "+draft/reply": msgid
	else
		if @config.strip_colors
			@send "PRIVMSG", target, "[#{command_name}] #{message}"
		else
			@send "PRIVMSG", target, "[\00303#{command_name}\003] #{message}"

IRCClient\add_sender 'COMMAND_ERR', (target, command_name, message, msgid)=>
	if msgid
		if @config.strip_colors
			@send "PRIVMSG", target, "[!#{command_name}] #{message}", "+draft/reply": msgid
		else
			@send "PRIVMSG", target, "[\00304!#{command_name}\003] #{message}", "+draft/reply": msgid
	else
		if @config.strip_colors
			@send "PRIVMSG", target, "[!#{command_name}] #{message}"
		else
			@send "PRIVMSG", target, "[\00304!#{command_name}\003] #{message}"

IRCClient\add_handler 'PRIVMSG', (prefix, args, tags)=>
	{target, message} = args

	cmd_prefix = @config.prefix
	return unless message\sub(1, #cmd_prefix) == cmd_prefix

	msgid = @get_tag(tags, key: 'msgid').value

	cmd_name = message\match "%S+", #cmd_prefix + 1
	command_args = {prefix, target, tags}
	command = @commands\get cmd_name, multiple: false, index: IRCClient.commands
	return @send "COMMAND_ERR", target, "core", "Command not found: #{cmd_name}", msgid unless command

	args_start = (message\find"%s" or #message) + 1
	args = [arg for arg in message\sub(args_start)\gmatch "%S+"]
	command_args[#command_args + 1] = table.concat args, " "
	command_args[#command_args + 1] = arg for arg in *args

	proxy_self = {
		-- need to fill in @send_ok and @send_err
		send_ok: (message)=>
			@send "COMMAND_OK", target, cmd_name, message, msgid

		send_err: (message)=>
			@send "COMMAND_ERR", target, cmd_name, message, msgid
	}

	setmetatable proxy_self, __index: self

	-- command proxy_self, unpack command_args
	ok, err = @pcall_bare command, proxy_self, unpack command_args
	unless ok
		handle_error self, target, cmd_name, err

IRCClient\add_command "test", async: true, (prefix, target, tags)=>
	{:nick} = prefix

	sleep 5

	@users\get(nick)\and_then (client)->
		if client.account != ""
			@send_ok "Account name: #{client.account}" if client.account != ""
		else
			@send_err "Account not known for: #{nick}"

IRCClient\add_command "caps", (prefix, target)=>
	line = [k for k in pairs @server.ircv3_caps]
	@send_ok table.concat(line, " ")

IRCClient\add_command "test_err", (prefix, target)=>
	@send_err "Error result"

IRCClient\add_command "test_fail", (prefix, target)=>
	error "test"

import IRCClient from require "lib.irc"
import sleep from require "cqueues"

unpack = unpack or table.unpack

handle_error = (target, command, err)=>
	@send "COMMAND_ERR", target, command, "Error: #{err}"

IRCClient\add_sender 'COMMAND_OK', (target, command_name, message, msgid)=>
	if msgid
		@send "PRIVMSG", target, "[\00303#{command_name}\003] #{message}", "+draft/reply": msgid
	else
		@send "PRIVMSG", target, "[\00303#{command_name}\003] #{message}"

IRCClient\add_sender 'COMMAND_ERR', (target, command_name, message, msgid)=>
	if msgid
		@send "PRIVMSG", target, "[\00304!#{command_name}\003] #{message}", "+draft/reply": msgid
	else
		@send "PRIVMSG", target, "[\00304!#{command_name}\003] #{message}"

IRCClient\add_handler 'PRIVMSG', (prefix, args, tags)=>
	{target, message} = args

	cmd_prefix = @config.prefix
	return unless message\sub(1, #cmd_prefix) == cmd_prefix

	msgid = @get_tag(tags, key: 'msgid').value

	cmd_name = message\match "%S+", #cmd_prefix + 1
	command_args = {prefix, target, tags}
	command = @commands\get cmd_name, multiple: false, index: IRCClient.commands
	return @send "COMMAND_ERR", target, "core", "Command not found: #{cmd_name}", msgid unless command

	args_start = (message\find"%s" or #message) + 1
	args = [arg for arg in message\sub(args_start)\gmatch "%S+"]
	command_args[#command_args + 1] = table.concat args, " "
	command_args[#command_args + 1] = arg for arg in *args

	proxy_self = {
		-- need to fill in @send_ok and @send_err
		send_ok: (message)=>
			@send "COMMAND_OK", target, cmd_name, message, msgid

		send_err: (message)=>
			@send "COMMAND_ERR", target, cmd_name, message, msgid
	}

	setmetatable proxy_self, __index: self

	-- command proxy_self, unpack command_args
	ok, err = @pcall_bare command, proxy_self, unpack command_args
	unless ok
		handle_error self, target, cmd_name, err

IRCClient\add_command "test", async: true, (prefix, target, tags)=>
	{:nick} = prefix

	sleep 5

	@users\get(nick)\and_then (client)->
		if client.account != ""
			@send_ok "Account name: #{client.account}" if client.account != ""
		else
			@send_err "Account not known for: #{nick}"

IRCClient\add_command "caps", (prefix, target)=>
	line = [k for k in pairs @server.ircv3_caps]
	@send_ok table.concat(line, " ")

IRCClient\add_command "test_err", (prefix, target)=>
	@send_err "Error result"

IRCClient\add_command "test_fail", (prefix, target)=>
	error "test"


Enable applications

Enable applications

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'
    smtp_from_newsletter 'MG Nedelja informatike - newsletter <newsletter@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'
    security_map_app_id_url 'http://127.0.0.1:19222/map_app_id'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled true

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'
    smtp_from_newsletter 'MG Nedelja informatike - newsletter <newsletter@csnedelja.mg.edu.rs>'

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'info'
    listen_address '127.0.0.1'
    secret secrets.app_secret
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'
    security_map_app_id_url 'http://10.131.29.36:19222/map_app_id'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled true

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'
    smtp_from_newsletter 'MG Nedelja informatike - newsletter <newsletter@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://127.0.0.1:19222/new_user'
    security_map_app_id_url 'http://127.0.0.1:19222/map_app_id'

    email_cooldown 20
    uploads_dir 'uploads'
    applications_enabled false

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'
    smtp_from_newsletter 'MG Nedelja informatike - newsletter <newsletter@csnedelja.mg.edu.rs>'

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'info'
    listen_address '127.0.0.1'
    secret secrets.app_secret
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    disable_email_confirmation true
    security_new_user_url 'http://10.131.29.36:19222/new_user'
    security_map_app_id_url 'http://10.131.29.36:19222/map_app_id'

    email_cooldown 30 * 60
    uploads_dir 'uploads'
    applications_enabled false

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true


Fixed Map\set.

Fixed Map\set.

More code will probably be needed for the features to work correctly,
but at least the tests now pass.

Class = require "nyaa.class"
images = require "images"
Ground = require "data.ground"

tool = (x,y,z) -> -- Cette fonction sert à générer une carte binaire de perlin noise
	part = 5
	pow = 1/9
	t = 2*love.math.noise(x,y,z)-1
	s = ((math.abs(t))^pow)*t/math.abs(t)+1
	t = (math.floor(s*part/2)/part)
	s = (math.abs(t)^(1/pow))*t/math.abs(t)
	return s

noise = (noise,xMax,yMax) ->
	xMax = 400
	yMax = 300
	grid = {}
	for x = 1, xMax
		grid[x] = grid[x] or {}
		for y = 1, yMax
		    grid[x][y] = tool((x+noise)/50,(y+noise)/50,noise)
	return grid

Tile = Class "Tile",
	__init: (arg) =>
		@groundChange = arg.groundChange or nil -- permet de savoir si une case est d'origine (générée par le bruit) ou si elle a été modifiée
		@groundType = arg.groundType
		@structure = arg.structure or nil
		@ressources = arg.ressources or nil

	crossable: =>
		if @groundType != 0 and not @structure
			return true
		else return false

local Map

Map = Class "Map",
	__init: (arg) =>
		@noises = arg.noises or nil -- FIXME
		@defaultground = arg.defaultGround
		@grid = {}
		@xMax = arg.xMax or 300
		@yMax = arg.yMax or 200
		@zMin = arg.zMin or -3
		@zMax = arg.zMax or 3

	__class: {
		:Tile

		perlinMap: (arg) ->
			self = Map arg

			mapSource1 = noise(@noises,@xMax,@yMax)		--Height Map
			mapSource2 = noise(@noises+0.1,@xMax,@yMax*2)	--GroundType Map
			for x = 1, @xMax
				for y = 1, @yMax
					for z = @zMin, mapSource1[x][y]
						grid[x][y][z] = Tile
							groundType: Ground[mapSource2[x][y]+1]
					for z = mapSource1[x][y]+1, @zMax
						grid[x][y][z] = Ground[3] -- empty ground

			self

		flatMap: (arg) ->
			self = Map arg

			rocks = Ground\getByName "Rocks"
			air = Ground\getByName "Air"

			for x = 1, @xMax
				@grid[x] = {}

				for y = 1, @yMax
					@grid[x][y] = {}

					for z = @zMin, @zMax
						if z <= 0
							@grid[x][y][z] = rocks
						else
							@grid[x][y][z] = air

			self

		---
		-- @param str (string)
		fromMoon: (str) =>
	}

	---
	-- @return (Tile)
	get: (x, y, z) =>
		@grid[x][y][z]

	---
	-- @return boolean
	set: (x, y, z, arguments) =>
		local modified

		if arguments.groundType
			@grid[x][y][z].groundType = arguments.groundType
			modified = true

		if arguments.structure
			@grid[x][y][z].structure = arguments.structure
			modified = true

		if arguments.ressource
			@grid[x][y][z].ressource = arguments.ressource
			modified = true

		if modified
			@grid[x][y][z].groundChange = true

		modified

	---
	-- @return (string)
	toMoon: =>

Map

Class = require "nyaa.class"
images = require "images"
Ground = require "data.ground"

tool = (x,y,z) -> -- Cette fonction sert à générer une carte binaire de perlin noise
	part = 5
	pow = 1/9
	t = 2*love.math.noise(x,y,z)-1
	s = ((math.abs(t))^pow)*t/math.abs(t)+1
	t = (math.floor(s*part/2)/part)
	s = (math.abs(t)^(1/pow))*t/math.abs(t)
	return s

noise = (noise,xMax,yMax) ->
	xMax = 400
	yMax = 300
	grid = {}
	for x = 1, xMax
		grid[x] = grid[x] or {}
		for y = 1, yMax
		    grid[x][y] = tool((x+noise)/50,(y+noise)/50,noise)
	return grid

Tile = Class "Tile",
	__init: (arg) =>
		@groundChange = arg.groundChange or nil -- permet de savoir si une case est d'origine (générée par le bruit) ou si elle a été modifiée
		@groundType = arg.groundType
		@structure = arg.structure or nil
		@ressources = arg.ressources or nil

	crossable: =>
		if @groundType != 0 and not @structure
			return true
		else return false

local Map

Map = Class "Map",
	__init: (arg) =>
		@noises = arg.noises or nil -- FIXME
		@defaultground = arg.defaultGround
		@grid = {}
		@xMax = arg.xMax or 300
		@yMax = arg.yMax or 200
		@zMin = arg.zMin or -3
		@zMax = arg.zMax or 3

	__class: {
		:Tile

		perlinMap: (arg) ->
			self = Map arg

			mapSource1 = noise(@noises,@xMax,@yMax)		--Height Map
			mapSource2 = noise(@noises+0.1,@xMax,@yMax*2)	--GroundType Map
			for x = 1, @xMax
				for y = 1, @yMax
					for z = @zMin, mapSource1[x][y]
						grid[x][y][z] = Tile
							groundType: Ground[mapSource2[x][y]+1]
					for z = mapSource1[x][y]+1, @zMax
						grid[x][y][z] = Ground[3] -- empty ground

			self

		flatMap: (arg) ->
			self = Map arg

			rocks = Ground\getByName "Rocks"
			air = Ground\getByName "Air"

			for x = 1, @xMax
				@grid[x] = {}

				for y = 1, @yMax
					@grid[x][y] = {}

					for z = @zMin, @zMax
						if z <= 0
							@grid[x][y][z] = rocks
						else
							@grid[x][y][z] = air

			self

		---
		-- @param str (string)
		fromMoon: (str) =>
	}

	---
	-- @return (Tile)
	get: (x, y, z) =>
		@grid[x][y][z]

	---
	-- @return nil
	set: (x, y, z, arguments) =>
		if arguments.groundType
			@grid[x][y][z].groundType = arguments.groundType
			modified = true
		if arguments.structure
			@grid[x][y][z].groundType = arguments.structure
			modified = true
		if arguments.ressource
			@grid[x][y][z].groundType = arguments.ressource
			modified = true
		if modified
			@grid[x][y][z].groundChange = true

	---
	-- @return (string)
	toMoon: =>

Map


debug minion : bug when clicking outside of available path


Coverage for Entity.followCamera

Coverage for Entity.followCamera

require "busted"
require "tests.mock_love"

require "LunoPunk.Entity"
require "LunoPunk.Graphic"
require "LunoPunk.Mask"
require "LunoPunk.utils.moonscript"

describe "Entity", ->
	it "construct", ->
		e = Entity!
		assert.are.equal true, e.active
		assert.are.equal false, e.autoClear
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height
		assert.are.equal 0, e.bottom
		assert.are.equal 0, e.centerX
		assert.are.equal 0, e.centerY
		assert.are.equal true, e.visible
		assert.are.equal true, e.collidable
		assert.are.equal false, e.followCamera

		-- Follow Camera coverage
		e.followCamera = true
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!
		LP.camera.x += 1
		assert.are.equal 1, e\x!
		assert.are.equal 0, e\y!
		LP.camera.y -= 1
		assert.are.equal -1, e\y!
		e.followCamera = false
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!

		e = Entity 10, 15
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		g = Graphic!
		g.x, g.y = 25, 30
		e = Entity 10, 15, g
		assert.are.equal g, e\graphic!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		m = Mask!
		e = Entity 10, 15, g, m
		assert.are.equal m, e\mask!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

require "busted"
require "tests.mock_love"

require "LunoPunk.Entity"
require "LunoPunk.Graphic"
require "LunoPunk.Mask"
require "LunoPunk.utils.moonscript"

describe "Entity", ->
	it "construct", ->
		e = Entity!
		assert.are.equal true, e.active
		assert.are.equal false, e.autoClear
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height
		assert.are.equal 0, e.bottom
		assert.are.equal 0, e.centerX
		assert.are.equal 0, e.centerY
		assert.are.equal true, e.visible
		assert.are.equal true, e.collidable
		assert.are.equal false, e.followCamera

		e = Entity 10, 15
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		g = Graphic!
		g.x, g.y = 25, 30
		e = Entity 10, 15, g
		assert.are.equal g, e\graphic!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		m = Mask!
		e = Entity 10, 15, g, m
		assert.are.equal m, e\mask!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height