summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/color.moon26
-rw-r--r--src/ecs.moon88
-rw-r--r--src/js_bridge.js70
-rw-r--r--src/log.moon66
-rw-r--r--src/main.lua29
-rw-r--r--src/net.moon148
-rw-r--r--src/net_test.moon64
m---------src/party/hardoncollider0
-rw-r--r--src/preload.lua71
-rw-r--r--src/shader_shim.moon20
-rw-r--r--src/shaders/stars.lua76
-rw-r--r--src/shaders/world.frag19
-rw-r--r--src/shaders/world.moon125
-rw-r--r--src/shaders/world.vert22
-rw-r--r--src/stars_test.moon25
-rw-r--r--src/state_machine.moon2
-rw-r--r--src/ui.moon127
-rw-r--r--src/ui/button.moon131
-rw-r--r--src/ui/joystick.moon98
-rw-r--r--src/ui/textbox.moon119
-rw-r--r--src/ui_test.moon7
-rw-r--r--src/util.lua84
-rw-r--r--src/window.moon10
-rw-r--r--src/world.moon23
-rw-r--r--src/world_test.moon15
25 files changed, 1446 insertions, 19 deletions
diff --git a/src/color.moon b/src/color.moon
new file mode 100644
index 0000000..447ae32
--- /dev/null
+++ b/src/color.moon
@@ -0,0 +1,26 @@
+
+color =
+ background: {100,113,136}
+ shadow: {62,81,84}
+ midground: {139,126,168}
+ foreground: {186,161,196}
+ highlight: {191,228,230}
+ outline: {44,54,52}
+ black: {34,40,37}
+
+am_color = {k, vec4(v[1]/255,v[2]/255,v[3]/255,1) for k,v in pairs(color)}
+
+am.ascii_color_map = {
+ b: am_color.background
+ m: am_color.midground
+ f: am_color.foreground
+ s: am_color.shadow
+ h: am_color.highlight
+ x: am_color.black
+ o: am_color.outline
+}
+
+{
+ color: color
+ am_color: am_color
+}
diff --git a/src/ecs.moon b/src/ecs.moon
new file mode 100644
index 0000000..4fdecc9
--- /dev/null
+++ b/src/ecs.moon
@@ -0,0 +1,88 @@
+
+entities = {}
+
+-- Bace component class of the ECS
+class Component
+ depends: {}
+ new: (name, properties) =>
+ @name = name
+ @properties = properties or {}
+ join: (e) =>
+ @
+ leave: (e) =>
+ @
+
+-- Base entity of our ECS
+class Entity
+ id: 1
+ new: (id, components) => -- [id], [components]
+ id_cur = #entities
+ while id == nil
+ id_cur += 1
+ if entities[id_cur] == nil
+ id = id_cur
+ assert(entities[id] == nil, "Attempted to create entity with the same id as one that already exists: " .. tostring(id))
+ entities[id] = @
+ @id = id
+ -- Bookkeeping for O(1) access for componenets
+ @c_by_type = {}
+
+ --Entity is responsible for the component -> entity link
+ @components = componenets or {}
+ for name, component in pairs(components)
+ component.entity = @
+ @c_by_type[component.__class] = @c_by_type[component.__class] or {}
+ @c_by_type[component.__class][name] = component
+ component\join(@)
+ add: (cid, component) =>
+ component.entity = @
+ component\join(@)
+ if cid == nil
+ cid = #@components
+ while @components[cid]
+ cid += 1
+ assert(@components[cid] == nil, "Already had a component with id" .. tostring(cid))
+ @components[cid] = component
+ @
+ remove: (cid) =>
+ component = @components[cid]
+ component.entity = nil
+ component\leave(@)
+ component
+ get: (cid) =>
+ @components[cid]
+
+class NetworkedComponent extends Component
+ pack: () =>
+ assert(@entity, "Tried to pack on a NetworkedComponent without an Entity")
+ return am.to_json({
+ id: @entity.id
+ data: @properties
+ time: am.current_time!
+ })
+
+class PredictedComponent extends Component
+ new: (name, properties, netc_name, calculate) =>
+ super(name, properties)
+ @netc_name = netc_name
+ @calculate = calculate
+ join: (entity) =>
+ @net = entity[@netc_name]
+ forward: () =>
+ for proeprty, calculation in pairs(@calculate)
+ @properties[property] = calculation(@)
+
+class GraphicsComponent extends Component
+ new: (name, properties) =>
+ assert(properties.node , "Failed to find node for graphics component")
+ super(name, properties)
+
+{
+ Component: Component
+ Entity: Entity
+ NetworkedComponent: NetworkedComponent
+ PredictedComponent: PredictedComponent
+ GraphicsComponent: GraphicsComponent
+}
+
+}
diff --git a/src/js_bridge.js b/src/js_bridge.js
index 46065cb..0b5ba08 100644
--- a/src/js_bridge.js
+++ b/src/js_bridge.js
@@ -8,15 +8,28 @@ function genRanHex(size) {
}
window.PEER = {
- event_queue: {},
+ event_queue: [],
peers: {},
- message_queue: {},
- create: function(name, options) {
+ message_queue: [],
+ connections: {},
+ create: function(tbl) {
+ var name = tbl.name;
+ var options = tbl.options;
+ console.log("[JS] Creating peer " + name);
var peer = new Peer(name, options);
PEER.peers[name] = peer;
},
- on: function(name, e, message) {
+ on: function(tbl) {
+ var name = tbl.name;
+ var e = tbl.e;
+ var message = tbl.message;
+ console.log("[JS] Setting hook for " + name + "," + e + "," + message);
PEER.peers[name].on(e, function(data) {
+ console.log("[JS] Peer " + name + " received " + e);
+ if(e == "connection"){
+ PEER.connections[[name,data.peer]] = data;
+ data = [name,data.peer]; // rewrite connections
+ }
PEER.message_queue.push({"message":message, "data":{
"call": "on",
"peer": name,
@@ -25,7 +38,52 @@ window.PEER = {
}});
});
},
- connect: function(name, id, options) {
- PEER.peers[name].connect(id, options);
+ connect: function(tbl) {
+ var name = tbl.name;
+ var id = tbl.id;
+ console.log("[JS] connecting " + name + " to " + id);
+ var conn = PEER.peers[name].connect(id);
+ PEER.connections[[name,id]] = conn;
+ return [name,id];
+ },
+ disconnect: function(tbl) {
+ PEER.peers[tbl.name].disconnect();
+ },
+ reconnect: function(tbl){
+ PEER.peers[tbl.name].reconnect();
+ },
+ destroy: function(tbl){
+ PEER.peers[tbl.name].destroy();
+ },
+ send: function(tbl){
+ var name = tbl.name;
+ var id = tbl.id;
+ var data = tbl.data;
+ console.log("[JS] " + name + " is sending " + data + " to " + id);
+ console.log(PEER.connections[[name,id]]);
+ console.log(data);
+ PEER.connections[[name,id]].send(data);
+ },
+ close: function(tbl){
+ var name = tbl.name;
+ var id = tbl.id;
+ PEER.connections[[name,id]].close();
+ },
+ conn_on: function(tbl){
+ var name = tbl.name;
+ var id = tbl.id;
+ var e = tbl.e;
+ var message = tbl.message;
+ console.log("[JS] Setting hook for [" + name + "," + id + "] " + e + "," + message);
+ PEER.connections[[name,id]].on(e, function(c) {
+ console.log("[JS] connection between " + name + " and " + id + " received " + e);
+ PEER.message_queue.push({"message":message, "data":{
+ "call": "on",
+ "peer": name,
+ "id": id,
+ "e": e,
+ "data": c
+ }});
+ });
},
};
diff --git a/src/log.moon b/src/log.moon
new file mode 100644
index 0000000..3c54dc2
--- /dev/null
+++ b/src/log.moon
@@ -0,0 +1,66 @@
+-- Singleton object to deal with log messages
+class log
+ log: (message, tags, level) ->
+ tags = tags or {}
+ if not level
+ error("Level is required")
+ tag_rev = {tag,true for tag in *tags}
+ chunk =
+ level: level
+ time: os.clock!
+ message: message
+ tags: tag_rev
+ stream_pos = log.messages + 1
+ log.stream[stream_pos] = chunk
+ for tag in *tags
+ log.by_tags[tag] = log.by_tags[tag] or {}
+ table.insert(log.by_tags[tag], stream_pos)
+ log.by_level[level] = log.by_level[level] or {}
+ table.insert(log.by_level[level], stream_pos)
+ log.messages += 1
+ for observer in *log.observers
+ observer(chunk)
+
+ reset: ->
+ log.stream = {}
+ log.messages = 0
+ log.by_tags = {}
+ log.by_level = {}
+ log.observers = {}
+
+ info: (message, tags) ->
+ log.log(message, tags, "info")
+ warn: (message, tags) ->
+ log.log(message, tags, "warn")
+ error: (message, tags) ->
+ log.log(message, tags, "error")
+ panic: (message, tags) ->
+ log.log(message, tags, "panic")
+ observe: (callback) ->
+ table.insert(log.observers, callback)
+ of_level: (level, callback) ->
+ if not log.by_level[level]
+ return
+ for message_idx in *log.by_level[level]
+ callback(log.stream[message_idx])
+ of_tags: (tags, callback) ->
+ if type(tags) ~= "table"
+ tags = {tags}
+ first_tag = tags[1]
+ assert(type(first_tag) == "string", "tag was not a string, was a " .. type(first_tag))
+ if not first_tag
+ error("Must pass a tag")
+ if not log.by_tags[first_tag]
+ return
+ for message_idx in *log.by_tags[first_tag]
+ message = log.stream[message_idx]
+ suitable = true
+ for tag in *tags
+ if not message.tags[tag]
+ suitable = false
+ break
+ if suitable
+ callback(message)
+
+log.reset()
+log
diff --git a/src/main.lua b/src/main.lua
index 8c8461a..55b7ef5 100644
--- a/src/main.lua
+++ b/src/main.lua
@@ -1,15 +1,18 @@
+require("preload")
+local win = require("window")
+--local stars = require("shaders.stars")
+--win.scene:append(stars)
+--world_shader = require("shaders.world")
+--print("World shader:",world_shader)
+--win.scene:append(world_shader.node)
+local ui = require("ui")
+win.scene:append(ui.node)
+--require("world_test")
+--require("net_test")
+require("ui_test")
-print("Hello, world!")
-
-am.eval_js("console.log('Hello, js!')")
-
-local win = am.window{
- title = "ggj25",
- width = 1280,
- height = 720,
- clear_color = vec4(0.8, 0.8, 0.3, 1)
-}
-print("about to pcall")
-am.eval_js(require("js_bridge"))
+require("log").observe(function(chunk)
+ print(table.concat({"[",chunk.level:upper(),"]",os.date()," > ",chunk.message}))
+end)
+--am.eval_js(require("js_bridge"))
--local a,b = pcall(am.eval_js, require("js_bridge"))
-print("done with pcall", a,b)
diff --git a/src/net.moon b/src/net.moon
new file mode 100644
index 0000000..b22d6f2
--- /dev/null
+++ b/src/net.moon
@@ -0,0 +1,148 @@
+-- Handles the bridge to javascript to do peer-to-peer connections
+
+
+log = require("log")
+net = {}
+
+net.initalize = () ->
+ am.eval_js(require("js_bridge"))
+
+net.call = (method, args) ->
+ print("About to eval")
+ result = am.eval_js("window.PEER." .. method .. "(" .. am.to_json(args) .. ")")
+ print("Done evaling")
+ result
+
+net.pull = () ->
+ messages = am.eval_js("window.PEER.message_queue")
+ am.eval_js("window.PEER.message_queue = []")
+ messages
+
+callbacks = {}
+peers = {}
+connections = {}
+
+class Connection
+ new: (source, dest) =>
+ @source = source
+ @dest = dest
+ on: (event, callback) =>
+ newid = #callbacks + 1
+ callbacks[newid] = callback
+ net.call("conn_on", {name: @source, id: @dest, e: event, message: newid})
+ send: (msgname, msg) =>
+ net.validate(msgname, msg)
+ net.call("send",{name: @source, id: @dest, data: msg})
+
+class Peer
+ new: (id) =>
+ net.call("create",{name: id})
+ if id
+ @id = id
+ peers[id] = @
+ on: (event, callback) =>
+ newid = #callbacks + 1
+ callbacks[newid] = callback
+ net.call("on",{name: @id, message:newid, e: event})
+
+ connect: (id, options) =>
+ conn = net.call("connect", {name: @id, id: id})
+ log.info("Got connection: " .. tostring(conn), {"net"})
+ Connection(conn[1],conn[2])
+
+
+net.Peer = Peer
+
+-- A fake peer for testing
+fakepeers = {}
+fakeconnections = {}
+fakecallbacks = {}
+channel = require("channel")
+
+class FakePeer
+ new: (id) =>
+ if id
+ @id = id
+ fakepeers[id] = @
+ on: (event, callback) =>
+ newid = #fakecallbacks + 1
+ fakecallbacks[newid] = callback
+
+ connect: (id, options) =>
+ conn = channel.FaultyChannel({
+ avg_latency: 200
+ latency_std: 100
+ loss: 0.1
+ })
+ conn
+
+
+messages = {}
+formatcache = {}
+message_callbacks = {}
+net.register_message = (name, format) ->
+ assert(type(format) == "table", "Format must be a table")
+ format.required = format.required or {}
+ format.optional = format.optional or {}
+ assert(next(format.required) or next(format.optional), "No fields found")
+ for set in *({format.required, format.optional})
+ for field, type_ in pairs(format.required)
+ if type(type_) == "string"
+ key = string.format("%s\0%s\0%s",name,field,type_)
+ if not formatcache[key]
+ formatcache[key] = (any) ->
+ assert(type(any) == type_, string.format("In message %q %q must be a %q, but was a %q", name, field, type_, type(any)))
+ set[field] = formatcache[key]
+ messages[name] = format
+ message_callbacks[name] = {}
+
+net.validate = (name, message) ->
+ assert(type(message) == "table", "Message must be a table")
+ format = messages[name]
+ required = {}
+ for field, validate in pairs(format.required)
+ required[field] = validate
+ for field, value in pairs(message)
+ if format.required[field]
+ required[field](value)
+ required[field] = nil
+ if format.optional[field]
+ format.optional[field](value)
+ missing = next(required)
+ if missing
+ error("Missing required field: " .. missing)
+ true
+
+net.listen = (name, callback, id) ->
+ id = id or {}
+ message_callbacks[name] = message_callbacks[name] or {}
+ message_callbacks[name][id] = callback
+ id
+
+net.defen = (name, id) ->
+ message_callbacks[name][id] = nil
+
+rewrite_events = {
+ connection: (message) ->
+ message.data.data = Connection(message.data.data[1], message.data.data[2])
+}
+
+net.pump = () ->
+ msg_ = net.pull!
+ log.info("Processing " .. tostring(#msg_) .. " messages", {"net"})
+ for message in *msg_
+ log.info(tostring(message), {"net", message.data.peer})
+ if rewrite_events[message.data.e]
+ log.info("Rewriting data due to " .. message.data.e .. " event", {"net", message.data.peer})
+ rewrite_events[message.data.e](message)
+ log.info(tostring(message), {"net", message.data.peer})
+ peer = peers[message.data.peer]
+ assert(peer, "Failed to find peer:" .. message.data.peer)
+ callback = callbacks[message.message]
+ assert(callback, "Failed to find callback " .. message.message .. " on peer " .. message.data.peer)
+ callback(peer,message.data)
+
+net.peers = () ->
+ peers
+
+net
diff --git a/src/net_test.moon b/src/net_test.moon
new file mode 100644
index 0000000..c4989ab
--- /dev/null
+++ b/src/net_test.moon
@@ -0,0 +1,64 @@
+net = require("net")
+
+ui = require("ui")
+
+test_button = ui.button(0,0,100,100,"Test")
+pull_button = ui.button(100,0,100,100,"Pull")
+send_button = ui.button(200,0,100,100,"Send")
+
+net.initalize!
+peer1, peer2, conn = nil, nil, nil
+test_button.on = (e) =>
+ net.register_message("HelloRequest",{
+ required:{
+ n1: "number"
+ n2: "number"
+ }
+ })
+ net.register_message("HelloResponse",{
+ required:{
+ n: "number"
+ }
+ })
+ peer1 = net.Peer()
+ peer1\on("open", (data) =>
+ print("Peer1 opened", data)
+ )
+ peer1\on("data",(data) =>
+ print("Peer 1 received data!")
+ )
+ peer1\on("error",(data) =>
+ print("Peer 1 error:", data)
+ )
+ peer1\on("connection",(message)=>
+ print("Peer1 connection:", message)
+ message.data\on("data",(data)=>
+ print("Peer1 received data:",data)
+ )
+ )
+ peer2 = net.Peer()
+ peer2\on("open", (data) =>
+ print("Peer2 opened",data)
+ conn = peer2\connect("blah-blah3")
+ conn\on("data",(data) =>
+ print("Peer2 data:",data)
+ )
+ )
+ peer2\on("connection", (data) =>
+ print("Peer2 connected", data)
+ )
+ peer2\on("error", (data) =>
+ error("Net error: " .. tostring(data))
+ )
+ peer2\on("data", (data) =>
+ print("Peer2 on data",data)
+ )
+
+pull_button.on = (e) =>
+ net.pump!
+
+send_button.on = (e) =>
+ conn\send("HelloRequest",{
+ n1:1
+ n2:2
+ })
diff --git a/src/party/hardoncollider b/src/party/hardoncollider
new file mode 160000
+Subproject eb1f285cb1cc4d951d90c92b64a4fc85e7ed06b
diff --git a/src/preload.lua b/src/preload.lua
new file mode 100644
index 0000000..8610cbc
--- /dev/null
+++ b/src/preload.lua
@@ -0,0 +1,71 @@
+-- Stuff to load before everything else
+
+--[[
+rewrite traceback function to map file names and line numbers to moonscript
+]]
+local old_traceback = debug.traceback
+debug.traceback = function(...)
+ local orig_traceback = old_traceback(...)
+ local noprint = {}
+ return orig_traceback:gsub("([ \t]*)([%w/_]+%.lua):(%d+):([^\n]*)",function(spaces, filename, linenum, rest)
+ --If our file is not moonscript, we won't have a debug file
+ local debugfile = am.load_string(filename .. ".X")
+ if not debugfile then
+ return spaces .. filename .. ":" .. linenum .. ":" .. rest
+ end
+ debugfile = debugfile .. "\n"
+ for line in debugfile:gmatch("([^\n]+)\n") do
+ _,_,pos,lua,moon = line:find("(%d+)%s+(%d+):%b[] >> (%d+)")
+ if pos and lua and moon and tonumber(linenum) == tonumber(lua) then
+ filename = filename:gsub(".lua$",".moon")
+ linenum = moon
+ break
+ end
+ end
+ return string.format("%s%s:%d: %s", spaces, filename, linenum, rest)
+ end)
+end
+
+-- Override tostring to display more info about the table
+local old_tostring = tostring
+local numtabs = 0
+local printed_tables = {}
+local function tostring_helper(el)
+ assert(type(el) == "table", "Tried to call helper with something that was not a table, it was a " .. type(el))
+ local mt = getmetatable(el)
+ if mt and mt.__tostring then
+ return mt.__tostring(el)
+ elseif printed_tables[el] == true then
+ return old_tostring(el)
+ else
+ printed_tables[el] = true
+ numtabs = numtabs + 1
+ local strbuilder = {"{"}
+ for k,v in pairs(el) do
+ local key,value
+ if type(k) == "table" then
+ key = tostring_helper(k)
+ else
+ key = old_tostring(k)
+ end
+ if type(v) == "table" then
+ value = tostring_helper(v)
+ else
+ value = old_tostring(v)
+ end
+ strbuilder[#strbuilder + 1] = string.format("%s%s : %s", string.rep("\t",numtabs), key, value)
+ end
+ strbuilder[#strbuilder + 1] = string.rep("\t",numtabs - 1) .. "}"
+ numtabs = numtabs - 1
+ return table.concat(strbuilder,"\n")
+ end
+
+end
+function tostring(el)
+ printed_tables = {}
+ if type(el) == "table" then
+ return tostring_helper(el)
+ end
+ return old_tostring(el)
+end
+
diff --git a/src/shader_shim.moon b/src/shader_shim.moon
new file mode 100644
index 0000000..05e11c1
--- /dev/null
+++ b/src/shader_shim.moon
@@ -0,0 +1,20 @@
+-- Sometimes we want to compile shaders, use the syntax
+-- {variable} to adress a variable
+win = require("window")
+inputs = {
+ "@width": win.width
+ "@height": win.height
+}
+
+shaders = setmetatable({},{
+ __index:(self, key) ->
+ vert = assert(am.load_string("shaders/" .. key .. ".vert"))
+ frag = assert(am.load_string("shaders/" .. key .. ".frag"))
+ vert_subbed = vert\gsub("@%b{}",(n) -> tostring(inputs[n]))
+ frag_subbed = frag\gsub("@%b{}",(n) -> tostring(inputs[n]))
+ succ, program = pcall(am.program, vert_subbed, frag_subbed)
+ if not succ
+ error(string.format("Failed compiling shader %q: %s vertex shader: %s fragment shader: %s", key, program, vert_subbed, frag_subbed))
+ am.use_program(am.program(vert_subbed, frag_subbed))
+})
+shaders
diff --git a/src/shaders/stars.lua b/src/shaders/stars.lua
new file mode 100644
index 0000000..111bef8
--- /dev/null
+++ b/src/shaders/stars.lua
@@ -0,0 +1,76 @@
+local win = require("window")
+local color = require("color")
+local world = require("world")
+local numstars = 400 -- we might have as many as 4 over
+local genned_stars = 0
+local period_x = 3
+local period_y = 3
+local stars = {}
+while genned_stars < numstars do
+ local rngx = math.random()
+ local xpos = rngx * win.width --* (period_x - 1)
+ local rngy = math.random()
+ local ypos = rngy * win.height --* (period_y - 1)
+ local blinks = math.random() > 0.3 and (math.random() * 2 * math.pi) or 0
+ stars[#stars+1] = vec3(xpos, ypos, blinks)
+ genned_stars = genned_stars + 1
+ if xpos < win.width then
+ -- duplicate on the last screen
+ stars[#stars+1] = vec3(xpos + (win.width * (period_x-2)), ypos, blinks)
+ genned_stars = genned_stars + 1
+ end
+ if ypos < win.height then
+ stars[#stars+1] = vec3(xpos, ypos + (win.height * (period_y-2)), blinks)
+ genned_stars = genned_stars + 1
+ end
+ if xpos < win.width and ypos < win.height then
+ stars[#stars+1] = vec3(xpos + (win.width * (period_x-2)), ypos+(win.height * (period_y-2)),blinks)
+ genned_stars = genned_stars + 1
+ end
+end
+local node = am.use_program(am.program([[
+ precision highp float;
+ attribute vec3 stars;
+ uniform float time;
+ uniform float world_x;
+ uniform float world_y;
+ uniform float world_x_period;
+ uniform float world_y_period;
+ uniform mat4 MV;
+ uniform mat4 P;
+ void main() {
+ float world_x_off = mod(world_x, world_x_period);
+ float world_y_off = mod(world_y, world_y_period);
+ gl_Position = P * MV * vec4(stars.x - world_x_off, stars.y - world_y_off, 0.0, 1.0);
+ float intensity = sin(stars.z + time) * cos(time) + 1.;
+ gl_PointSize = pow(intensity, 2.) * stars.z * 0.3;
+ }
+ ]],[[
+ precision mediump float;
+ uniform vec4 color;
+ void main() {
+ gl_FragColor = color;
+ }
+]]))
+^ am.bind({
+ MV = mat4(
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ (-win.width / 2), (-win.height/2), 0, 1
+ ),
+ color = color.am_color.highlight,
+ stars = am.vec3_array(stars),
+ world_x = am.current_time(),
+ world_x_period = (period_x - 2) * win.width,
+ world_y = am.current_time(),
+ world_y_period = (period_y - 2) * win.height,
+ time = am.current_time(),
+})
+^ am.draw("points")
+node:action(function(self)
+ self("bind").time = am.current_time()
+ self("bind").world_x = world.world_x
+ self("bind").world_y = world.world_y
+end)
+return node
diff --git a/src/shaders/world.frag b/src/shaders/world.frag
new file mode 100644
index 0000000..ded8abb
--- /dev/null
+++ b/src/shaders/world.frag
@@ -0,0 +1,19 @@
+precision mediump float;
+varying vec2 textureuv; // uv
+uniform sampler2D textures;
+uniform sampler2D emissives;
+uniform sampler2D normals;
+varying mat3 light1; // position, color, intensity-fadetime-?
+uniform float time;
+varying vec4 v_color;
+void main() {
+
+ vec2 uv = textureuv;
+ //vec2 uv = gl_FragCoord.xy;
+ //vec3 view_origin = vec3(0., 0., -3.);
+ //vec3 view_direction = vec3(uv, 3);
+ //vec3 screen_intersection = vec3(uv.x, uv.y, 0.);
+ gl_FragColor = texture2D(textures,uv);// + vec4(uv.xy / 4.,0.,1.);
+ //gl_FragColor = texture2D(textures,screen_intersection.xy);
+
+}
diff --git a/src/shaders/world.moon b/src/shaders/world.moon
new file mode 100644
index 0000000..ad40cb9
--- /dev/null
+++ b/src/shaders/world.moon
@@ -0,0 +1,125 @@
+win = require("window")
+color = require("color")
+world = require("world")
+sprites = require("world.sprites")
+shader_shim = require("shader_shim")
+hc = require("party.hardoncollider.init")
+-- Process the world into buffers to send to the shader
+
+error("Who is including world?")
+print("sprites:",sprites,getmetatable(sprites))
+print("sprites.floor1_diffuse",sprites["floor1_diffuse"])
+view_angle = math.pi / 4
+near_plane = 1
+far_plane = 2
+aspect = win.width / win.height
+s_mv = mat4(
+ 1, 0, 0, 0,
+ 0, aspect, 0, 0,
+ 0, 0, 1, 0,
+ -0.5, 0.5, 0, 4
+ )
+p_mv = mat4(
+ 1 / ((win.width / win.height) * math.tan(view_angle / 2)), 0, 0, 0,
+ 0, 1/math.tan(view_angle/2), 0, 0,
+ 0, 0, far_plane / (far_plane - near_plane), 1,
+ 0, 0,(-far_plane * near_plane)/(far_plane - near_plane), 0
+)
+
+-- Each point needs:
+-- vec3 position (x,y,z)
+-- vec2 (u,v)
+up_down_hall = (x,y) ->
+ r = {
+ --floor
+ vec3(x,y,0),
+ vec3(x+1,y,0),
+ vec3(x+1,y-1,0),
+ vec3(x+1,y-1,0),
+ vec3(x,y-1,0),
+ vec3(x,y,0),
+
+ -- Left wall
+ vec3(x,y,1),
+ vec3(x,y,0),
+ vec3(x,y-1,0),
+ vec3(x,y-1,0),
+ vec3(x,y-1,1),
+ vec3(x,y,1),
+
+ --Right wall
+ vec3(x+1,y,0),
+ vec3(x+1,y,1),
+ vec3(x+1,y-1,1),
+ vec3(x+1,y-1,1),
+ vec3(x+1,y-1,0),
+ vec3(x+1,y,0),
+ }
+ r
+
+sd = sprites.floor1_diffuse
+w1 = sprites.wall1_diffuse
+
+-- uvs are s,t,smult, tmult
+up_down_hall_uv = (x,y) ->
+ r = {
+ --floor
+ vec4(sd.s1,sd.t1,1,1),
+ vec4(sd.s2,sd.t1,1,1),
+ vec4(sd.s2,sd.t2,1,1),
+ vec4(sd.s2,sd.t2,1,1),
+ vec4(sd.s1,sd.t2,1,1),
+ vec4(sd.s1,sd.t1,1,1),
+ -- left wall
+ vec4(w1.s1,w1.t1,1,1),
+ vec4(w1.s1,w1.t2,1,1),
+ vec4(w1.s2,w1.t2,1,1),
+ vec4(w1.s2,w1.t2,1,1),
+ vec4(w1.s2,w1.t1,1,1),
+ vec4(w1.s1,w1.t1,1,1),
+ -- right wall
+ vec4(w1.s2,w1.t2,1,1),
+ vec4(w1.s2,w1.t1,1,1),
+ vec4(w1.s1,w1.t1,1,1),
+ vec4(w1.s1,w1.t1,1,1),
+ vec4(w1.s1,w1.t2,1,1),
+ vec4(w1.s2,w1.t2,1,1),
+
+ }
+ r
+
+add_verts = (tbl, new) ->
+ for i = 1, #new
+ tbl[#tbl+1] = new[i]
+
+world_geom = {}
+world_uv = {}
+add_verts(world_geom, up_down_hall(0,0))
+add_verts(world_uv, up_down_hall_uv(0,0))
+add_verts(world_geom, up_down_hall(0,1))
+add_verts(world_uv, up_down_hall_uv(0,0))
+add_verts(world_geom, up_down_hall(0,-1))
+add_verts(world_uv, up_down_hall_uv(0,0))
+--sprites["diffuse"].texture.wrap = "repeat"
+--sprites["normals"].texture.wrap = "repeat"
+node = shader_shim.world\append(am.cull_face("front")\append(am.bind({
+ MV: s_mv
+ P: mat4(1)
+ color: color.am_color.highlight,
+ world_x: 0,
+ world_y: 0,
+ world: am.vec3_array(world_geom)
+ time: am.current_time(),
+ textures: sprites.floor1_diffuse.texture
+ texuv: am.vec4_array(world_uv)
+})\append(am.draw("triangles"))))
+node\action(() =>
+ bind = self("bind")
+ bind.time = am.current_time!
+ bind.world_x = math.sin(am.current_time!) * 2
+ bind.world_y = math.cos(am.current_time!) * 2
+)
+{
+ node: node
+ bind: node("bind")
+}
diff --git a/src/shaders/world.vert b/src/shaders/world.vert
new file mode 100644
index 0000000..0e6747a
--- /dev/null
+++ b/src/shaders/world.vert
@@ -0,0 +1,22 @@
+precision highp float;
+attribute vec3 world; // position
+attribute vec2 texuv;
+varying vec2 textureuv;
+varying mat3 light1;
+uniform vec4 color;
+varying vec4 v_color;
+uniform float world_x;
+uniform float world_y;
+uniform mat4 MV;
+uniform mat4 P;
+void main() {
+ v_color = vec4(world.xyz,1.);
+ vec2 vxy = vec2(world.x - world_x, world.y - world_y);
+ float z_scale = 0.5;
+ float xoff = world.z * vxy.x * z_scale;
+ float yoff = world.z * vxy.y * z_scale;
+ textureuv=texuv;
+ // if z > 0 then
+ // xoff = ceil(xoff, 0)
+ gl_Position = P * MV * vec4(vxy.x + xoff, vxy.y + yoff, 0., 1.0);
+}
diff --git a/src/stars_test.moon b/src/stars_test.moon
new file mode 100644
index 0000000..2dbdd4e
--- /dev/null
+++ b/src/stars_test.moon
@@ -0,0 +1,25 @@
+shader = require("shaders.stars")
+win = require("window")
+
+node = am.group!
+x = win.left
+y = win.right
+time = math.sin(am.current_time!)
+world_x = am.current_time!
+shader = am.use_program(shader)\append(am.bind({
+ MV: mat4(
+ 1, 0, 0, 0
+ 0, 1, 0, 0
+ 0, 0, 1, 0
+ x, y, 0, 1
+ )
+ thickness: thickness
+ radius: radius
+ index: am.vec2_array(arr)
+ color: color
+ })\append(am.draw("triangles")))
+button = ui.button(10,10,100,100, "Hello,\nworld!")
+joystick = ui.joystick(-100,-100,200)
+--joystick2 = ui.joystick(100,100,150)
+print("Got button:",button)
+node
diff --git a/src/state_machine.moon b/src/state_machine.moon
new file mode 100644
index 0000000..4ec7359
--- /dev/null
+++ b/src/state_machine.moon
@@ -0,0 +1,2 @@
+
+class StateMachine
diff --git a/src/ui.moon b/src/ui.moon
new file mode 100644
index 0000000..7a06522
--- /dev/null
+++ b/src/ui.moon
@@ -0,0 +1,127 @@
+hc = require("party.hardoncollider.init")
+win = require("window")
+Button = require("ui.button")
+Joystick = require("ui.joystick")
+Textbox = require("ui.textbox")
+
+ui_world = hc.new(64)
+
+ui = {}
+ui.events = {
+ touch: {}
+ mouse: {}
+ controller: {}
+ keyboard: {}
+}
+ui.button = (x,y,width,height,text) ->
+ button = Button(x,y,width,height,text)
+ ui.node\append(button.node)
+ bounds = ui_world\rectangle(x,y,width,height)
+ ui.events.touch[bounds] = button
+ ui.events.mouse[bounds] = button
+ button
+
+ui.click = (x,y) ->
+ ui_world\shapesAt(x,y)
+
+ui.joystick = (x,y,r) ->
+ joystick = Joystick(x,y,r)
+ ui.node\append(joystick.node)
+ bounds = ui_world\circle(x,y,r)
+ ui.events.touch[bounds] = joystick
+
+ui.textbox = (x,y,width,height,value,placeholder) ->
+ value = value or ""
+ placeholder = placeholder or ""
+ textbox = Textbox(x,y,width,height,value,placeholder)
+ ui.node\append(textbox.node)
+ bounds = ui_world\rectangle(x,y,width,height)
+ ui.events.mouse[bounds] = textbox
+ ui.events.keyboard[textbox] = true
+
+
+ui.node = am.group!
+
+has_fire = (obj) ->
+ assert(obj.fire, obj.__class.__name .. " doesn't have a .fire method")
+
+ui.dbg = am.translate(0,0)\append(am.circle(vec2(0,0),5,vec4(0,0,0,1)))
+ui.node\append(ui.dbg)
+
+ui.node\action(() ->
+ pos = win\mouse_position()
+ down = win\mouse_pressed("left")
+ up = win\mouse_released("left")
+ wheel = win\mouse_wheel_delta()
+ keys = win\keys_pressed()
+ -- Debugging for mouse position:
+ ui.dbg.position2d = pos
+ mo_tbl =
+ event: "mouse_over"
+ data: pos
+ md_tbl =
+ event: "mouse_down"
+ data: pos
+ mu_tbl =
+ event: "mouse_up"
+ data: pos
+ for collider,_ in pairs(ui_world\shapesAt(pos.x, pos.y))
+ match = ui.events.mouse[collider]
+ if match
+ has_fire(match)
+ match\fire(mo_tbl)
+ if down
+ match\fire(md_tbl)
+ if up
+ match\fire(mu_tbl)
+ if math.length(wheel) > 0
+ etbl =
+ event: "mouse_scroll"
+ data: wheel
+ for collider, uiobj in pairs(ui.events.mouse)
+ has_fire(uiobj)
+ uiobj\fire(etbl)
+ if #keys > 0
+ etbl =
+ event: "keys_pressed"
+ data: keys
+ shift: win\key_down("lshift") or win\key_down("rshift")
+ for uiobj, _ in pairs(ui.events.keyboard)
+ has_fire(uiobj)
+ if uiobj\fire(etbl)
+ break -- allow any keyboard listener to "trap" the signal by returning true
+
+-- in_touch_events = {
+-- "active_touch"
+-- "touches_began"
+-- }
+-- for touch in *win\active_touches!
+-- tpos = win\touch_position(touch)
+-- etbl =
+-- event: "active_touch"
+-- data: tpos
+-- for collider,_ in pairs(ui_world\shapesAt(tpos.x, tpos.y))
+-- print("Touched collider:", collider)
+-- match = ui.events.touch[collider]
+-- if match
+-- has_fire(match)
+-- match\fire(etbl)
+-- delta = win\touch_delta(touch)
+-- if math.length(delta) > 0
+-- dtbl =
+-- event: "touch_delta"
+-- data: delta
+-- for _, uiobj in pairs(ui.events.touch)
+-- has_fire(uiobj)
+-- uiobj\fire(dtbl)
+-- for touch in *win\touches_ended!
+-- etbl =
+-- event: "touches_ended"
+-- data: win\touch_position(touch)
+-- for _,uiobj in pairs(ui.events.touch)
+-- has_fire(uiobj)
+-- uiobj\fire(etbl)
+ -- todo: expand this with controller support.
+)
+
+ui
diff --git a/src/ui/button.moon b/src/ui/button.moon
new file mode 100644
index 0000000..789ecae
--- /dev/null
+++ b/src/ui/button.moon
@@ -0,0 +1,131 @@
+
+s = require("ui.sprites")
+util = require("util")
+color = require("color")
+states = {"up","down"}
+rows = {"upper","mid","lower"}
+cols = {"left","mid","right"}
+class Button
+ --am.sprite() only works once the window is created
+ @initalized = false
+ @initalize: =>
+ for _, state, _, row, _, col in util.cartesian(states, rows, cols)
+ name = table.concat({state,row,col},"_")
+ assert(s["button_" .. name], "Failed to find sprite:" .. name)
+ @[name] = am.sprite(s["button_" .. name],"left","top")
+ @initalized = true
+ new: (x,y,w,h,text)=>
+ if not @@initalized
+ @@initalize!
+ @em = 16 -- width of a character
+ text = text or ""
+ assert(w > 15, "Button must have at least width 15")
+ @node = am.group!
+ position = am.translate(x,y+h)\tag("position")
+ @up_sprites = am.group!
+ position\append(@up_sprites)
+ @down_sprites = am.group!
+ @node\append(position)
+ @up_sprites\append(@@up_upper_left)
+ @up_sprites\append(
+ am.translate(@@up_upper_left.width,0)\append(
+ am.scale(w - @@up_upper_left.width - @@up_upper_right.width,1)\append(
+ @@up_upper_mid
+ )))
+ @up_sprites\append(
+ am.translate(w - @@up_upper_right.width, 0)\append(
+ @@up_upper_right
+ ))
+ mid_height = h - @@up_upper_left.height - @@up_lower_left.height
+ @up_sprites\append(
+ am.translate(0,-@@up_upper_left.height)\append(
+ am.scale(1,mid_height)\append(
+ @@up_mid_left
+ )))
+ @up_sprites\append(
+ am.translate(@@up_upper_left.width, -@@up_upper_left.height)\append(
+ am.scale(w - @@up_mid_left.width - @@up_mid_right.width, h - @@up_upper_mid.height - @@up_lower_mid.height)\append(
+ @@up_mid_mid
+ )))
+ @up_sprites\append(
+ am.translate(w - @@up_mid_right.width,-@@up_upper_right.height)\append(
+ am.scale(1,h - @@up_upper_right.height - @@up_lower_right.height)\append(
+ @@up_mid_right
+ )))
+ @up_sprites\append(
+ am.translate(0,-(h - @@up_lower_left.height))\append(
+ @@up_lower_left
+ ))
+ @up_sprites\append(
+ am.translate(@@up_lower_left.width,-(h - @@up_lower_mid.height))\append(
+ am.scale(w - @@up_lower_left.width - @@up_lower_right.width,1)\append(
+ @@up_lower_mid
+ )))
+ @up_sprites\append(
+ am.translate(w - @@up_lower_right.width, -(h - @@up_lower_right.height))\append(
+ @@up_lower_right
+ ))
+ @up_sprites\append(
+ am.translate(@@up_upper_left.width, -@@up_upper_right.height)\append(
+ am.text(text, "left","top", color.am_color.foreground)
+ ))
+ @down_sprites\append(@@down_upper_left)
+ @down_sprites\append(
+ am.translate(@@down_upper_left.width,0)\append(
+ am.scale(w - @@down_upper_left.width - @@down_upper_right.width,1)\append(
+ @@down_upper_mid
+ )))
+ @down_sprites\append(
+ am.translate(w - @@down_upper_right.width, 0)\append(
+ @@down_upper_right
+ ))
+ mid_height = h - @@down_upper_left.height - @@down_lower_left.height
+ @down_sprites\append(
+ am.translate(0,-@@down_upper_left.height)\append(
+ am.scale(1,mid_height)\append(
+ @@down_mid_left
+ )))
+ @down_sprites\append(
+ am.translate(@@down_upper_left.width, -@@down_upper_left.height)\append(
+ am.scale(w - @@down_mid_left.width - @@down_mid_right.width, h - @@down_upper_mid.height - @@down_lower_mid.height)\append(
+ @@down_mid_mid
+ )))
+ @down_sprites\append(
+ am.translate(w - @@down_mid_right.width,-@@down_upper_right.height)\append(
+ am.scale(1,h - @@down_upper_right.height - @@down_lower_right.height)\append(
+ @@down_mid_right
+ )))
+ @down_sprites\append(
+ am.translate(0,-(h - @@down_lower_left.height))\append(
+ @@down_lower_left
+ ))
+ @down_sprites\append(
+ am.translate(@@down_lower_left.width,-(h - @@down_lower_mid.height))\append(
+ am.scale(w - @@down_lower_left.width - @@down_lower_right.width,1)\append(
+ @@down_lower_mid
+ )))
+ @down_sprites\append(
+ am.translate(w - @@down_lower_right.width, -(h - @@down_lower_right.height))\append(
+ @@down_lower_right
+ ))
+ @text = am.text(text, "left","top", color.am_color.foreground)
+ position\append(
+ am.translate(@@down_upper_left.width, -@@down_upper_right.height)\append(
+ @text
+ ))
+ @depressed = false
+ down: () =>
+ @depressed = true
+ @.node("position")\replace(@up_sprites, @down_sprites)
+ up: () =>
+ @depressed = false
+ @.node("position")\replace(@down_sprites, @up_sprites)
+ fire: (e) =>
+ if e.event == "touches_began" or e.event == "mouse_down"
+ @down!
+ if @on
+ @on(e)
+ if e.event == "touches_ended" or e.event == "mouse_up"
+ @up!
+
+Button
diff --git a/src/ui/joystick.moon b/src/ui/joystick.moon
new file mode 100644
index 0000000..168e18b
--- /dev/null
+++ b/src/ui/joystick.moon
@@ -0,0 +1,98 @@
+
+color = require("color")
+window = require("window")
+
+circle_cache = setmetatable({},{__mode: "v"})
+hollow_circle = (x,y,radius, thickness, color) ->
+ key = string.format("%d\0%d\0%d\0%d",x,y,radius, thickness)
+ if circle_cache[key]
+ return circle_cache[key]
+ arr = {}
+ segments = 60
+ step = (2*math.pi) / segments
+ for i = 0,2*math.pi, step
+ arr[#arr+1] = vec2(i+step, 1)
+ arr[#arr+1] = vec2(i, 1)
+ arr[#arr+1] = vec2(i+step,0)
+ arr[#arr+1] = vec2(i+step,0)
+ arr[#arr+1] = vec2(i,1)
+ arr[#arr+1] = vec2(i,0)
+ circle = am.use_program(am.program([[
+ precision highp float;
+ attribute vec2 index;
+ uniform float thickness;
+ uniform float radius;
+ uniform mat4 MV;
+ uniform mat4 P;
+ void main() {
+ float distance = thickness * index[1];
+ vec2 vert = vec2(cos(index[0]) * (radius - distance), sin(index[0]) * (radius - distance));
+ gl_Position = P * MV * vec4(vert, 0.0, 1.0);
+ }
+ ]],[[
+ precision mediump float;
+ uniform vec4 color;
+ void main() {
+ gl_FragColor = color;
+ }
+ ]]))\append(am.bind({
+ MV: mat4(
+ 1, 0, 0, 0
+ 0, 1, 0, 0
+ 0, 0, 1, 0
+ x, y, 0, 1
+ )
+ thickness: thickness
+ radius: radius
+ index: am.vec2_array(arr)
+ color: color
+ })\append(am.draw("triangles")))
+ circle_cache[key] = circle
+ circle
+
+class Joystick
+ --am.sprite() only works once the window is created
+ @initalized = false
+ @initalize: =>
+ @hollow_circle = am.group!
+ step = 0.5
+ thickness = 0.02
+ lastpoint = vec2(1,0)
+ print("color.am_color.background is:", color.am_color.background)
+ for k,v in pairs(color.am_color)
+ print(k,":",v)
+ highlight_start = (5/8) * math.pi
+ highlight_end = (7/8) * math.pi
+ shadow_start = (3/2) * math.pi
+ shadow_end = 2 * math.pi
+ for i = 0,2*math.pi,step
+ nextpoint = vec2(math.cos(i), math.sin(i))
+ @hollow_circle\append(am.line(lastpoint + vec2(1,1), nextpoint + vec2(1,1), thickness, color.am_color.outline))
+ --@hollow_circle\append(am.line(lastpoint, nextpoint, thickness, color.am_color.background))
+ lastpoint = nextpoint
+ @hollow_circle\append(am.line(lastpoint, vec2(1,0), thickness * 2, color.am_color.outline))
+ @hollow_circle\append(am.line(lastpoint,vec2(1,0), thickness, color.am_color.background))
+ @initalized = true
+ new: (x,y,r)=>
+ if not @@initalized
+ @@initalize!
+ @node = am.group!
+ position = am.translate(x,y)\tag("position")
+ @node\append(position)
+ --position\append(am.circle(vec2(x,y), r, color.am_color.background))
+ @stick_pos = am.translate(0,0)\tag("stick")
+ position\append(
+ @stick_pos\append(
+ am.circle(vec2(0,0), r/9, color.am_color.outline)\append(
+ am.circle(vec2(0,0), r/10, color.am_color.background)\append(
+ am.circle(vec2(-r/60,r/60),r/15, color.am_color.foreground)\append(
+ am.circle(vec2(5,-5),r/13, color.am_color.background)
+ )))))
+ --position\append(am.scale(r,r)\append(@@hollow_circle))
+ --position\append(am.circle(vec2(x,y),r)\append(am.blend("subtract")\append(am.circle(vec2(x,y),r-10))))
+ position\append(hollow_circle(x,y,r,8,color.am_color.outline))
+ position\append(hollow_circle(x,y,r-math.sqrt(2),5,color.am_color.background))
+ fire: (tbl) =>
+ print("Fired",tbl)
+
+Joystick
diff --git a/src/ui/textbox.moon b/src/ui/textbox.moon
new file mode 100644
index 0000000..3c6151d
--- /dev/null
+++ b/src/ui/textbox.moon
@@ -0,0 +1,119 @@
+
+color = require("color")
+Button = require("ui.button")
+
+valid_chars = "abcdefghijklmnopqrstuvwxyz"
+shifted_nums = "!@#$%^&*()"
+class Textbox extends Button
+ new: (x,y,w,h,value,placeholder) =>
+ super(x,y,w,h,value)
+ if value == ""
+ @text.text = placeholder
+ @text.color = color.am_color.shadow
+ @cursor = am.group(
+ am.translate(@em,0)\append(
+ am.rect(0,0,@em/4,-@em,color.am_color.foreground)
+ ))
+ @cursor.hidden = true
+ @text\append(@cursor)
+ @cursor_pos = #@text.text
+ @update_cursor_pos!
+ @valid_chars = {}
+ @max_chars = math.huge
+ for i = 1, #valid_chars
+ char = valid_chars\sub(i,i)
+ @valid_chars[char] = char
+ @valid_chars[char\upper] = char\upper
+ for i = 0,9
+ @valid_chars[tostring(i)] = tostring(i)
+ @valid_chars["kp_" .. tostring(i)] = tostring(i)
+ @valid_chars.kp_divide = "/"
+ @valid_chars.kp_multiply = "*"
+ @valid_chars.kp_minus = "-"
+ @valid_chars.kp_plus = "+"
+ @valid_chars.kp_period = "."
+ @valid_chars.space = " "
+ @valid_chars.minus = "-"
+ @valid_chars.MINUS = "_"
+ @valid_chars.equals = "="
+ @valid_chars.EQUALS = "+"
+ @valid_chars.leftbracket = "["
+ @valid_chars.LEFTBRACKET = "{"
+ @valid_chars.rightbracket = "]"
+ @valid_chars.RIGHTBRACKET = "}"
+ @valid_chars.semicolon = ";"
+ @valid_chars.SEMICOLON = ":"
+ @valid_chars.quote = "'"
+ @valid_chars.QUOTE = '"'
+ @valid_chars.backquote = "`"
+ @valid_chars.BACKQUOTE = "~"
+ @valid_chars.comma = ","
+ @valid_chars.COMMA = "<"
+ @valid_chars.period = "."
+ @valid_chars.PERIOD = ">"
+ @valid_chars.slash = "/"
+ @valid_chars.SLASH = "?"
+ @cursor
+ down: () =>
+ super!
+ @cursor.hidden = false
+ @text.color = color.am_color.foreground
+ up: () =>
+ super!
+ @cursor.hidden = true
+ @text.color = color.am_color.shadow
+ update_cursor_pos: () =>
+ @.cursor("translate").x = @cursor_pos * 9
+ fire: (e) =>
+ print("cursor pos is", @cursor_pos)
+ if e.event == "mouse_down"
+ @down!
+ if @on
+ @on(e)
+ add_key = e.event == "keys_pressed" and @depressed
+ if add_key
+ t = @text.text
+ for key in *e.data
+ print("analyzing key:",key)
+ if key == "delete" or key == "backspace"
+ @cursor_pos -=1
+ if @cursor_pos < 0
+ @cursor_pos = 0
+ t = t\sub(1,@cursor_pos) .. t\sub(@cursor_pos+2)
+ elseif key == "home"
+ @cursor_pos = 0
+ elseif key == "end"
+ @cursor_pos = #t
+ elseif key == "right"
+ @cursor_pos += 1
+ if @cursor_pos > #t
+ @cursor_pos = #t
+ elseif key == "left"
+ @cursor_pos -= 1
+ if @cursor_pos < 0
+ @cursor_pos = 0
+ elseif tonumber(key) and tonumber(key) >= 0 and tonumber(key) <= 9
+ kn = tonumber(key)
+ nd = key
+ if e.shift
+ nd = shifted_nums\sub(kn,kn)
+ @cursor_pos += 1
+ t = t\sub(1,@cursor_pos) .. nd .. t\sub(@cursor_pos)
+ elseif key == "kp_enter" or key == "enter"
+ if @on
+ @on(e)
+ elseif key == "escape"
+ @up!
+ else
+ if e.shift and key\sub(1,3) ~= "kp_"
+ key = key\upper!
+ if @valid_chars[key]
+ @cursor_pos += 1
+ t = t\sub(0,@cursor_pos) .. @valid_chars[key] .. t\sub(@cursor_pos)
+ if #t > @max_chars
+ t = t\sub(1,@max_chars)
+ @text.text = t
+ @update_cursor_pos!
+ add_key
+
+Textbox
diff --git a/src/ui_test.moon b/src/ui_test.moon
new file mode 100644
index 0000000..18445d2
--- /dev/null
+++ b/src/ui_test.moon
@@ -0,0 +1,7 @@
+ui = require("ui")
+
+button = ui.button(10,10,100,100, "Hello,\nworld!")
+joystick = ui.joystick(-100,-100,200)
+textbox = ui.textbox(100,100,300,32)
+--joystick2 = ui.joystick(100,100,150)
+print("Got button:",button)
diff --git a/src/util.lua b/src/util.lua
new file mode 100644
index 0000000..8fccab2
--- /dev/null
+++ b/src/util.lua
@@ -0,0 +1,84 @@
+--[[
+Various helpful functions
+]]
+local util = {}
+function util.cartesian(...)
+ -- cartesian(tbl1, tbl2, tbl3, ...)
+ -- for each table, returns a permutation of key, value in tbl1, tbl2, ect.
+ local args = {...}
+ return coroutine.wrap(function()
+ local cursors = {} -- cursors[1], cursors[3], ect. are the keys
+ for k,v in ipairs(args) do
+ local a,b = next(v,nil)
+ cursors[(k*2) - 1] = a
+ cursors[(k*2)] = b
+ end
+ coroutine.yield(unpack(cursors))
+ local any_left = true
+ while any_left do
+ while next(args[#args],cursors[#cursors - 1]) do
+ local a,b = next(args[#args],cursors[#cursors - 1])
+ cursors[#cursors - 1] = a
+ cursors[#cursors] = b
+ coroutine.yield(unpack(cursors))
+ end
+ any_left = false
+ for i = #args, 1, -1 do
+ if next(args[i],cursors[(i*2)-1]) then
+ cursors[(i*2)-1], cursors[i*2] = next(args[i],cursors[(i*2)-1])
+ for j = i+1, #args do
+ cursors[(j*2)-1], cursors[j*2] = next(args[j],nil)
+ end
+ coroutine.yield(unpack(cursors))
+ any_left = true
+ break
+ end
+ end
+ end
+ end)
+end
+
+-- Override tostring to display more info about the table
+local old_tostring = tostring
+local numtabs = 0
+local printed_tables = {}
+local function tostring_helper(el)
+ assert(type(el) == "table", "Tried to call helper with something that was not a table, it was a " .. type(el))
+ local mt = getmetatable(el)
+ if mt and mt.__tostring then
+ return mt.__tostring(el)
+ elseif printed_tables[el] == true then
+ return old_tostring(el)
+ else
+ printed_tables[el] = true
+ numtabs = numtabs + 1
+ local strbuilder = {"{"}
+ for k,v in pairs(el) do
+ local key,value
+ if type(k) == "table" then
+ key = tostring_helper(k)
+ else
+ key = old_tostring(k)
+ end
+ if type(v) == "table" then
+ value = tostring_helper(v)
+ else
+ value = old_tostring(v)
+ end
+ strbuilder[#strbuilder + 1] = string.format("%s%s : %s", string.rep("\t",numtabs), key, value)
+ end
+ strbuilder[#strbuilder + 1] = string.rep("\t",numtabs - 1) .. "}"
+ numtabs = numtabs - 1
+ return table.concat(strbuilder,"\n")
+ end
+
+end
+function tostring(el)
+ printed_tables = {}
+ if type(el) == "table" then
+ return tostring_helper(el)
+ end
+ return old_tostring(el)
+end
+
+return util
diff --git a/src/window.moon b/src/window.moon
new file mode 100644
index 0000000..e3ec8ba
--- /dev/null
+++ b/src/window.moon
@@ -0,0 +1,10 @@
+
+-- Special file to hold the window, no dependencies!
+win = am.window{
+ title: "ggj25"
+ width: 1280
+ height: 720
+ clear_color: vec4(0.2, 0.2, 0.3, 1)
+}
+win.scene = am.group!
+win
diff --git a/src/world.moon b/src/world.moon
new file mode 100644
index 0000000..d511e88
--- /dev/null
+++ b/src/world.moon
@@ -0,0 +1,23 @@
+-- Global state
+win = require("window")
+hc = require("party.hardoncollider.init")
+ecs = require("ecs")
+
+--Use a collider to decide what to render
+x = {
+ world_e: ecs.Entity(1)
+ world_x: 0
+ world_y: 0
+ controller_selected: false
+ hosting: false
+ peer_channels: {}
+ network_proposed: {}
+ network_commited: {}
+ level: {
+ graphics:{}
+ entities:{}
+ graphic_world: hc.new(5)
+ }
+}
+x.level.collider = x.level.graphic_world\rectangle(0,0,1,1/win.width)
+x
diff --git a/src/world_test.moon b/src/world_test.moon
new file mode 100644
index 0000000..7ba6eb8
--- /dev/null
+++ b/src/world_test.moon
@@ -0,0 +1,15 @@
+shader = require("shaders.world")
+win = require("window")
+hc = require("party.hardoncollider.init")
+world = require("world")
+
+
+world_cache = {
+
+}
+print("Before setting world level:", world)
+world.level = {
+ graphics:{}
+ entities:{}
+ graphic_world: hc\new(5)
+}