From 1e299dfa8430bdfbfc9ce1fc9f30bb81498595f6 Mon Sep 17 00:00:00 2001
From: Patrick Schwarzer <patrickschwarzer2000@gmail.com>
Date: Mon, 14 Apr 2025 03:58:35 +0200
Subject: [PATCH] classes, better debugging and spritesheets

---
 classes.lua            | 160 +++++++++++++++--
 games/map.json         |   0
 games/test.lua         |  30 ++--
 games/testSound.mp3    | Bin 0 -> 5504 bytes
 games/testSprites.json |  18 ++
 games/tileset.png      | Bin 0 -> 2375 bytes
 libraries/json.lua     | 400 +++++++++++++++++++++++++++++++++++++++++
 main.lua               | 114 ++++++++----
 8 files changed, 660 insertions(+), 62 deletions(-)
 create mode 100644 games/map.json
 create mode 100644 games/testSound.mp3
 create mode 100644 games/testSprites.json
 create mode 100644 games/tileset.png
 create mode 100644 libraries/json.lua

diff --git a/classes.lua b/classes.lua
index 5c4152f..01721ec 100644
--- a/classes.lua
+++ b/classes.lua
@@ -1,4 +1,27 @@
 
+local function newImage(path, alphaColor)
+    if (not alphaColor) then
+        return love.graphics.newImage(path)
+    end
+
+	local imageData = love.image.newImageData(path)
+
+	imageData:mapPixel(function(x, y, r, g, b, a)
+        if (r == alphaColor.r and
+            g == alphaColor.g and
+            b == alphaColor.b and
+            a == alphaColor.a) then
+            return 0, 0, 0, 0
+        end
+
+		return r, g, b, a
+	end)
+
+    local image = love.graphics.newImage(imageData)
+
+    return image
+end
+
 --[[
     Sprite Class
     This is essentially just a Wrapper for LÖVEs Image
@@ -7,10 +30,10 @@
 Sprite = {}
 Sprite.__index = Sprite
 
-function Sprite.new(path)
+function Sprite.new(path, alphaColor)
     local self = setmetatable({}, Sprite)
 
-    self.image = love.graphics.newImage(path)
+    self.image = newImage(path, alphaColor)
 
     return self
 end
@@ -21,41 +44,150 @@ end
 
 
 --[[
-    Audio Class
+    Spritesheet Class
+    This is essentially just a Wrapper for LÖVEs Image, specifically made for Spritesheets
+]]--
+
+Spritesheet = {}
+Spritesheet.__index = Spritesheet
+
+function Spritesheet.new(path, width, height, alphaColor)
+    local self = setmetatable({}, Spritesheet)
+
+    self.sprites = {}
+
+    if (string.find(path, ".json")) then
+        local contents, size = love.filesystem.read(path)
+        local sliceData = json.decode(contents)
+
+        print("games/" .. sliceData.image)
+        self.image = newImage("games/" .. sliceData.image, alphaColor)
+
+        -- Create the slices according to the sliceData
+        local imageWidth, imageHeight = self.image:getWidth(), self.image:getHeight()
+
+        local counter = 0
+        for _, data in pairs(sliceData) do
+            table.insert(self.sprites, love.graphics.newQuad(
+                data.x,
+                data.y,
+                data.width,
+                data.height,
+                imageWidth,
+                imageHeight
+            ))
+            counter = counter + 1
+        end
+
+        self.Count = counter
+
+        return self
+    end
+
+    self.image = newImage(path, alphaColor)
+
+    -- Generate slices
+    local imageWidth, imageHeight = self.image:getWidth(), self.image:getHeight()
+    local columns = math.floor(imageWidth / width)
+    local rows = math.floor(imageHeight / height)
+
+    local counter = 0
+    for x = 0, columns - 1 do
+        for y = 0, rows - 1 do
+            table.insert(self.sprites, love.graphics.newQuad(
+                x * width,
+                y * height,
+                width,
+                height,
+                imageWidth,
+                imageHeight
+            ))
+            counter = counter + 1
+        end
+    end
+
+    self.Count = counter
+
+    return self
+end
+
+function Spritesheet:Draw(slice, x, y)
+    local sprite = self.sprites[slice]
+    if sprite then
+        love.graphics.draw(self.image, sprite, x or 0, y or 0)
+    end
+end
+
+
+--[[
+    Sound Class
     This is essentially just a Wrapper for LÖVEs Source
 ]]--
 
-Audio = {}
-Audio.__index = Audio
+Sound = {}
+Sound.__index = Sound
 
-function Audio.new(path, soundType)
-    local self = setmetatable({}, Audio)
+function Sound.new(path, soundType)
+    local self = setmetatable({}, Sound)
 
     self.source = love.audio.newSource(path, soundType or "static")
 
     return self
 end
 
-function Audio:Play()
+function Sound:Play()
     self.source:play()
 end
 
-function Audio:Stop()
+function Sound:Stop()
     self.source:stop()
 end
 
-function Audio:Pause()
+function Sound:Pause()
     self.source:pause()
 end
 
-function Audio:Resume()
+function Sound:Resume()
     self.source:play()
 end
 
-function Audio:SetLooping(bool)
+function Sound:SetLooping(bool)
     self.source:setLooping(bool)
 end
 
-function Audio:SetVolume(volume)
+function Sound:SetVolume(volume)
     self.source:setVolume(volume)
-end
\ No newline at end of file
+end
+
+
+--[[
+    Color Class
+    A convenient Color class that works with any love2D function
+]]--
+
+function Color(r, g, b, a)
+    r = (r or 255) / 255
+    g = (g or 255) / 255
+    b = (b or 255) / 255
+    a = (a or 255) / 255
+
+    local color = {
+        r = r, g = g, b = b, a = a,
+        [1] = r, [2] = g, [3] = b, [4] = a -- love2D expects a sequential array, so heres the fix for that
+    }
+
+    -- Return it as a class, that way we can use it freely anywhere we wish
+    return setmetatable(color, {
+        __index = function(tbl, key)
+            if key == 1 then return tbl.r
+            elseif key == 2 then return tbl.g
+            elseif key == 3 then return tbl.b
+            elseif key == 4 then return tbl.a
+            else return rawget(tbl, key)
+            end
+        end,
+        __tostring = function(tbl)
+            return tbl
+        end
+    })
+end
diff --git a/games/map.json b/games/map.json
new file mode 100644
index 0000000..e69de29
diff --git a/games/test.lua b/games/test.lua
index 66af9ad..ea70599 100644
--- a/games/test.lua
+++ b/games/test.lua
@@ -3,26 +3,36 @@ NAME = "Test"
 AUTHOR = "Tarion"
 VERSION = 0
 
-local test = 0
-local delay = 0
-
-local last = ""
-
+local backgroundColor = Color(48, 104, 20, 255)
 function Load()
+    SetBackgroundColor(backgroundColor)
 end
 
 function Update(dt, curTime)
 end
 
-local sprite = Sprite("testSprites.png")
+local sprite = Spritesheet("testSprites.json", 16, 16, Color(48, 104, 80, 255))
+local testSound = Sound("testSound.mp3", "static")
+local test = 1
 function KeyPressed(key)
-    if (key == "f5") then
+    if (key == "f3") then
+        test = test - 1
+
+        if (test == 0) then
+            test = sprite.Count
+        end
+    elseif (key == "f4") then
         test = test + 1
-        sprite = Sprite("testSprites.png")
+
+        if (test == sprite.Count + 1) then
+            test = 1
+        end
+    elseif (key == "f2") then
+        testSound:Play()
     end
 end
 
 function Draw()
-    print("Loaded Sprites: " .. test, 10, 10)
-    sprite:Draw(100, 0)
+    DrawText(test .. "/" .. sprite.Count, 25, 25)
+    sprite:Draw(test, 50, 0)
 end
diff --git a/games/testSound.mp3 b/games/testSound.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..8e64435ac5c63491e4cb272974d1a366c50bdb50
GIT binary patch
literal 5504
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
L|4;sZ(}Dj1SBJz)

literal 0
HcmV?d00001

diff --git a/games/testSprites.json b/games/testSprites.json
new file mode 100644
index 0000000..e806425
--- /dev/null
+++ b/games/testSprites.json
@@ -0,0 +1,18 @@
+[
+    {
+        "image": "testSprites.png"
+    },
+    {"x":0,"y":0,"width":64,"height":24},
+    {"x":64,"y":0,"width":16,"height":24},
+    {"x":64,"y":0,"width":16,"height":24},
+    {"x":0,"y":24,"width":64,"height":24},
+    {"x":80,"y":52,"width":8,"height":12},
+    {"x":80,"y":68,"width":8,"height":12},
+    {"x":0,"y":72,"width":80,"height":24},
+    {"x":90,"y":48,"width":14,"height":48},
+    {"x":72,"y":96,"width":32,"height":15},
+    {"x":32,"y":96,"width":40,"height":15},
+    {"x":24,"y":96,"width":8,"height":8},
+    {"x":16,"y":96,"width":8,"height":8},
+    {"x":16,"y":104,"width":8,"height":8},
+    {"x":0,"y":96,"width":16,"height":16},{"x":0,"y":112,"width":32,"height":24},{"x":32,"y":111,"width":32,"height":24},{"x":72,"y":136,"width":32,"height":24}]
\ No newline at end of file
diff --git a/games/tileset.png b/games/tileset.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b21c11a8e151c4b29a85acb53e14d5c5b1cab8a
GIT binary patch
literal 2375
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

literal 0
HcmV?d00001

diff --git a/libraries/json.lua b/libraries/json.lua
new file mode 100644
index 0000000..098e7b2
--- /dev/null
+++ b/libraries/json.lua
@@ -0,0 +1,400 @@
+--
+-- json.lua
+--
+-- Copyright (c) 2019 rxi
+--
+-- 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.
+--
+
+local json = { _version = "0.1.2" }
+
+-------------------------------------------------------------------------------
+-- Encode
+-------------------------------------------------------------------------------
+
+local encode
+
+local escape_char_map = {
+  [ "\\" ] = "\\\\",
+  [ "\"" ] = "\\\"",
+  [ "\b" ] = "\\b",
+  [ "\f" ] = "\\f",
+  [ "\n" ] = "\\n",
+  [ "\r" ] = "\\r",
+  [ "\t" ] = "\\t",
+}
+
+local escape_char_map_inv = { [ "\\/" ] = "/" }
+for k, v in pairs(escape_char_map) do
+  escape_char_map_inv[v] = k
+end
+
+
+local function escape_char(c)
+  return escape_char_map[c] or string.format("\\u%04x", c:byte())
+end
+
+
+local function encode_nil(val)
+  return "null"
+end
+
+
+local function encode_table(val, stack)
+  local res = {}
+  stack = stack or {}
+
+  -- Circular reference?
+  if stack[val] then error("circular reference") end
+
+  stack[val] = true
+
+  if rawget(val, 1) ~= nil or next(val) == nil then
+    -- Treat as array -- check keys are valid and it is not sparse
+    local n = 0
+    for k in pairs(val) do
+      if type(k) ~= "number" then
+        error("invalid table: mixed or invalid key types")
+      end
+      n = n + 1
+    end
+    if n ~= #val then
+      error("invalid table: sparse array")
+    end
+    -- Encode
+    for i, v in ipairs(val) do
+      table.insert(res, encode(v, stack))
+    end
+    stack[val] = nil
+    return "[" .. table.concat(res, ",") .. "]"
+
+  else
+    -- Treat as an object
+    for k, v in pairs(val) do
+      if type(k) ~= "string" then
+        error("invalid table: mixed or invalid key types")
+      end
+      table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
+    end
+    stack[val] = nil
+    return "{" .. table.concat(res, ",") .. "}"
+  end
+end
+
+
+local function encode_string(val)
+  return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
+end
+
+
+local function encode_number(val)
+  -- Check for NaN, -inf and inf
+  if val ~= val or val <= -math.huge or val >= math.huge then
+    error("unexpected number value '" .. tostring(val) .. "'")
+  end
+  return string.format("%.14g", val)
+end
+
+
+local type_func_map = {
+  [ "nil"     ] = encode_nil,
+  [ "table"   ] = encode_table,
+  [ "string"  ] = encode_string,
+  [ "number"  ] = encode_number,
+  [ "boolean" ] = tostring,
+}
+
+
+encode = function(val, stack)
+  local t = type(val)
+  local f = type_func_map[t]
+  if f then
+    return f(val, stack)
+  end
+  error("unexpected type '" .. t .. "'")
+end
+
+
+function json.encode(val)
+  return ( encode(val) )
+end
+
+
+-------------------------------------------------------------------------------
+-- Decode
+-------------------------------------------------------------------------------
+
+local parse
+
+local function create_set(...)
+  local res = {}
+  for i = 1, select("#", ...) do
+    res[ select(i, ...) ] = true
+  end
+  return res
+end
+
+local space_chars   = create_set(" ", "\t", "\r", "\n")
+local delim_chars   = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
+local escape_chars  = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
+local literals      = create_set("true", "false", "null")
+
+local literal_map = {
+  [ "true"  ] = true,
+  [ "false" ] = false,
+  [ "null"  ] = nil,
+}
+
+
+local function next_char(str, idx, set, negate)
+  for i = idx, #str do
+    if set[str:sub(i, i)] ~= negate then
+      return i
+    end
+  end
+  return #str + 1
+end
+
+
+local function decode_error(str, idx, msg)
+  local line_count = 1
+  local col_count = 1
+  for i = 1, idx - 1 do
+    col_count = col_count + 1
+    if str:sub(i, i) == "\n" then
+      line_count = line_count + 1
+      col_count = 1
+    end
+  end
+  error( string.format("%s at line %d col %d", msg, line_count, col_count) )
+end
+
+
+local function codepoint_to_utf8(n)
+  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
+  local f = math.floor
+  if n <= 0x7f then
+    return string.char(n)
+  elseif n <= 0x7ff then
+    return string.char(f(n / 64) + 192, n % 64 + 128)
+  elseif n <= 0xffff then
+    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
+  elseif n <= 0x10ffff then
+    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
+                       f(n % 4096 / 64) + 128, n % 64 + 128)
+  end
+  error( string.format("invalid unicode codepoint '%x'", n) )
+end
+
+
+local function parse_unicode_escape(s)
+  local n1 = tonumber( s:sub(3, 6),  16 )
+  local n2 = tonumber( s:sub(9, 12), 16 )
+  -- Surrogate pair?
+  if n2 then
+    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
+  else
+    return codepoint_to_utf8(n1)
+  end
+end
+
+
+local function parse_string(str, i)
+  local has_unicode_escape = false
+  local has_surrogate_escape = false
+  local has_escape = false
+  local last
+  for j = i + 1, #str do
+    local x = str:byte(j)
+
+    if x < 32 then
+      decode_error(str, j, "control character in string")
+    end
+
+    if last == 92 then -- "\\" (escape char)
+      if x == 117 then -- "u" (unicode escape sequence)
+        local hex = str:sub(j + 1, j + 5)
+        if not hex:find("%x%x%x%x") then
+          decode_error(str, j, "invalid unicode escape in string")
+        end
+        if hex:find("^[dD][89aAbB]") then
+          has_surrogate_escape = true
+        else
+          has_unicode_escape = true
+        end
+      else
+        local c = string.char(x)
+        if not escape_chars[c] then
+          decode_error(str, j, "invalid escape char '" .. c .. "' in string")
+        end
+        has_escape = true
+      end
+      last = nil
+
+    elseif x == 34 then -- '"' (end of string)
+      local s = str:sub(i + 1, j - 1)
+      if has_surrogate_escape then
+        s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
+      end
+      if has_unicode_escape then
+        s = s:gsub("\\u....", parse_unicode_escape)
+      end
+      if has_escape then
+        s = s:gsub("\\.", escape_char_map_inv)
+      end
+      return s, j + 1
+
+    else
+      last = x
+    end
+  end
+  decode_error(str, i, "expected closing quote for string")
+end
+
+
+local function parse_number(str, i)
+  local x = next_char(str, i, delim_chars)
+  local s = str:sub(i, x - 1)
+  local n = tonumber(s)
+  if not n then
+    decode_error(str, i, "invalid number '" .. s .. "'")
+  end
+  return n, x
+end
+
+
+local function parse_literal(str, i)
+  local x = next_char(str, i, delim_chars)
+  local word = str:sub(i, x - 1)
+  if not literals[word] then
+    decode_error(str, i, "invalid literal '" .. word .. "'")
+  end
+  return literal_map[word], x
+end
+
+
+local function parse_array(str, i)
+  local res = {}
+  local n = 1
+  i = i + 1
+  while 1 do
+    local x
+    i = next_char(str, i, space_chars, true)
+    -- Empty / end of array?
+    if str:sub(i, i) == "]" then
+      i = i + 1
+      break
+    end
+    -- Read token
+    x, i = parse(str, i)
+    res[n] = x
+    n = n + 1
+    -- Next token
+    i = next_char(str, i, space_chars, true)
+    local chr = str:sub(i, i)
+    i = i + 1
+    if chr == "]" then break end
+    if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
+  end
+  return res, i
+end
+
+
+local function parse_object(str, i)
+  local res = {}
+  i = i + 1
+  while 1 do
+    local key, val
+    i = next_char(str, i, space_chars, true)
+    -- Empty / end of object?
+    if str:sub(i, i) == "}" then
+      i = i + 1
+      break
+    end
+    -- Read key
+    if str:sub(i, i) ~= '"' then
+      decode_error(str, i, "expected string for key")
+    end
+    key, i = parse(str, i)
+    -- Read ':' delimiter
+    i = next_char(str, i, space_chars, true)
+    if str:sub(i, i) ~= ":" then
+      decode_error(str, i, "expected ':' after key")
+    end
+    i = next_char(str, i + 1, space_chars, true)
+    -- Read value
+    val, i = parse(str, i)
+    -- Set
+    res[key] = val
+    -- Next token
+    i = next_char(str, i, space_chars, true)
+    local chr = str:sub(i, i)
+    i = i + 1
+    if chr == "}" then break end
+    if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
+  end
+  return res, i
+end
+
+
+local char_func_map = {
+  [ '"' ] = parse_string,
+  [ "0" ] = parse_number,
+  [ "1" ] = parse_number,
+  [ "2" ] = parse_number,
+  [ "3" ] = parse_number,
+  [ "4" ] = parse_number,
+  [ "5" ] = parse_number,
+  [ "6" ] = parse_number,
+  [ "7" ] = parse_number,
+  [ "8" ] = parse_number,
+  [ "9" ] = parse_number,
+  [ "-" ] = parse_number,
+  [ "t" ] = parse_literal,
+  [ "f" ] = parse_literal,
+  [ "n" ] = parse_literal,
+  [ "[" ] = parse_array,
+  [ "{" ] = parse_object,
+}
+
+
+parse = function(str, idx)
+  local chr = str:sub(idx, idx)
+  local f = char_func_map[chr]
+  if f then
+    return f(str, idx)
+  end
+  decode_error(str, idx, "unexpected character '" .. chr .. "'")
+end
+
+
+function json.decode(str)
+  if type(str) ~= "string" then
+    error("expected argument of type string, got " .. type(str))
+  end
+  local res, idx = parse(str, next_char(str, 1, space_chars, true))
+  idx = next_char(str, idx, space_chars, true)
+  if idx <= #str then
+    decode_error(str, idx, "trailing garbage")
+  end
+  return res
+end
+
+
+return json
diff --git a/main.lua b/main.lua
index d3dc6a8..08a522b 100644
--- a/main.lua
+++ b/main.lua
@@ -1,8 +1,15 @@
 
+if arg[2] == "debug" then
+    require("lldebugger").start()
+end
+
+
+json = require("libraries/json")
+
 require("classes")
 
 local LoadObject = {
-    ["sprite"] = function(name)
+    ["sprite"] = function(name, alphaColor)
         name = "games/" .. name
     
         local info = love.filesystem.getInfo(name)
@@ -15,10 +22,10 @@ local LoadObject = {
             return
         end
 
-        local newObject = Sprite.new(name)
+        local newObject = Sprite.new(name, alphaColor)
 
         virtual.loadedObjects[newObject] = {
-            object = newSprite,
+            object = newObject,
             size = info.size,
         }
         virtual.currentMemory = virtual.currentMemory + info.size
@@ -26,7 +33,32 @@ local LoadObject = {
 
         return newObject
     end,
-    ["audio"] = function(name, soundType)
+    ["spritesheet"] = function(name, width, height, alphaColor)
+        name = "games/" .. name
+        height = height or width
+
+        local info = love.filesystem.getInfo(name)
+        if (not info) then
+            error("Failed to load Object (FILE NOT FOUND) ("..name..")")
+            return
+        end
+        if ((info.size + virtual.currentMemory) > virtual.maxMemory) then
+            error("Failed to load Object (MAX MEMORY) ("..name..")")
+            return
+        end
+
+        local newObject = Spritesheet.new(name, width, height, alphaColor)
+
+        virtual.loadedObjects[newObject] = {
+            object = newObject,
+            size = info.size,
+        }
+        virtual.currentMemory = virtual.currentMemory + info.size
+        virtual.loadedCount = virtual.loadedCount + 1
+
+        return newObject
+    end,
+    ["sound"] = function(name, soundType)
         name = "games/" .. name
         soundType = soundType or "static"
 
@@ -40,10 +72,10 @@ local LoadObject = {
             return
         end
 
-        local newObject = Audio.new(name, soundType)
+        local newObject = Sound.new(name, soundType)
 
         virtual.loadedObjects[newObject] = {
-            object = newSprite,
+            object = newObject,
             size = info.size,
         }
         virtual.currentMemory = virtual.currentMemory + info.size
@@ -53,6 +85,10 @@ local LoadObject = {
     end,
 }
 
+local function SetBackgroundColor(color)
+    love.graphics.setBackgroundColor(color)
+end
+
 -- Establish Sandbox
 -- Everything in the table is what the Sandbox has access to
 local consoleEnv = {
@@ -65,17 +101,18 @@ local consoleEnv = {
     Draw,
     KeyPressed,
 
-    Sprite2 = LoadSprite,
     Sprite = LoadObject["sprite"],
+    Spritesheet = LoadObject["spritesheet"],
+    Sound = LoadObject["sound"],
 
-    print = love.graphics.print,
+    Color = Color,
+
+    SetBackgroundColor = SetBackgroundColor,
+    DrawText = love.graphics.print,
+
+    print = print,
 }
 consoleEnv._G = consoleEnv
-setmetatable(consoleEnv, {
-    __index = function(_, key)
-        return function(...) end -- Return an empty function to avoid erroring, not perfect, but works most of the time
-    end
-})
 
 function loadGame(gameName)
     local fn, err = loadfile("games/" .. gameName .. ".lua", "t", consoleEnv)
@@ -83,9 +120,9 @@ function loadGame(gameName)
         error("Failed to load sandbox file (1): " .. err)
     end
 
-    local ok, execErr = pcall(fn)
+    local ok, execErr = xpcall(fn, debug.traceback)
     if not ok then
-        error("Error running sandbox file (2): " .. execErr)
+        error("Error running sandbox file (2):\n" .. execErr)
     end
 
     love.window.setTitle(consoleEnv.NAME or "NULL")
@@ -100,7 +137,6 @@ function love.load()
     -- Console Variables
     curTime = 0
     scaling = 4
-    keysPressed = {}
 
     -- Setup Window
     love.graphics.setDefaultFilter("nearest", "nearest")
@@ -118,7 +154,6 @@ function love.load()
 
     loadGame("test")
 
-
     if (consoleEnv.Load) then
         consoleEnv.Load()
     end
@@ -128,26 +163,7 @@ function love.keypressed(key)
     if key == "escape" then
         love.event.quit()
     end
-
-    keysPressed[key] = true
-
-    if (consoleEnv.KeyPressed) then
-        consoleEnv.KeyPressed(key)
-    end
-end
-
-function love.keyboard.wasPressed(key)
-    return keysPressed[key]
-end
-
-function love.window.updateWindow()
-    love.window.setMode(scaling*240, scaling*160, {resizable=false, vsync=0})
-end
-
-function love.update(dt)
-    curTime = curTime + dt
-
-    if (love.keyboard.wasPressed("f1")) then
+    if (key == "f1") then
         scaling = scaling + 1
 
         if (scaling == maxScaling+1) then
@@ -157,7 +173,17 @@ function love.update(dt)
         love.window.updateWindow()
     end
 
-    keysPressed = {}
+    if (consoleEnv.KeyPressed) then
+        consoleEnv.KeyPressed(key)
+    end
+end
+
+function love.window.updateWindow()
+    love.window.setMode(scaling*240, scaling*160, {resizable=false, vsync=0})
+end
+
+function love.update(dt)
+    curTime = curTime + dt
 
     if (consoleEnv.Update) then
         consoleEnv.Update(dt, curTime)
@@ -176,3 +202,15 @@ function love.draw()
         consoleEnv.Draw()
     end
 end
+
+
+
+local love_errorhandler = love.errhand
+
+function love.errorhandler(msg)
+    if lldebugger then
+        error(msg, 2)
+    else
+        return love_errorhandler(msg)
+    end
+end
\ No newline at end of file