autocycle: more checks

autocycle: more checks

Former-commit-id: 789ffc66f50f36131aae7dfd11bd70798f30cca5 [formerly a1806b50467132ad2596f6fc460af636304cae5b]
Former-commit-id: 9fd75aa4b25074d6785b4179b01df75fdc2dcd31

cycleNeeded = (source, destination, arg) =>
  -- Don't care about our own events
  if source.nick == @config.nick
    return
  -- Check if we are the last nick in the channel
  counter = 0
  unless @channels[destination]
    return
  unless @channels[destination].nicks
    return
  for nick, meta in pairs @channels[destination].nicks
    if nick == @config.nick
      -- Check if we have op
      for m in *meta.modes
        if m == 'o'
          return
    unless nick == @config.nick or nick == source.nick
      counter += 1

  if counter == 0
    @Log 'info', 'Cycling opless channel %s', destination
    @Part(destination)
    @Join(destination)

{
  PART: {
    cycleNeeded
  }
  QUIT: {
    cycleNeeded
  }
}

cycleNeeded = (source, destination, arg) =>
  -- Don't care about our own events
  if source.nick == @config.nick
    return
  -- Check if we are the last nick in the channel
  counter = 0
  for nick, meta in pairs @channels[destination].nicks
    if nick == @config.nick
      -- Check if we have op
      for m in *meta.modes
        if m == 'o'
          return
    unless nick == @config.nick or nick == source.nick
      counter += 1

  if counter == 0
    @Log 'info', 'Cycling opless channel %s', destination
    @Part(destination)
    @Join(destination)

{
  PART: {
    cycleNeeded
  }
  QUIT: {
    cycleNeeded
  }
}


Added blurring

Added blurring

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

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

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

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

ENABLE_EMOTES_IN_CHAT = CreateConVar('ppm2_cl_emotes_chat', '1', {FCVAR_ARCHIVE}, 'Show emotes list while chatbox is open')
ENABLE_EMOTES_IN_CONTEXT = CreateConVar('ppm2_cl_emotes_context', '1', {FCVAR_ARCHIVE}, 'Show emotes list while context menu is open')

net.Receive 'PPM2.DamageAnimation', ->
	ent = net.ReadEntity()
	return if not IsValid(ent) or not ent\GetPonyData()
	hook.Call('PPM2_HurtAnimation', nil, ent)

net.Receive 'PPM2.KillAnimation', ->
	ent = net.ReadEntity()
	return if not IsValid(ent) or not ent\GetPonyData()
	hook.Call('PPM2_KillAnimation', nil, ent)

net.Receive 'PPM2.AngerAnimation', ->
	ent = net.ReadEntity()
	return if not IsValid(ent) or not ent\GetPonyData()
	hook.Call('PPM2_AngerAnimation', nil, ent)

net.Receive 'PPM2.PlayEmote', ->
	emoteID = net.ReadUInt(8)
	ply = net.ReadEntity()
	isEndless = net.ReadBool()
	shouldStop = net.ReadBool()
	return if not IsValid(ply) or not ply\GetPonyData()
	return if not PPM2.AVALIABLE_EMOTES[emoteID]
	hook.Call('PPM2_EmoteAnimation', nil, ply, PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time, isEndless, shouldStop)

PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
PPM2.EmotesPanel\Remove() if IsValid(PPM2.EmotesPanel)

CONSOLE_EMOTES_COMMAND = (ply = LocalPlayer(), cmd = '', args = {}) ->
	args[1] = args[1] or ''
	emoteID = tonumber(args[1])
	isEndless = tobool(args[2])
	shouldStop = tobool(args[3])

	if emoteID
		if not PPM2.AVALIABLE_EMOTES[emoteID]
			PPM2.LMessage('message.ppm2.emotes.invalid', emoteID)
			return
		net.Start('PPM2.PlayEmote')
		net.WriteUInt(emoteID, 8)
		net.WriteBool(isEndless)
		net.WriteBool(shouldStop)
		net.SendToServer()
		hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time)
	else
		emoteID = args[1]\lower()
		if not PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID]
			PPM2.LMessage('message.ppm2.emotes.invalid', emoteID)
			return
		net.Start('PPM2.PlayEmote')
		net.WriteUInt(PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].id, 8)
		net.WriteBool(isEndless)
		net.WriteBool(shouldStop)
		net.SendToServer()
		hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), emoteID, PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].time)

CONSOLE_DEF_LIST = ['ppm2_emote "' .. sequence .. '"' for _, {:sequence} in ipairs PPM2.AVALIABLE_EMOTES]
CONSOLE_EMOTES_AUTOCOMPLETE = (cmd = '', args = '') ->
	args = args\Trim()
	return CONSOLE_DEF_LIST if args == ''
	output = {}
	for _, {:sequence} in ipairs PPM2.AVALIABLE_EMOTES
		if string.find(sequence, '^' .. args)
			table.insert(output, 'ppm2_emote "' .. sequence .. '"')
	return output

concommand.Add 'ppm2_emote', CONSOLE_EMOTES_COMMAND, CONSOLE_EMOTES_AUTOCOMPLETE

BUTTON_CLICK_FUNC = (isEndless = false, shouldStop = false) =>
	if @sendToServer
		net.Start('PPM2.PlayEmote')
		net.WriteUInt(@id, 8)
		net.WriteBool(isEndless)
		net.WriteBool(shouldStop)
		net.SendToServer()
		hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), @sequence, @time, isEndless, shouldStop)
	else
		hook.Call('PPM2_EmoteAnimation', nil, @target, @sequence, @time, isEndless, shouldStop)

BUTTON_TEXT_COLOR = Color(255, 255, 255)

IMAGE_PANEL_THINK = =>
	@lastThink = RealTimeL() + .4
	if @IsHovered()
		if not @oldHover
			@oldHover = true
			@hoverPnl\SetVisible(true)
			x, y = @LocalToScreen(0, 0)
			@hoverPnl\SetPos(x - 256, y - 224)
	else
		if @oldHover
			@oldHover = false
			@hoverPnl\SetVisible(false)

HOVERED_IMAGE_PANEL_THINK = =>
	if not @parent\IsValid()
		@Remove()
		return
	if @parent.lastThink < RealTimeL()
		@SetVisible(false)

PPM2.CreateEmotesPanel = (parent, target = LocalPlayer(), sendToServer = true) ->
	self = vgui.Create('DPanel', parent)
	self\SetSkin('DLib_Black')
	@SetSize(200, 300)
	@Paint = (w = 0, h = 0) =>
		DLib.blur.DrawPanel(w, h, @LocalToScreen(0, 0)) if DLib.skin.ENABLE_BLUR\GetBool()
		surface.SetDrawColor(0, 0, 0, 150)
		surface.DrawRect(0, 0, w, h)
	@scroll = vgui.Create('DScrollPanel', @)
	with @scroll
		\Dock(FILL)
		\SetSkin('DLib_Black')
		\SetSize(200, 300)
		.Paint = ->
		\SetMouseInputEnabled(true)
	@buttons = for _, {:name, :id, :sequence, :time, :fexists, :filecrop} in ipairs PPM2.AVALIABLE_EMOTES
		with btn = vgui.Create('DButton', @scroll)
			.id = id
			.time = time
			.sequence = sequence
			.sendToServer = sendToServer
			.target = target
			.DoClick = BUTTON_CLICK_FUNC
			\SetSize(200, 32)
			\SetText(name)
			\SetFont('HudHintTextLarge')
			\Dock(TOP)
			with .checkbox = \Add('DCheckBox')
				\Dock(RIGHT)
				\DockMargin(2, 8, 2, 8)
				\SetSize(16, 16)
				\SetChecked(false)
				.Think = ->
					if IsValid(target)
						if ponyData = target\GetPonyData()
							if renderController = ponyData\GetRenderController()
								if emotesController = renderController\CreateEmotesController()
									\SetChecked(emotesController\HasSequence(sequence) and emotesController\GetSequence(sequence)\GetInfinite())
				.OnChange = (checkbox3, newVal) ->
					return if .suppress
					BUTTON_CLICK_FUNC(btn, true) if newVal
					BUTTON_CLICK_FUNC(btn, false, true) if not newVal
			if fexists
				image = vgui.Create('DImage', btn)
				with image
					\Dock(LEFT)
					\SetSize(32, 32)
					\SetImage(filecrop)
					\SetMouseInputEnabled(true)
					.hoverPnl = vgui.Create('DImage')
					.Think = IMAGE_PANEL_THINK
					.oldHover = false
					with .hoverPnl
						\SetMouseInputEnabled(false)
						\SetVisible(false)
						\SetImage(filecrop)
						\SetSize(256, 256)
						.Think = HOVERED_IMAGE_PANEL_THINK
						.parent = image
					.OnRemove = -> .hoverPnl\Remove() if IsValid(.hoverPnl)
			btn
	@scroll\AddItem(btn) for _, btn in ipairs @buttons
	@SetVisible(false)
	@SetMouseInputEnabled(false)
	return @

hook.Add 'ContextMenuCreated', 'PPM2.Emotes', =>
	return if not IsValid(@)
	return if not ENABLE_EMOTES_IN_CONTEXT\GetBool()
	PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
	PPM2.EmotesPanelContext = PPM2.CreateEmotesPanel(@)
	PPM2.EmotesPanelContext\SetPos(ScrW() / 2 - 100, ScrH() - 300)
	PPM2.EmotesPanelContext\SetVisible(true)
	PPM2.EmotesPanelContext\SetMouseInputEnabled(true)
	timer.Create 'PPM2.ContextMenuEmotesUpdate', 1, 0, ->
		if not IsValid(PPM2.EmotesPanelContext)
			timer.Remove 'PPM2.ContextMenuEmotesUpdate'
			return
		return if not IsValid(LocalPlayer())
		status = LocalPlayer()\IsPony()
		PPM2.EmotesPanelContext\SetVisible(status)
		PPM2.EmotesPanelContext\SetMouseInputEnabled(status)

hook.Add 'StartChat', 'PPM2.Emotes', ->
	if not IsValid(PPM2.EmotesPanel) and ENABLE_EMOTES_IN_CHAT\GetBool()
		PPM2.EmotesPanel = PPM2.CreateEmotesPanel()
		PPM2.EmotesPanel\SetPos(ScrW() - 500, ScrH() - 300)

	if IsValid(PPM2.EmotesPanel)
		if LocalPlayer()\IsPony()
			PPM2.EmotesPanel\SetVisible(true)
			PPM2.EmotesPanel\SetMouseInputEnabled(true)
			PPM2.EmotesPanel\RequestFocus()
		else
			PPM2.EmotesPanel\SetVisible(false)
			PPM2.EmotesPanel\SetMouseInputEnabled(false)
			PPM2.EmotesPanel\KillFocus()

hook.Add 'FinishChat', 'PPM2.Emotes', ->
	if IsValid(PPM2.EmotesPanel)
		PPM2.EmotesPanel\KillFocus()
		PPM2.EmotesPanel\SetVisible(false)
		PPM2.EmotesPanel\SetMouseInputEnabled(false)

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

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

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

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

ENABLE_EMOTES_IN_CHAT = CreateConVar('ppm2_cl_emotes_chat', '1', {FCVAR_ARCHIVE}, 'Show emotes list while chatbox is open')
ENABLE_EMOTES_IN_CONTEXT = CreateConVar('ppm2_cl_emotes_context', '1', {FCVAR_ARCHIVE}, 'Show emotes list while context menu is open')

net.Receive 'PPM2.DamageAnimation', ->
	ent = net.ReadEntity()
	return if not IsValid(ent) or not ent\GetPonyData()
	hook.Call('PPM2_HurtAnimation', nil, ent)

net.Receive 'PPM2.KillAnimation', ->
	ent = net.ReadEntity()
	return if not IsValid(ent) or not ent\GetPonyData()
	hook.Call('PPM2_KillAnimation', nil, ent)

net.Receive 'PPM2.AngerAnimation', ->
	ent = net.ReadEntity()
	return if not IsValid(ent) or not ent\GetPonyData()
	hook.Call('PPM2_AngerAnimation', nil, ent)

net.Receive 'PPM2.PlayEmote', ->
	emoteID = net.ReadUInt(8)
	ply = net.ReadEntity()
	isEndless = net.ReadBool()
	shouldStop = net.ReadBool()
	return if not IsValid(ply) or not ply\GetPonyData()
	return if not PPM2.AVALIABLE_EMOTES[emoteID]
	hook.Call('PPM2_EmoteAnimation', nil, ply, PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time, isEndless, shouldStop)

PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
PPM2.EmotesPanel\Remove() if IsValid(PPM2.EmotesPanel)

CONSOLE_EMOTES_COMMAND = (ply = LocalPlayer(), cmd = '', args = {}) ->
	args[1] = args[1] or ''
	emoteID = tonumber(args[1])
	isEndless = tobool(args[2])
	shouldStop = tobool(args[3])

	if emoteID
		if not PPM2.AVALIABLE_EMOTES[emoteID]
			PPM2.LMessage('message.ppm2.emotes.invalid', emoteID)
			return
		net.Start('PPM2.PlayEmote')
		net.WriteUInt(emoteID, 8)
		net.WriteBool(isEndless)
		net.WriteBool(shouldStop)
		net.SendToServer()
		hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), PPM2.AVALIABLE_EMOTES[emoteID].sequence, PPM2.AVALIABLE_EMOTES[emoteID].time)
	else
		emoteID = args[1]\lower()
		if not PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID]
			PPM2.LMessage('message.ppm2.emotes.invalid', emoteID)
			return
		net.Start('PPM2.PlayEmote')
		net.WriteUInt(PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].id, 8)
		net.WriteBool(isEndless)
		net.WriteBool(shouldStop)
		net.SendToServer()
		hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), emoteID, PPM2.AVALIABLE_EMOTES_BY_SEQUENCE[emoteID].time)

CONSOLE_DEF_LIST = ['ppm2_emote "' .. sequence .. '"' for _, {:sequence} in ipairs PPM2.AVALIABLE_EMOTES]
CONSOLE_EMOTES_AUTOCOMPLETE = (cmd = '', args = '') ->
	args = args\Trim()
	return CONSOLE_DEF_LIST if args == ''
	output = {}
	for _, {:sequence} in ipairs PPM2.AVALIABLE_EMOTES
		if string.find(sequence, '^' .. args)
			table.insert(output, 'ppm2_emote "' .. sequence .. '"')
	return output

concommand.Add 'ppm2_emote', CONSOLE_EMOTES_COMMAND, CONSOLE_EMOTES_AUTOCOMPLETE

BUTTON_CLICK_FUNC = (isEndless = false, shouldStop = false) =>
	if @sendToServer
		net.Start('PPM2.PlayEmote')
		net.WriteUInt(@id, 8)
		net.WriteBool(isEndless)
		net.WriteBool(shouldStop)
		net.SendToServer()
		hook.Call('PPM2_EmoteAnimation', nil, LocalPlayer(), @sequence, @time, isEndless, shouldStop)
	else
		hook.Call('PPM2_EmoteAnimation', nil, @target, @sequence, @time, isEndless, shouldStop)

BUTTON_TEXT_COLOR = Color(255, 255, 255)

IMAGE_PANEL_THINK = =>
	@lastThink = RealTimeL() + .4
	if @IsHovered()
		if not @oldHover
			@oldHover = true
			@hoverPnl\SetVisible(true)
			x, y = @LocalToScreen(0, 0)
			@hoverPnl\SetPos(x - 256, y - 224)
	else
		if @oldHover
			@oldHover = false
			@hoverPnl\SetVisible(false)

HOVERED_IMAGE_PANEL_THINK = =>
	if not @parent\IsValid()
		@Remove()
		return
	if @parent.lastThink < RealTimeL()
		@SetVisible(false)

PPM2.CreateEmotesPanel = (parent, target = LocalPlayer(), sendToServer = true) ->
	self = vgui.Create('DPanel', parent)
	self\SetSkin('DLib_Black')
	@SetSize(200, 300)
	@Paint = (w = 0, h = 0) =>
		surface.SetDrawColor(0, 0, 0, 150)
		surface.DrawRect(0, 0, w, h)
	@scroll = vgui.Create('DScrollPanel', @)
	with @scroll
		\Dock(FILL)
		\SetSkin('DLib_Black')
		\SetSize(200, 300)
		.Paint = ->
		\SetMouseInputEnabled(true)
	@buttons = for _, {:name, :id, :sequence, :time, :fexists, :filecrop} in ipairs PPM2.AVALIABLE_EMOTES
		with btn = vgui.Create('DButton', @scroll)
			.id = id
			.time = time
			.sequence = sequence
			.sendToServer = sendToServer
			.target = target
			.DoClick = BUTTON_CLICK_FUNC
			\SetSize(200, 32)
			\SetText(name)
			\SetFont('HudHintTextLarge')
			\Dock(TOP)
			with .checkbox = \Add('DCheckBox')
				\Dock(RIGHT)
				\DockMargin(2, 8, 2, 8)
				\SetSize(16, 16)
				\SetChecked(false)
				.Think = ->
					if IsValid(target)
						if ponyData = target\GetPonyData()
							if renderController = ponyData\GetRenderController()
								if emotesController = renderController\CreateEmotesController()
									\SetChecked(emotesController\HasSequence(sequence) and emotesController\GetSequence(sequence)\GetInfinite())
				.OnChange = (checkbox3, newVal) ->
					return if .suppress
					BUTTON_CLICK_FUNC(btn, true) if newVal
					BUTTON_CLICK_FUNC(btn, false, true) if not newVal
			if fexists
				image = vgui.Create('DImage', btn)
				with image
					\Dock(LEFT)
					\SetSize(32, 32)
					\SetImage(filecrop)
					\SetMouseInputEnabled(true)
					.hoverPnl = vgui.Create('DImage')
					.Think = IMAGE_PANEL_THINK
					.oldHover = false
					with .hoverPnl
						\SetMouseInputEnabled(false)
						\SetVisible(false)
						\SetImage(filecrop)
						\SetSize(256, 256)
						.Think = HOVERED_IMAGE_PANEL_THINK
						.parent = image
					.OnRemove = -> .hoverPnl\Remove() if IsValid(.hoverPnl)
			btn
	@scroll\AddItem(btn) for _, btn in ipairs @buttons
	@SetVisible(false)
	@SetMouseInputEnabled(false)
	return @

hook.Add 'ContextMenuCreated', 'PPM2.Emotes', =>
	return if not IsValid(@)
	return if not ENABLE_EMOTES_IN_CONTEXT\GetBool()
	PPM2.EmotesPanelContext\Remove() if IsValid(PPM2.EmotesPanelContext)
	PPM2.EmotesPanelContext = PPM2.CreateEmotesPanel(@)
	PPM2.EmotesPanelContext\SetPos(ScrW() / 2 - 100, ScrH() - 300)
	PPM2.EmotesPanelContext\SetVisible(true)
	PPM2.EmotesPanelContext\SetMouseInputEnabled(true)
	timer.Create 'PPM2.ContextMenuEmotesUpdate', 1, 0, ->
		if not IsValid(PPM2.EmotesPanelContext)
			timer.Remove 'PPM2.ContextMenuEmotesUpdate'
			return
		return if not IsValid(LocalPlayer())
		status = LocalPlayer()\IsPony()
		PPM2.EmotesPanelContext\SetVisible(status)
		PPM2.EmotesPanelContext\SetMouseInputEnabled(status)

hook.Add 'StartChat', 'PPM2.Emotes', ->
	if not IsValid(PPM2.EmotesPanel) and ENABLE_EMOTES_IN_CHAT\GetBool()
		PPM2.EmotesPanel = PPM2.CreateEmotesPanel()
		PPM2.EmotesPanel\SetPos(ScrW() - 500, ScrH() - 300)

	if IsValid(PPM2.EmotesPanel)
		if LocalPlayer()\IsPony()
			PPM2.EmotesPanel\SetVisible(true)
			PPM2.EmotesPanel\SetMouseInputEnabled(true)
			PPM2.EmotesPanel\RequestFocus()
		else
			PPM2.EmotesPanel\SetVisible(false)
			PPM2.EmotesPanel\SetMouseInputEnabled(false)
			PPM2.EmotesPanel\KillFocus()

hook.Add 'FinishChat', 'PPM2.Emotes', ->
	if IsValid(PPM2.EmotesPanel)
		PPM2.EmotesPanel\KillFocus()
		PPM2.EmotesPanel\SetVisible(false)
		PPM2.EmotesPanel\SetMouseInputEnabled(false)


loadstring to load

loadstring to load

parse = require'moonscript.parse'
compile = require'moonscript.compile'
ln = require'linenoise'
inspect = require'inspect'
import remove, insert, concat from table

printerr = (...) -> io.stderr\write "#{concat {...}, "\t"}\n"

prompt =
	p: ">"
	deepen: => @p = "? "
	reset: => @p = ">"

-- MoonScript transpiler
to_lua = (code) ->
	tree, err = parse.string code

	return nil, err if err

	lua_code, err, pos = compile.tree tree

	unless lua_code
		nil, compile.format_error err, pos, code
	else lua_code

-- Lua evaluator & printer
fnwrap = (code) -> "return function(__newenv) local _ENV = setmetatable(__newenv, {__index = _ENV}) #{code} end"

evalprint = (env, lua_code) ->
	lua_code = if vardec = lua_code\match"^local%s+(.*)$"
			if exportFnCl = vardec\match "^%w+%s+(.*)$"
				if exportFnCl\match "^="
					"#{lua_code\match"local%s+(%w+)"} #{exportFnCl}"
				else exportFnCl
			else vardec
		elseif lua_code\match"__class"
			lua_code = lua_code\gsub "^local%s+", "export" , "1"
		else lua_code

	luafn, err = load fnwrap(lua_code), "tmp"

	return printerr err if err

	result = {pcall luafn!, env}

	ok = remove result, 1

	unless ok then printerr result[1]
	else
		if #result > 0
			print (inspect result)\match"^%s*{%s*(.*)%s*}%s*%n?%s*$"
			unpack result

---- tab completion
cndgen = (env) ->  (line) ->
	if i1 = line\find '[.\\%w_]+$' -- if completable
		with res = {}
			front = line\sub(1, i1 - 1)
			partial = line\sub i1
			prefix, last = partial\match '(.-)([^.\\]*)$'
			t, all = env

			if #prefix > 0 -- tbl.ky or not
				P = prefix\sub(1, -2)
				all = last == ''

				for w in P\gmatch '[^.\\]+'
					t = t[w]

					return unless t

			prefix = front .. prefix

			append_candidates = (t) ->
				if type(t) == 'table'
					table.insert(res, prefix .. k) for k in pairs t when all or k\sub(1, #last) == last

			append_candidates t

			if mt = getmetatable t then append_candidates mt.__index

compgen = (env) ->
	candidates = cndgen env

	(c, s) -> if cc = candidates s
		ln.addcompletion c, name for name in *cc

string.match_if_fncls = =>
	@\match"[-=]>$" or
	@\match"class%s*$" or
	@\match"class%s+%w+$" or
	@\match"class%s+extends%s+%w+%s*$" or
	@\match"class%s+%w+%s+extends%s+%w+%s*$"

-- for busted unit test, repl is sepalated with `get_line` and `replgen`
-- and using the former for the test, the latter is only needed by `repl`.

get_line = ->
	with line = ln.linenoise prompt.p .. " "
		if line and line\match '%S' then ln.historyadd line

replgen = (get_line) -> (env = {}, _ENV = _ENV) ->
	block = {}

	ln.setcompletion compgen _ENV

	while true
		line = get_line!

		unless line then break
		elseif #line < 1 then continue

		-- if line\match"^:"
			-- (require'moor.replcmd') line
			-- continue

		is_fncls, lua_code, err =  if line\match_if_fncls!
			true
		else
			false, to_lua line

		if lua_code and not err
			evalprint env, lua_code
		elseif is_fncls or err\match "^Failed to parse"
			insert block, line

			prompt.reset with prompt
				\deepen!
				while line and #line > 0
					line = get_line!
					insert block, " #{line}"

			lua_code, err = to_lua concat block, "\n"

			if lua_code then evalprint env, lua_code

			block = {}

		if err
			printerr err

	env

-- this is main repl
repl = replgen get_line

setmetatable {:printerr, :to_lua, :evalprint, :replgen, :repl}, __call: (env, _ENV) => repl env, _ENV

parse = require'moonscript.parse'
compile = require'moonscript.compile'
ln = require'linenoise'
inspect = require'inspect'
import remove, insert, concat from table

printerr = (...) -> io.stderr\write "#{concat {...}, "\t"}\n"

prompt =
	p: ">"
	deepen: => @p = "? "
	reset: => @p = ">"

-- MoonScript transpiler
to_lua = (code) ->
	tree, err = parse.string code

	return nil, err if err

	lua_code, err, pos = compile.tree tree

	unless lua_code
		nil, compile.format_error err, pos, code
	else lua_code

-- Lua evaluator & printer
fnwrap = (code) -> "return function(__newenv) local _ENV = setmetatable(__newenv, {__index = _ENV}) #{code} end"

evalprint = (env, lua_code) ->
	lua_code = if vardec = lua_code\match"^local%s+(.*)$"
			if exportFnCl = vardec\match "^%w+%s+(.*)$"
				if exportFnCl\match "^="
					"#{lua_code\match"local%s+(%w+)"} #{exportFnCl}"
				else exportFnCl
			else vardec
		elseif lua_code\match"__class"
			lua_code = lua_code\gsub "^local%s+", "export" , "1"
		else lua_code

	luafn, err = loadstring fnwrap(lua_code), "tmp"

	return printerr err if err

	result = {pcall luafn!, env}

	ok = remove result, 1

	unless ok then printerr result[1]
	else
		if #result > 0
			print (inspect result)\match"^%s*{%s*(.*)%s*}%s*%n?%s*$"
			unpack result

---- tab completion
cndgen = (env) ->  (line) ->
	if i1 = line\find '[.\\%w_]+$' -- if completable
		with res = {}
			front = line\sub(1, i1 - 1)
			partial = line\sub i1
			prefix, last = partial\match '(.-)([^.\\]*)$'
			t, all = env

			if #prefix > 0 -- tbl.ky or not
				P = prefix\sub(1, -2)
				all = last == ''

				for w in P\gmatch '[^.\\]+'
					t = t[w]

					return unless t

			prefix = front .. prefix

			append_candidates = (t) ->
				if type(t) == 'table'
					table.insert(res, prefix .. k) for k in pairs t when all or k\sub(1, #last) == last

			append_candidates t

			if mt = getmetatable t then append_candidates mt.__index

compgen = (env) ->
	candidates = cndgen env

	(c, s) -> if cc = candidates s
		ln.addcompletion c, name for name in *cc

string.match_if_fncls = =>
	@\match"[-=]>$" or
	@\match"class%s*$" or
	@\match"class%s+%w+$" or
	@\match"class%s+extends%s+%w+%s*$" or
	@\match"class%s+%w+%s+extends%s+%w+%s*$"

-- for busted unit test, repl is sepalated with `get_line` and `replgen`
-- and using the former for the test, the latter is only needed by `repl`.

get_line = ->
	with line = ln.linenoise prompt.p .. " "
		if line and line\match '%S' then ln.historyadd line

replgen = (get_line) -> (env = {}, _ENV = _ENV) ->
	block = {}

	ln.setcompletion compgen _ENV

	while true
		line = get_line!

		unless line then break
		elseif #line < 1 then continue

		-- if line\match"^:"
			-- (require'moor.replcmd') line
			-- continue

		is_fncls, lua_code, err =  if line\match_if_fncls!
			true
		else
			false, to_lua line

		if lua_code and not err
			evalprint env, lua_code
		elseif is_fncls or err\match "^Failed to parse"
			insert block, line

			prompt.reset with prompt
				\deepen!
				while line and #line > 0
					line = get_line!
					insert block, " #{line}"

			lua_code, err = to_lua concat block, "\n"

			if lua_code then evalprint env, lua_code

			block = {}

		if err
			printerr err

	env

-- this is main repl
repl = replgen get_line

setmetatable {:printerr, :to_lua, :evalprint, :replgen, :repl}, __call: (env, _ENV) => repl env, _ENV


Updated cutiemark display sizes

Updated cutiemark display sizes

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

MODEL_BOX_PANEL = {
    SEQUENCE_STAND: 22
    SEQUENCE_FORWARD: 316
    SEQUENCE_WALK: 232
    PONY_VEC_Z: 64 * .7

    EDITOR_SEQUENCES: {
        -- idle
        {
            time: 5
            func: (pos, ang, delta) -> pos, ang
        }
    }

    Init: =>
        @animRate = 1
        @seq = @SEQUENCE_STAND
        @targetAngle = Angle(0, 0, 0)
        @angle = Angle(0, 0, 0)
        @distToPony = 100
        @targetDistToPony = 100
        @vectorPos = Vector(@distToPony, 0, @PONY_VEC_Z)
        @hold = false
        @holdLast = 0
        @mouseX, @mouseY = 0, 0
        @SetMouseInputEnabled(true)
        @editorSeq = 1
        @nextSeq = @EDITOR_SEQUENCES[@editorSeq].time + RealTime()
        @playing = true
        @lastTick = RealTime()
        @SetCursor('none')

    OnMousePressed: (code = MOUSE_LEFT) =>
        return if code ~= MOUSE_LEFT
        @hold = true
        @SetCursor('sizeall')
        @holdLast = RealTime() + .1
        @oldPlaying = @playing
        @playing = false
        @mouseX, @mouseY = gui.MousePos()
    OnMouseReleased: (code = MOUSE_LEFT) =>
        return if code ~= MOUSE_LEFT
        @hold = false
        @SetCursor('none')
        if @holdLast > RealTime()
            @playing = true
            if not @oldPlaying
                @editorSeq = 1

    SetController: (val) => @controller = val

    OnMouseWheeled: (wheelDelta = 0) =>
        @playing = false
        @editorSeq = 1
        @targetDistToPony = math.Clamp(@targetDistToPony - wheelDelta * 10, 20, 150)
    GetModel: => @model
    GetSequence: => @seq
    GetSeq: => @seq
    GetAnimRate: => @animRate
    SetAnimRate: (val = 1) => @animRate = val
    SetSeq: (val = @SEQUENCE_STAND) =>
        @seq = val
        @model\SetSequence(@seq) if IsValid(@model)
    SetSequence: (val = @SEQUENCE_STAND) =>
        @seq = val
        @model\SetSequence(@seq) if IsValid(@model)
    ResetSequence: => @SetSequence(@SEQUENCE_STAND)
    ResetSeq: => @SetSequence(@SEQUENCE_STAND)

    ResetModel: (ponydata, model = 'models/ppm/player_default_base.mdl') =>
        @model\Remove() if IsValid(@model)
        @model = ClientsideModel(model)
        with @model
            \SetNoDraw(true)
            .__PPM2_PonyData = ponydata
        @model\SetSequence(@seq)
        @model\FrameAdvance(0)
        return @model
    Think: =>
        rtime = RealTime()
        delta = rtime - @lastTick
        @lastTick = rtime
        if IsValid(@model)
            @model\FrameAdvance(delta * @animRate)

        @hold = @IsHovered() if @hold

        if @playing
            cseq = @EDITOR_SEQUENCES[@editorSeq]
            if @nextSeq < rtime
                @editorSeq += 1
                @editorSeq = 1 if not @EDITOR_SEQUENCES[@editorSeq]
                cseq = @EDITOR_SEQUENCES[@editorSeq]
                @nextSeq = rtime + cseq.time
            @targetDistToPony, @targetAngle = cseq.func(@targetDistToPony, @targetAngle, delta)
        else
            if @hold
                x, y = gui.MousePos()
                deltaX, deltaY = x - @mouseX, y - @mouseY
                @mouseX, @mouseY = x, y
                {:pitch, :yaw, :roll} = @targetAngle
                yaw -= deltaX * .5
                pitch = math.Clamp(pitch - deltaY * .5, -40, 40)
                @targetAngle = Angle(pitch, yaw, roll)

        @angle = LerpAngle(delta * 4, @angle, @targetAngle)
        @distToPony = Lerp(delta * 4, @distToPony, @targetDistToPony)
        @vectorPos = Vector(@distToPony, 0, @PONY_VEC_Z)
        @vectorPos\Rotate(@angle)
        @drawAngle = (Vector(0, 0, @PONY_VEC_Z) - @vectorPos)\Angle()
    Paint: (w = 0, h = 0) =>
        surface.SetDrawColor(0, 0, 0)
        surface.DrawRect(0, 0, w, h)
        return if not IsValid(@model)
        x, y = @LocalToScreen(0, 0)
        cam.Start3D(@vectorPos, @drawAngle, 90, x, y, w, h)
        @controller\GetRenderController()\PreDraw(@model) if @controller
        @model\DrawModel()
        @controller\GetRenderController()\PostDraw(@model) if @controller
        cam.End3D()
}

vgui.Register('PPM2ModelPanel', MODEL_BOX_PANEL, 'EditablePanel')

PANEL_SETTINGS_BASE = {
    Init: =>
        @shouldSaveData = false
        @SetMouseInputEnabled(true)
        @SetKeyboardInputEnabled(true)
        @DockPadding(5, 5, 5, 5)
        @unsavedChanges = false
        @updateFuncs = {}
    ValueChanges: (valID, newVal, pnl) =>
        @unsavedChanges = true
        @frame.unsavedChanges = true
        @frame\SetTitle("#{@GetTargetData() and @GetTargetData()\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor; *Unsaved changes*")
    GetShouldSaveData: => @shouldSaveData
    ShouldSaveData: => @shouldSaveData
    SetShouldSaveData: (val = false) => @shouldSaveData = val
    GetTargetData: => @data
    TargetData: => @data
    SetTargetData: (val) => @data = val
    DoUpdate: => func() for func in *@updateFuncs
    NumSlider: (name = 'Slider', option = '', decimals = 0) =>
		with vgui.Create('DNumSlider', @scroll or @)
			\Dock(TOP)
			\DockMargin(2, 0, 2, 0)
			\SetTooltip("#{name}\nData value: #{option}")
			\SetText(name)
			\SetMin(0)
			\SetMax(1)
			\SetMin(@GetTargetData()["GetMin#{option}"](@GetTargetData())) if @GetTargetData()
			\SetMax(@GetTargetData()["GetMax#{option}"](@GetTargetData())) if @GetTargetData()
			\SetDecimals(decimals)
            \SetValue(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
			.TextArea\SetTextColor(color_white)
			.Label\SetTextColor(color_white)
            .OnValueChanged = (pnl, newVal = 1) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal, @GetShouldSaveData())
                @ValueChanges(option, newVal, pnl)
            table.insert @updateFuncs, ->
                \SetMin(@GetTargetData()["GetMin#{option}"](@GetTargetData())) if @GetTargetData()
                \SetMax(@GetTargetData()["GetMax#{option}"](@GetTargetData())) if @GetTargetData()
                \SetValue(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            @scroll\AddItem(_with_0) if IsValid(@scroll)
    Label: (text = '') =>
        with vgui.Create('DLabel', @scroll or @)
            \SetText(text)
            \Dock(TOP)
            \SetTextColor(color_white)
            \SizeToContents()
            w, h = \GetSize()
            \SetSize(w, h + 5)
            @scroll\AddItem(_with_0) if IsValid(@scroll)
	CheckBox: (name = 'Label', option = '') =>
		with vgui.Create('DCheckBoxLabel', @scroll or @)
			\Dock(TOP)
			\DockMargin(2, 2, 2, 2)
			\SetText(name)
			\SetTextColor(color_white)
			\SetTooltip("#{name}\nData value: #{option}")
			\SetChecked(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            .OnChange = (pnl, newVal = false) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal and 1 or 0, @GetShouldSaveData())
                @ValueChanges(option, newVal and 1 or 0, pnl)
            table.insert @updateFuncs, ->
                \SetChecked(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            @scroll\AddItem(_with_0) if IsValid(@scroll)
    ColorBox: (name = 'Colorful Box', option = '') =>
        collapse = vgui.Create('DCollapsibleCategory', @scroll or @)
        box = vgui.Create('DColorMixer', collapse)
        collapse.box = box
        with box
            \SetSize(250, 250)
			\SetTooltip("#{name}\nData value: #{option}")
            \SetColor(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            .ValueChanged = (pnl, newVal = Color(0, 0, 0)) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal, @GetShouldSaveData())
                @ValueChanges(option, newVal, pnl)
            table.insert @updateFuncs, ->
                \SetColor(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
        with collapse
            \SetContents(box)
            \Dock(TOP)
            \DockMargin(2, 2, 2, 2)
            \SetSize(250, 250)
            \SetLabel(name)
            \SetExpanded(false)
        @scroll\AddItem(collapse) if IsValid(@scroll)
        return box, collapse
    ComboBox: (name = 'Combo Box', option = '', choices) =>
        label = vgui.Create('DLabel', @scroll or @)
        with label
            \SetText(name)
            \SetTextColor(color_white)
            \Dock(TOP)
            \SetSize(0, 20)
            \DockMargin(5, 0, 5, 0)
            \SetMouseInputEnabled(true)
        @scroll\AddItem(label) if IsValid(@scroll)
        box = vgui.Create('DComboBox', label)
        with box
            \Dock(RIGHT)
            \SetSize(170, 0)
            \DockMargin(0, 0, 5, 0)
            \SetValue(@GetTargetData()["Get#{option}Enum"](@GetTargetData())) if @GetTargetData()
            if choices
                \AddChoice(choice) for choice in *choices
            else
                \AddChoice(choice) for choice in *@GetTargetData()["Get#{option}Types"](@GetTargetData()) if @GetTargetData() and @GetTargetData()["Get#{option}Types"]
            .OnSelect = (pnl = box, index = 1, value = '', data = value) ->
                index -= 1
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, index, @GetShouldSaveData())
                @ValueChanges(option, index, pnl)
            table.insert @updateFuncs, ->
                \SetValue(@GetTargetData()["Get#{option}Enum"](@GetTargetData())) if @GetTargetData()
        return box, label
    ScrollPanel: =>
        return @scroll if IsValid(@scroll)
        @scroll = vgui.Create('DScrollPanel', @)
        @scroll\Dock(FILL)
        return @scroll
    Paint: (w = 0, h = 0) =>
		surface.SetDrawColor(130, 130, 130)
		surface.DrawRect(0, 0, w, h)
}

vgui.Register('PPM2SettingsBase', PANEL_SETTINGS_BASE, 'EditablePanel')

EditorPages = {
    {
        'name': 'Main'
        'internal': 'main'
        'func': (sheet) =>
            @CheckBox('Gender', 'Gender')
            @ComboBox('Race', 'Race')
            @NumSlider('Weight', 'Weight', 2)
            @CheckBox('Socks', 'Socks')
            @ComboBox('Eyelashes', 'EyelashType')
            @ComboBox('Bodysuit', 'Bodysuit')
            @ColorBox('Body color', 'BodyColor')
    }

    {
        'name': 'Eyes'
        'internal': 'eyes'
        'func': (sheet) =>
            @ScrollPanel()
            @CheckBox('Use separated settings for eyes', 'SeparateEyes')
            for publicName in *{'', 'Left', 'Right'}
                @Label("'#{publicName}' Eye settings")
                @CheckBox("#{publicName} Eye lines", "EyeLines#{publicName}")
                @CheckBox("#{publicName} Derp eye", "DerpEyes#{publicName}")
                @NumSlider("#{publicName} Derp eye strength", "DerpEyesStrength#{publicName}", 2)
                @NumSlider("#{publicName} Eye size", "IrisSize#{publicName}", 2)
                @NumSlider("#{publicName} Eye hole width", "HoleWidth#{publicName}", 2)
                @NumSlider("#{publicName} Eye hole size", "HoleSize#{publicName}", 2)

                @ColorBox("#{publicName} Eye background", "EyeBackground#{publicName}")
                @ColorBox("#{publicName} Eye hole", "EyeHole#{publicName}")
                @ColorBox("#{publicName} Top eye iris", "EyeIrisTop#{publicName}")
                @ColorBox("#{publicName} Bottom eye iris", "EyeIrisBottom#{publicName}")
                @ColorBox("#{publicName} Eye line 1", "EyeIrisLine1#{publicName}")
                @ColorBox("#{publicName} Eye line 2", "EyeIrisLine2#{publicName}")
                @ColorBox("#{publicName} Eye line 2", "EyeIrisLine2#{publicName}")
    }

    {
        'name': 'Mane and tail'
        'internal': 'manetail'
        'func': (sheet) =>
            @ScrollPanel()
            @Label('"New" types affect only new model')
            @ComboBox('Tail type', 'TailType')
            @ComboBox('New Tail type', 'TailTypeNew')

            @ComboBox('Mane type', 'ManeType')
            @ComboBox('New Mane type', 'ManeTypeNew')

            @ComboBox('Lower Mane type', 'ManeTypeLower')
            @ComboBox('New Lower Mane type', 'ManeTypeLowerNew')

            for i = 1, 2
                @ColorBox("Mane color #{i}", "ManeColor#{i}")
                @ColorBox("Tail color #{i}", "TailColor#{i}")
            for i = 1, 6
                @ColorBox("Mane detail color #{i}", "ManeColor#{i}")
                @ColorBox("Tail detail color #{i}", "TailDetailColor#{i}")
    }

    {
        'name': 'Body details'
        'internal': 'bodydetail'
        'func': (sheet) =>
            for i = 1, PPM2.MAX_BODY_DETAILS
                @ComboBox("Detail #{i}", "BodyDetail#{i}")
                @ColorBox("Detail color #{i}", "BodyDetailColor#{i}")
    }

    {
        'name': 'Cutiemark'
        'internal': 'cmark'
        'func': (sheet) =>
            @CheckBox('Display cutiemark', 'CMark')
            @ComboBox('Cutiemark type', 'CMarkType')
            @markDisplay = vgui.Create('EditablePanel', @)
            with @markDisplay
                \Dock(TOP)
                \SetSize(320, 320)
                \DockMargin(20, 20, 20, 20)
                .Paint = (pnl, w = 0, h = 0) ->
                    data = @GetTargetData()
                    return if not data
                    controller = data\GetController()
                    return if not controller
                    rcontroller = controller\GetRenderController()
                    return if not rcontroller
                    tcontroller = rcontroller\GetTextureController()
                    return if not tcontroller
                    mat = tcontroller\GetCMarkGUI()
                    return if not mat
                    surface.SetDrawColor(0, 0, 0)
                    surface.DrawRect(0, 0, w, h)
                    surface.SetDrawColor(255, 255, 255)
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, w, h)

            @Label('Cutiemark URL image input field\nShould be PNG or JPEG (works same as\nPAC3 URL texture)')\DockMargin(5, 10, 5, 10)
            @textInput = vgui.Create('DTextEntry', @)
            with @textInput
                \Dock(TOP)
                \DockMargin(5, 10, 5, 10)
                \SetText(@GetTargetData()\GetCMarkURL())
                .OnEnter = ->
                    text = \GetValue()
                    if text\find('^https?://')
                        @GetTargetData()\SetCMarkURL(text)
                        @ValueChanges('CMarkURL', text, @textInput)
                    else
                        @GetTargetData()\SetCMarkURL('')
                        @ValueChanges('CMarkURL', '', @textInput)
                .OnKeyCodeTyped = (pnl, key = KEY_FIRST) ->
                    switch key
                        when KEY_FIRST
                            return true
                        when KEY_NONE
                            return true
                        when KEY_TAB
                            return true
                        when KEY_ENTER
                            .OnEnter()
                            \KillFocus()
                            return true
                    timer.Create 'PPM2.EditorCodeChange', 1, 1, ->
                        return if not IsValid(@textInput)
                        .OnEnter()
                table.insert @updateFuncs, ->
                    \SetText(@GetTargetData()\GetCMarkURL())
    }

    {
        'name': 'Files'
        'internal': 'saves'
        'func': (sheet) =>
            @Label('Open file by double click')
            list = vgui.Create('DListView', @)
            list\Dock(FILL)
            list\SetMultiSelect(false)
            list.DoDoubleClick = (pnl, rowID, line) ->
                fil = line\GetColumnText(1)
                if @unsavedChanges
                    confirm = ->
                        @frame.data\SetFilename(fil)
                        @frame.data\ReadFromDisk(true)
                        @frame.data\UpdateController()
                        @frame.DoUpdate()
                        @unsavedChanges = false
                        @frame.unsavedChanges = false
                        @frame\SetTitle("#{fil} - PPM2 Pony Editor")
                    Derma_Query(
                        "Currently, you did not stated your changes.\nDo you really want to open #{fil}?",
                        'Unsaved changes!',
                        'Yas!',
                        confirm,
                        'Noh!'
                    )
                else
                    @frame.data\SetFilename(fil)
                    @frame.data\ReadFromDisk(true)
                    @frame.data\UpdateController()
                    @frame.DoUpdate()
                    @unsavedChanges = false
                    @frame.unsavedChanges = false
                    @frame\SetTitle("#{fil} - PPM2 Pony Editor")
            list\AddColumn('Filename')
            @rebuildFileList = ->
                list\Clear()
                files, dirs = file.Find('ppm2/*', 'DATA')
                for fil in *files
                    matchBak = '.bak.txt'
                    continue if fil\sub(-#matchBak) == matchBak
                    list\AddLine(fil\sub(1, #fil - 4))
            @rebuildFileList()
    }
}

EditorModels = {
    'DEFAULT': 'models/ppm/player_default_base.mdl'
    'CPPM': 'models/cppm/player_default_base.mdl'
    'NEW': 'models/ppm/player_default_base_new.mdl'
}

USE_MODEL = CreateConVar('ppm2_editor_model', 'new', {FCVAR_ARCHIVE}, 'What model to use in editor. Valids are "default", "cppm", "new"')

PPM2.OpenEditor = ->
    if IsValid(PPM2.EditorFrame)
        PPM2.EditorFrame\SetVisible(true)
        PPM2.EditorFrame\Center()
        PPM2.EditorFrame\MakePopup()
        return

    frame = vgui.Create('DFrame')
    self = frame
    W, H = ScrW() - 25, ScrH() - 25
    @SetSize(W, H)
    @Center()
    @MakePopup()
    @SetTitle('PPM2 Pony Editor')
    @SetDeleteOnClose(false)
    PPM2.EditorFrame = @

    @menus = vgui.Create('DPropertySheet', @)
    @menus\Dock(LEFT)
    @menus\SetSize(370, 0)

    @model = vgui.Create('PPM2ModelPanel', @)
    @model\Dock(FILL)

    copy = PPM2.GetMainData()\Copy()
    ply = LocalPlayer()
    editorModelSelect = USE_MODEL\GetString()\upper()
    editorModelSelect = EditorModels[editorModelSelect] and editorModelSelect or 'DEFAULT'
    ent = @model\ResetModel(nil, EditorModels[editorModelSelect])
    controller = copy\CreateCustomController(ent)
    copy\SetController(controller)
    frame.controller = controller
    frame.data = copy
    frame.DoUpdate = -> pnl\DoUpdate() for i, pnl in pairs @panels

    saveAs = (callback = (->)) ->
        confirm = (txt = '') ->
            txt = txt\Trim()
            return if txt == ''
            copy\SetFilename(txt)
            copy\Save()
            @unsavedChanges = false
            @model.unsavedChanges = false
            @SetTitle("#{copy\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor")
            @panels.saves.rebuildFileList()
            callback(txt)
        Derma_StringRequest('Save as', 'Enter file name without ppm2/ and .txt', copy\GetFilename(), confirm)

    @saveButton = vgui.Create('DButton', @)
    with @saveButton
        \SetText('Save')
        \SetPos(W - 205, 5)
        \SetSize(90, 20)
        .DoClick = -> saveAs()

    @wearButton = vgui.Create('DButton', @)
    with @wearButton
        \SetText('Apply changes (wear)')
        \SetPos(W - 350, 5)
        \SetSize(140, 20)
        lastWear = 0
        .DoClick = ->
            return if RealTime() < lastWear
            lastWear = RealTime() + 5
            mainData = PPM2.GetMainData()
            copy\ApplyDataToObject(mainData, false) -- no save on apply
    @selectModelBox = vgui.Create('DComboBox', @)
    with @selectModelBox
        \SetSize(120, 20)
        \SetPos(W - 475, 5)
        \SetValue(editorModelSelect)
        \AddChoice(choice) for choice in *{'default', 'cppm', 'new'}
        .OnSelect = (pnl = box, index = 1, value = '', data = value) ->
            @SetDeleteOnClose(true)
            RunConsoleCommand('ppm2_editor_model', value)

            confirm = ->
                @Close()
                timer.Simple 0.1, PPM2.OpenEditor
            Derma_Query(
                'You should restart editor for applying change.\nRestart now?\nUnsaved data can be lost!',
                'Editor restart required',
                'Yas!',
                confirm,
                'Noh!'
            )

    @SetTitle("#{copy\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor")

    @model\SetController(controller)
    controller\SetupEntity(ent)

    @panels = {}

    for {:name, :func, :internal} in *EditorPages
        pnl = vgui.Create('PPM2SettingsBase', @menus)
        @menus\AddSheet(name, pnl)
        pnl\SetTargetData(copy)
        pnl\Dock(FILL)
        pnl.frame = @
        func(pnl, @menus)
        @panels[internal] = pnl

concommand.Add 'ppm2_editor', PPM2.OpenEditor

IconData =
	title: 'PPM V2.0',
	icon: 'gui/pped_icon.png',
	width: 960,
	height: 700,
	onewindow: true,
	init: (icon, window) ->
		window\Remove()
		RunConsoleCommand('ppm2_editor')

list.Set('DesktopWindows', 'PPM2', IconData)
CreateContextMenu() if IsValid(g_ContextMenu)

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

MODEL_BOX_PANEL = {
    SEQUENCE_STAND: 22
    SEQUENCE_FORWARD: 316
    SEQUENCE_WALK: 232
    PONY_VEC_Z: 64 * .7

    EDITOR_SEQUENCES: {
        -- idle
        {
            time: 5
            func: (pos, ang, delta) -> pos, ang
        }
    }

    Init: =>
        @animRate = 1
        @seq = @SEQUENCE_STAND
        @targetAngle = Angle(0, 0, 0)
        @angle = Angle(0, 0, 0)
        @distToPony = 100
        @targetDistToPony = 100
        @vectorPos = Vector(@distToPony, 0, @PONY_VEC_Z)
        @hold = false
        @holdLast = 0
        @mouseX, @mouseY = 0, 0
        @SetMouseInputEnabled(true)
        @editorSeq = 1
        @nextSeq = @EDITOR_SEQUENCES[@editorSeq].time + RealTime()
        @playing = true
        @lastTick = RealTime()
        @SetCursor('none')

    OnMousePressed: (code = MOUSE_LEFT) =>
        return if code ~= MOUSE_LEFT
        @hold = true
        @SetCursor('sizeall')
        @holdLast = RealTime() + .1
        @oldPlaying = @playing
        @playing = false
        @mouseX, @mouseY = gui.MousePos()
    OnMouseReleased: (code = MOUSE_LEFT) =>
        return if code ~= MOUSE_LEFT
        @hold = false
        @SetCursor('none')
        if @holdLast > RealTime()
            @playing = true
            if not @oldPlaying
                @editorSeq = 1

    SetController: (val) => @controller = val

    OnMouseWheeled: (wheelDelta = 0) =>
        @playing = false
        @editorSeq = 1
        @targetDistToPony = math.Clamp(@targetDistToPony - wheelDelta * 10, 20, 150)
    GetModel: => @model
    GetSequence: => @seq
    GetSeq: => @seq
    GetAnimRate: => @animRate
    SetAnimRate: (val = 1) => @animRate = val
    SetSeq: (val = @SEQUENCE_STAND) =>
        @seq = val
        @model\SetSequence(@seq) if IsValid(@model)
    SetSequence: (val = @SEQUENCE_STAND) =>
        @seq = val
        @model\SetSequence(@seq) if IsValid(@model)
    ResetSequence: => @SetSequence(@SEQUENCE_STAND)
    ResetSeq: => @SetSequence(@SEQUENCE_STAND)

    ResetModel: (ponydata, model = 'models/ppm/player_default_base.mdl') =>
        @model\Remove() if IsValid(@model)
        @model = ClientsideModel(model)
        with @model
            \SetNoDraw(true)
            .__PPM2_PonyData = ponydata
        @model\SetSequence(@seq)
        @model\FrameAdvance(0)
        return @model
    Think: =>
        rtime = RealTime()
        delta = rtime - @lastTick
        @lastTick = rtime
        if IsValid(@model)
            @model\FrameAdvance(delta * @animRate)

        @hold = @IsHovered() if @hold

        if @playing
            cseq = @EDITOR_SEQUENCES[@editorSeq]
            if @nextSeq < rtime
                @editorSeq += 1
                @editorSeq = 1 if not @EDITOR_SEQUENCES[@editorSeq]
                cseq = @EDITOR_SEQUENCES[@editorSeq]
                @nextSeq = rtime + cseq.time
            @targetDistToPony, @targetAngle = cseq.func(@targetDistToPony, @targetAngle, delta)
        else
            if @hold
                x, y = gui.MousePos()
                deltaX, deltaY = x - @mouseX, y - @mouseY
                @mouseX, @mouseY = x, y
                {:pitch, :yaw, :roll} = @targetAngle
                yaw -= deltaX * .5
                pitch = math.Clamp(pitch - deltaY * .5, -40, 40)
                @targetAngle = Angle(pitch, yaw, roll)

        @angle = LerpAngle(delta * 4, @angle, @targetAngle)
        @distToPony = Lerp(delta * 4, @distToPony, @targetDistToPony)
        @vectorPos = Vector(@distToPony, 0, @PONY_VEC_Z)
        @vectorPos\Rotate(@angle)
        @drawAngle = (Vector(0, 0, @PONY_VEC_Z) - @vectorPos)\Angle()
    Paint: (w = 0, h = 0) =>
        surface.SetDrawColor(0, 0, 0)
        surface.DrawRect(0, 0, w, h)
        return if not IsValid(@model)
        x, y = @LocalToScreen(0, 0)
        cam.Start3D(@vectorPos, @drawAngle, 90, x, y, w, h)
        @controller\GetRenderController()\PreDraw(@model) if @controller
        @model\DrawModel()
        @controller\GetRenderController()\PostDraw(@model) if @controller
        cam.End3D()
}

vgui.Register('PPM2ModelPanel', MODEL_BOX_PANEL, 'EditablePanel')

PANEL_SETTINGS_BASE = {
    Init: =>
        @shouldSaveData = false
        @SetMouseInputEnabled(true)
        @SetKeyboardInputEnabled(true)
        @DockPadding(5, 5, 5, 5)
        @unsavedChanges = false
        @updateFuncs = {}
    ValueChanges: (valID, newVal, pnl) =>
        @unsavedChanges = true
        @frame.unsavedChanges = true
        @frame\SetTitle("#{@GetTargetData() and @GetTargetData()\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor; *Unsaved changes*")
    GetShouldSaveData: => @shouldSaveData
    ShouldSaveData: => @shouldSaveData
    SetShouldSaveData: (val = false) => @shouldSaveData = val
    GetTargetData: => @data
    TargetData: => @data
    SetTargetData: (val) => @data = val
    DoUpdate: => func() for func in *@updateFuncs
    NumSlider: (name = 'Slider', option = '', decimals = 0) =>
		with vgui.Create('DNumSlider', @scroll or @)
			\Dock(TOP)
			\DockMargin(2, 0, 2, 0)
			\SetTooltip("#{name}\nData value: #{option}")
			\SetText(name)
			\SetMin(0)
			\SetMax(1)
			\SetMin(@GetTargetData()["GetMin#{option}"](@GetTargetData())) if @GetTargetData()
			\SetMax(@GetTargetData()["GetMax#{option}"](@GetTargetData())) if @GetTargetData()
			\SetDecimals(decimals)
            \SetValue(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
			.TextArea\SetTextColor(color_white)
			.Label\SetTextColor(color_white)
            .OnValueChanged = (pnl, newVal = 1) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal, @GetShouldSaveData())
                @ValueChanges(option, newVal, pnl)
            table.insert @updateFuncs, ->
                \SetMin(@GetTargetData()["GetMin#{option}"](@GetTargetData())) if @GetTargetData()
                \SetMax(@GetTargetData()["GetMax#{option}"](@GetTargetData())) if @GetTargetData()
                \SetValue(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            @scroll\AddItem(_with_0) if IsValid(@scroll)
    Label: (text = '') =>
        with vgui.Create('DLabel', @scroll or @)
            \SetText(text)
            \Dock(TOP)
            \SetTextColor(color_white)
            \SizeToContents()
            w, h = \GetSize()
            \SetSize(w, h + 5)
            @scroll\AddItem(_with_0) if IsValid(@scroll)
	CheckBox: (name = 'Label', option = '') =>
		with vgui.Create('DCheckBoxLabel', @scroll or @)
			\Dock(TOP)
			\DockMargin(2, 2, 2, 2)
			\SetText(name)
			\SetTextColor(color_white)
			\SetTooltip("#{name}\nData value: #{option}")
			\SetChecked(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            .OnChange = (pnl, newVal = false) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal and 1 or 0, @GetShouldSaveData())
                @ValueChanges(option, newVal and 1 or 0, pnl)
            table.insert @updateFuncs, ->
                \SetChecked(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            @scroll\AddItem(_with_0) if IsValid(@scroll)
    ColorBox: (name = 'Colorful Box', option = '') =>
        collapse = vgui.Create('DCollapsibleCategory', @scroll or @)
        box = vgui.Create('DColorMixer', collapse)
        collapse.box = box
        with box
            \SetSize(250, 250)
			\SetTooltip("#{name}\nData value: #{option}")
            \SetColor(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
            .ValueChanged = (pnl, newVal = Color(0, 0, 0)) ->
                return if option == ''
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, newVal, @GetShouldSaveData())
                @ValueChanges(option, newVal, pnl)
            table.insert @updateFuncs, ->
                \SetColor(@GetTargetData()["Get#{option}"](@GetTargetData())) if @GetTargetData()
        with collapse
            \SetContents(box)
            \Dock(TOP)
            \DockMargin(2, 2, 2, 2)
            \SetSize(250, 250)
            \SetLabel(name)
            \SetExpanded(false)
        @scroll\AddItem(collapse) if IsValid(@scroll)
        return box, collapse
    ComboBox: (name = 'Combo Box', option = '', choices) =>
        label = vgui.Create('DLabel', @scroll or @)
        with label
            \SetText(name)
            \SetTextColor(color_white)
            \Dock(TOP)
            \SetSize(0, 20)
            \DockMargin(5, 0, 5, 0)
            \SetMouseInputEnabled(true)
        @scroll\AddItem(label) if IsValid(@scroll)
        box = vgui.Create('DComboBox', label)
        with box
            \Dock(RIGHT)
            \SetSize(170, 0)
            \DockMargin(0, 0, 5, 0)
            \SetValue(@GetTargetData()["Get#{option}Enum"](@GetTargetData())) if @GetTargetData()
            if choices
                \AddChoice(choice) for choice in *choices
            else
                \AddChoice(choice) for choice in *@GetTargetData()["Get#{option}Types"](@GetTargetData()) if @GetTargetData() and @GetTargetData()["Get#{option}Types"]
            .OnSelect = (pnl = box, index = 1, value = '', data = value) ->
                index -= 1
                data = @GetTargetData()
                return if not data
                data["Set#{option}"](data, index, @GetShouldSaveData())
                @ValueChanges(option, index, pnl)
            table.insert @updateFuncs, ->
                \SetValue(@GetTargetData()["Get#{option}Enum"](@GetTargetData())) if @GetTargetData()
        return box, label
    ScrollPanel: =>
        return @scroll if IsValid(@scroll)
        @scroll = vgui.Create('DScrollPanel', @)
        @scroll\Dock(FILL)
        return @scroll
    Paint: (w = 0, h = 0) =>
		surface.SetDrawColor(130, 130, 130)
		surface.DrawRect(0, 0, w, h)
}

vgui.Register('PPM2SettingsBase', PANEL_SETTINGS_BASE, 'EditablePanel')

EditorPages = {
    {
        'name': 'Main'
        'internal': 'main'
        'func': (sheet) =>
            @CheckBox('Gender', 'Gender')
            @ComboBox('Race', 'Race')
            @NumSlider('Weight', 'Weight', 2)
            @CheckBox('Socks', 'Socks')
            @ComboBox('Eyelashes', 'EyelashType')
            @ComboBox('Bodysuit', 'Bodysuit')
            @ColorBox('Body color', 'BodyColor')
    }

    {
        'name': 'Eyes'
        'internal': 'eyes'
        'func': (sheet) =>
            @ScrollPanel()
            @CheckBox('Use separated settings for eyes', 'SeparateEyes')
            for publicName in *{'', 'Left', 'Right'}
                @Label("'#{publicName}' Eye settings")
                @CheckBox("#{publicName} Eye lines", "EyeLines#{publicName}")
                @CheckBox("#{publicName} Derp eye", "DerpEyes#{publicName}")
                @NumSlider("#{publicName} Derp eye strength", "DerpEyesStrength#{publicName}", 2)
                @NumSlider("#{publicName} Eye size", "IrisSize#{publicName}", 2)
                @NumSlider("#{publicName} Eye hole width", "HoleWidth#{publicName}", 2)
                @NumSlider("#{publicName} Eye hole size", "HoleSize#{publicName}", 2)

                @ColorBox("#{publicName} Eye background", "EyeBackground#{publicName}")
                @ColorBox("#{publicName} Eye hole", "EyeHole#{publicName}")
                @ColorBox("#{publicName} Top eye iris", "EyeIrisTop#{publicName}")
                @ColorBox("#{publicName} Bottom eye iris", "EyeIrisBottom#{publicName}")
                @ColorBox("#{publicName} Eye line 1", "EyeIrisLine1#{publicName}")
                @ColorBox("#{publicName} Eye line 2", "EyeIrisLine2#{publicName}")
                @ColorBox("#{publicName} Eye line 2", "EyeIrisLine2#{publicName}")
    }

    {
        'name': 'Mane and tail'
        'internal': 'manetail'
        'func': (sheet) =>
            @ScrollPanel()
            @Label('"New" types affect only new model')
            @ComboBox('Tail type', 'TailType')
            @ComboBox('New Tail type', 'TailTypeNew')

            @ComboBox('Mane type', 'ManeType')
            @ComboBox('New Mane type', 'ManeTypeNew')

            @ComboBox('Lower Mane type', 'ManeTypeLower')
            @ComboBox('New Lower Mane type', 'ManeTypeLowerNew')

            for i = 1, 2
                @ColorBox("Mane color #{i}", "ManeColor#{i}")
                @ColorBox("Tail color #{i}", "TailColor#{i}")
            for i = 1, 6
                @ColorBox("Mane detail color #{i}", "ManeColor#{i}")
                @ColorBox("Tail detail color #{i}", "TailDetailColor#{i}")
    }

    {
        'name': 'Body details'
        'internal': 'bodydetail'
        'func': (sheet) =>
            for i = 1, PPM2.MAX_BODY_DETAILS
                @ComboBox("Detail #{i}", "BodyDetail#{i}")
                @ColorBox("Detail color #{i}", "BodyDetailColor#{i}")
    }

    {
        'name': 'Cutiemark'
        'internal': 'cmark'
        'func': (sheet) =>
            @CheckBox('Display cutiemark', 'CMark')
            @ComboBox('Cutiemark type', 'CMarkType')
            @markDisplay = vgui.Create('EditablePanel', @)
            with @markDisplay
                \Dock(TOP)
                \SetSize(250, 320)
                .Paint = (pnl, w = 0, h = 0) ->
                    data = @GetTargetData()
                    return if not data
                    controller = data\GetController()
                    return if not controller
                    rcontroller = controller\GetRenderController()
                    return if not rcontroller
                    tcontroller = rcontroller\GetTextureController()
                    return if not tcontroller
                    mat = tcontroller\GetCMarkGUI()
                    return if not mat
                    surface.SetDrawColor(0, 0, 0)
                    surface.DrawRect(0, 0, w, h)
                    surface.SetDrawColor(255, 255, 255)
                    surface.SetMaterial(mat)
                    surface.DrawTexturedRect(0, 0, w, h)

            @Label('Cutiemark URL image input field\nShould be PNG or JPEG (works same as\nPAC3 URL texture)')\DockMargin(5, 10, 5, 10)
            @textInput = vgui.Create('DTextEntry', @)
            with @textInput
                \Dock(TOP)
                \DockMargin(5, 10, 5, 10)
                \SetText(@GetTargetData()\GetCMarkURL())
                .OnEnter = ->
                    text = \GetValue()
                    if text\find('^https?://')
                        @GetTargetData()\SetCMarkURL(text)
                        @ValueChanges('CMarkURL', text, @textInput)
                .OnKeyCodeTyped = (pnl, key = KEY_FIRST) ->
                    switch key
                        when KEY_FIRST
                            return true
                        when KEY_NONE
                            return true
                        when KEY_TAB
                            return true
                        when KEY_ENTER
                            .OnEnter()
                            \KillFocus()
                            return true
                    timer.Create 'PPM2.EditorCodeChange', 1, 1, ->
                        return if not IsValid(@textInput)
                        .OnEnter()
                table.insert @updateFuncs, ->
                    \SetText(@GetTargetData()\GetCMarkURL())
    }

    {
        'name': 'Files'
        'internal': 'saves'
        'func': (sheet) =>
            @Label('Open file by double click')
            list = vgui.Create('DListView', @)
            list\Dock(FILL)
            list\SetMultiSelect(false)
            list.DoDoubleClick = (pnl, rowID, line) ->
                fil = line\GetColumnText(1)
                if @unsavedChanges
                    confirm = ->
                        @frame.data\SetFilename(fil)
                        @frame.data\ReadFromDisk(true)
                        @frame.data\UpdateController()
                        @frame.DoUpdate()
                        @unsavedChanges = false
                        @frame.unsavedChanges = false
                        @frame\SetTitle("#{fil} - PPM2 Pony Editor")
                    Derma_Query(
                        "Currently, you did not stated your changes.\nDo you really want to open #{fil}?",
                        'Unsaved changes!',
                        'Yas!',
                        confirm,
                        'Noh!'
                    )
                else
                    @frame.data\SetFilename(fil)
                    @frame.data\ReadFromDisk(true)
                    @frame.data\UpdateController()
                    @frame.DoUpdate()
                    @unsavedChanges = false
                    @frame.unsavedChanges = false
                    @frame\SetTitle("#{fil} - PPM2 Pony Editor")
            list\AddColumn('Filename')
            @rebuildFileList = ->
                list\Clear()
                files, dirs = file.Find('ppm2/*', 'DATA')
                for fil in *files
                    matchBak = '.bak.txt'
                    continue if fil\sub(-#matchBak) == matchBak
                    list\AddLine(fil\sub(1, #fil - 4))
            @rebuildFileList()
    }
}

EditorModels = {
    'DEFAULT': 'models/ppm/player_default_base.mdl'
    'CPPM': 'models/cppm/player_default_base.mdl'
    'NEW': 'models/ppm/player_default_base_new.mdl'
}

USE_MODEL = CreateConVar('ppm2_editor_model', 'new', {FCVAR_ARCHIVE}, 'What model to use in editor. Valids are "default", "cppm", "new"')

PPM2.OpenEditor = ->
    if IsValid(PPM2.EditorFrame)
        PPM2.EditorFrame\SetVisible(true)
        PPM2.EditorFrame\Center()
        PPM2.EditorFrame\MakePopup()
        return

    frame = vgui.Create('DFrame')
    self = frame
    W, H = ScrW() - 25, ScrH() - 25
    @SetSize(W, H)
    @Center()
    @MakePopup()
    @SetTitle('PPM2 Pony Editor')
    @SetDeleteOnClose(false)
    PPM2.EditorFrame = @

    @menus = vgui.Create('DPropertySheet', @)
    @menus\Dock(LEFT)
    @menus\SetSize(370, 0)

    @model = vgui.Create('PPM2ModelPanel', @)
    @model\Dock(FILL)

    copy = PPM2.GetMainData()\Copy()
    ply = LocalPlayer()
    editorModelSelect = USE_MODEL\GetString()\upper()
    editorModelSelect = EditorModels[editorModelSelect] and editorModelSelect or 'DEFAULT'
    ent = @model\ResetModel(nil, EditorModels[editorModelSelect])
    controller = copy\CreateCustomController(ent)
    copy\SetController(controller)
    frame.controller = controller
    frame.data = copy
    frame.DoUpdate = -> pnl\DoUpdate() for i, pnl in pairs @panels

    saveAs = (callback = (->)) ->
        confirm = (txt = '') ->
            txt = txt\Trim()
            return if txt == ''
            copy\SetFilename(txt)
            copy\Save()
            @unsavedChanges = false
            @model.unsavedChanges = false
            @SetTitle("#{copy\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor")
            @panels.saves.rebuildFileList()
            callback(txt)
        Derma_StringRequest('Save as', 'Enter file name without ppm2/ and .txt', copy\GetFilename(), confirm)

    @saveButton = vgui.Create('DButton', @)
    with @saveButton
        \SetText('Save')
        \SetPos(W - 205, 5)
        \SetSize(90, 20)
        .DoClick = -> saveAs()

    @wearButton = vgui.Create('DButton', @)
    with @wearButton
        \SetText('Apply changes (wear)')
        \SetPos(W - 350, 5)
        \SetSize(140, 20)
        lastWear = 0
        .DoClick = ->
            return if RealTime() < lastWear
            lastWear = RealTime() + 5
            mainData = PPM2.GetMainData()
            copy\ApplyDataToObject(mainData, false) -- no save on apply
    @selectModelBox = vgui.Create('DComboBox', @)
    with @selectModelBox
        \SetSize(120, 20)
        \SetPos(W - 475, 5)
        \SetValue(editorModelSelect)
        \AddChoice(choice) for choice in *{'default', 'cppm', 'new'}
        .OnSelect = (pnl = box, index = 1, value = '', data = value) ->
            @SetDeleteOnClose(true)
            RunConsoleCommand('ppm2_editor_model', value)

            confirm = ->
                @Close()
                timer.Simple 0.1, PPM2.OpenEditor
            Derma_Query(
                'You should restart editor for applying change.\nRestart now?\nUnsaved data can be lost!',
                'Editor restart required',
                'Yas!',
                confirm,
                'Noh!'
            )

    @SetTitle("#{copy\GetFilename() or '%ERRNAME%'} - PPM2 Pony Editor")

    @model\SetController(controller)
    controller\SetupEntity(ent)

    @panels = {}

    for {:name, :func, :internal} in *EditorPages
        pnl = vgui.Create('PPM2SettingsBase', @menus)
        @menus\AddSheet(name, pnl)
        pnl\SetTargetData(copy)
        pnl\Dock(FILL)
        pnl.frame = @
        func(pnl, @menus)
        @panels[internal] = pnl

concommand.Add 'ppm2_editor', PPM2.OpenEditor

IconData =
	title: 'PPM V2.0',
	icon: 'gui/pped_icon.png',
	width: 960,
	height: 700,
	onewindow: true,
	init: (icon, window) ->
		window\Remove()
		RunConsoleCommand('ppm2_editor')

list.Set('DesktopWindows', 'PPM2', IconData)
CreateContextMenu() if IsValid(g_ContextMenu)


naustver: use more moonismes

naustver: use more moonismes

Former-commit-id: 615311c96b0ca5224f500ab20173a666e7b4a552 [formerly 9fa65b9e43555d633568c434f938d448e1ea4138]
Former-commit-id: 7f0815eac6cf3f47bbc7ad27f1cbdc0d14ac1083

simplehttp = require'simplehttp'
json = require'json'

APIBase = 'http://yr.hveem.no/api/now'

PRIVMSG:
  '^%pvêr$': (source, destination, input) =>
    simplehttp APIBase, (data) ->
      result = json.decode data
      if result
        result = result[1]
        temp = result.outtemp
        ws = result.windspeed / 3.6 -- correct unit
        rain = result.dayrain
        windtext = ''
        if ws > 32
            windtext = 'orkan!'
        elseif ws > 28.5
            windtext = 'sterk storm'
        elseif ws > 24.5
            windtext = 'full storm'
        elseif ws > 20.8
            windtext = 'liten storm'
        elseif ws > 17.2
            windtext = 'sterk kuling'
        elseif ws > 13.9
            windtext = 'stiv kuling'
        elseif ws > 10.8
            windtext = 'liten kuling'
        elseif ws > 8
            windtext = 'frisk bris'
        elseif ws > 5.5
            windtext = 'laber bris'
        elseif ws > 3.4
            windtext = 'lett bris'
        elseif ws > 1.5
            windtext = 'svak vind'
        elseif ws > 0.3
            windtext = 'flau vind'
        else
            windtext = 'vindstille'

        @Msg 'privmsg', destination, source, '\002%.1f\002 °C, \002%.1f\002 m/s (%s), \002%.1f\002 mm nedbør', temp, ws, windtext, rain

simplehttp = require'simplehttp'
json = require'json'

APIBase = 'http://yr.hveem.no/api/now'

PRIVMSG:
  '^%pvêr$': (self, source, destination, input) ->
    simplehttp APIBase, (data) ->
      result = json.decode data
      if result
        result = result[1]
        temp = result.outtemp
        ws = result.windspeed
        rain = result.dayrain
        windtext = ''
        if ws > 32
            windtext = 'orkan!'
        elseif ws > 28.5
            windtext = 'sterk storm'
        elseif ws > 24.5
            windtext = 'full storm'
        elseif ws > 20.8
            windtext = 'liten storm'
        elseif ws > 17.2
            windtext = 'sterk kuling'
        elseif ws > 13.9
            windtext = 'stiv kuling'
        elseif ws > 10.8
            windtext = 'liten kuling'
        elseif ws > 8
            windtext = 'frisk bris'
        elseif ws > 5.5
            windtext = 'laber bris'
        elseif ws > 3.4
            windtext = 'lett bris'
        elseif ws > 1.5
            windtext = 'svak vind'
        elseif ws > 0.3
            windtext = 'flau vind'
        else
            windtext = 'vindstille'

        self\Msg 'privmsg', destination, source, '\002%.1f\002 °C, \002%.1f\002 m/s (%s), \002%.1f\002 mm nedbør', temp, ws, windtext, rain