From cf8bb800049e5e6fe5655dbbf05b4d2a0d8e9a33 Mon Sep 17 00:00:00 2001 From: AV <48882585+DMClVG@users.noreply.github.com> Date: Sun, 15 Oct 2023 12:09:48 +0200 Subject: [PATCH 1/7] Use JSON for packet format --- client/nethandler.lua | 11 +- client/packets.lua | 17 +- common/init.lua | 2 + common/lib/inspect.lua | 371 +++++++++++++++++++++++++++++++++++++++ common/lib/json.lua | 388 +++++++++++++++++++++++++++++++++++++++++ common/socket.lua | 83 +++++---- server/main.lua | 17 +- server/packets.lua | 50 ++++-- 8 files changed, 883 insertions(+), 56 deletions(-) create mode 100644 common/lib/inspect.lua create mode 100644 common/lib/json.lua diff --git a/client/nethandler.lua b/client/nethandler.lua index 375785a..0df20b1 100644 --- a/client/nethandler.lua +++ b/client/nethandler.lua @@ -29,7 +29,9 @@ end function nethandler.placed(g, d) local x, y, z, t = tonumber(d.x), tonumber(d.y), tonumber(d.z), tonumber(d.t) local hash = ("%d/%d/%d"):format(x, y, z) - if not g.placequeue[hash] or g.placequeue[hash].placed ~= t then g.world:tile(x, y, z, t) end + if not g.placequeue[hash] or g.placequeue[hash].t ~= t then + g.world:tile(x, y, z, t) + end g.placequeue[hash] = nil end @@ -55,8 +57,11 @@ function nethandler.entityremove(g, d) g.world:remove(d.id) end function nethandler.entityremoteset(g, d) local entity = g.world:entity(d.id) - if entity.id == g.player.id and d.property:match("[xyz]") then return end -- TODO: position correction - entity[d.property] = d.value + for k, v in pairs(d.properties) do + if not (entity.id == g.player.id and k:match("[xyz]")) then -- TODO: position correction + entity[k] = v + end + end end function nethandler.chunkadd(g, d) diff --git a/client/packets.lua b/client/packets.lua index d6a452b..84c81df 100644 --- a/client/packets.lua +++ b/client/packets.lua @@ -1,11 +1,20 @@ local packets = {} +local encode = loex.socket.encode -function packets.place(x, y, z, t) return ("[type=place;x=%d;y=%d;z=%d;t=%d;]"):format(x, y, z, t) end +function packets.place(x, y, z, t) + return encode {type="place",x=x,y=y,z=z,t=t} +end -function packets.breaktile(x, y, z) return ("[type=breaktile;x=%d;y=%d;z=%d;]"):format(x, y, z) end +function packets.breaktile(x, y, z) + return encode {type="breaktile",x=x,y=y,z=z} +end -function packets.join(username) return ("[type=join;username=%s;]"):format(username) end +function packets.join(username) + return encode {type="join", username=username} +end -function packets.move(x, y, z) return ("[type=move;x=%f;y=%f;z=%f;]"):format(x, y, z) end +function packets.move(x, y, z) + return encode {type="move", x=x,y=y,z=z} +end return packets diff --git a/common/init.lua b/common/init.lua index 14a8996..79e5995 100644 --- a/common/init.lua +++ b/common/init.lua @@ -31,6 +31,8 @@ loex = { assert(love, "this package needs löve!") lume = require(loex.lpath .. "lib.lume") +json = require(loex.lpath .. "lib.json") +inspect = require(loex.lpath .. "lib.inspect") loex.hash = require(loex.lpath .. "hash") loex.signal = require(loex.lpath .. "signal") diff --git a/common/lib/inspect.lua b/common/lib/inspect.lua new file mode 100644 index 0000000..9900a0b --- /dev/null +++ b/common/lib/inspect.lua @@ -0,0 +1,371 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local inspect = {Options = {}, } + + + + + + + + + + + + + + + + + +inspect._VERSION = 'inspect.lua 3.1.0' +inspect._URL = 'http://github.com/kikito/inspect.lua' +inspect._DESCRIPTION = 'human-readable representations of tables' +inspect._LICENSE = [[ + MIT LICENSE + + Copyright (c) 2022 Enrique García Cota + + 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. +]] +inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end }) +inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end }) + +local tostring = tostring +local rep = string.rep +local match = string.match +local char = string.char +local gsub = string.gsub +local fmt = string.format + +local _rawget +if rawget then + _rawget = rawget +else + _rawget = function(t, k) return t[k] end +end + +local function rawpairs(t) + return next, t, nil +end + + + +local function smartQuote(str) + if match(str, '"') and not match(str, "'") then + return "'" .. str .. "'" + end + return '"' .. gsub(str, '"', '\\"') .. '"' +end + + +local shortControlCharEscapes = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", +} +local longControlCharEscapes = { ["\127"] = "\127" } +for i = 0, 31 do + local ch = char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = fmt("\\%03d", i) + end +end + +local function escape(str) + return (gsub(gsub(gsub(str, "\\", "\\\\"), + "(%c)%f[0-9]", longControlCharEscapes), + "%c", shortControlCharEscapes)) +end + +local luaKeywords = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +local function isIdentifier(str) + return type(str) == "string" and + not not str:match("^[_%a][_%a%d]*$") and + not luaKeywords[str] +end + +local flr = math.floor +local function isSequenceKey(k, sequenceLength) + return type(k) == "number" and + flr(k) == k and + 1 <= (k) and + k <= sequenceLength +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7, +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + + if ta == tb and (ta == 'string' or ta == 'number') then + return (a) < (b) + end + + local dta = defaultTypeOrders[ta] or 100 + local dtb = defaultTypeOrders[tb] or 100 + + + return dta == dtb and ta < tb or dta < dtb +end + +local function getKeys(t) + + local seqLen = 1 + while _rawget(t, seqLen) ~= nil do + seqLen = seqLen + 1 + end + seqLen = seqLen - 1 + + local keys, keysLen = {}, 0 + for k in rawpairs(t) do + if not isSequenceKey(k, seqLen) then + keysLen = keysLen + 1 + keys[keysLen] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLen, seqLen +end + +local function countCycles(x, cycles) + if type(x) == "table" then + if cycles[x] then + cycles[x] = cycles[x] + 1 + else + cycles[x] = 1 + for k, v in rawpairs(x) do + countCycles(k, cycles) + countCycles(v, cycles) + end + countCycles(getmetatable(x), cycles) + end + end +end + +local function makePath(path, a, b) + local newPath = {} + local len = #path + for i = 1, len do newPath[i] = path[i] end + + newPath[len + 1] = a + newPath[len + 2] = b + + return newPath +end + + +local function processRecursive(process, + item, + path, + visited) + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == "table" then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= 'table' then mt = nil end + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + +local function puts(buf, str) + buf.n = buf.n + 1 + buf[buf.n] = str +end + + + +local Inspector = {} + + + + + + + + + + +local Inspector_mt = { __index = Inspector } + +local function tabify(inspector) + puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) +end + +function Inspector:getId(v) + local id = self.ids[v] + local ids = self.ids + if not id then + local tv = type(v) + id = (ids[tv] or 0) + 1 + ids[v], ids[tv] = id, id + end + return tostring(id) +end + +function Inspector:putValue(v) + local buf = self.buf + local tv = type(v) + if tv == 'string' then + puts(buf, smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or + tv == 'cdata' or tv == 'ctype' then + puts(buf, tostring(v)) + elseif tv == 'table' and not self.ids[v] then + local t = v + + if t == inspect.KEY or t == inspect.METATABLE then + puts(buf, tostring(t)) + elseif self.level >= self.depth then + puts(buf, '{...}') + else + if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end + + local keys, keysLen, seqLen = getKeys(t) + + puts(buf, '{') + self.level = self.level + 1 + + for i = 1, seqLen + keysLen do + if i > 1 then puts(buf, ',') end + if i <= seqLen then + puts(buf, ' ') + self:putValue(t[i]) + else + local k = keys[i - seqLen] + tabify(self) + if isIdentifier(k) then + puts(buf, k) + else + puts(buf, "[") + self:putValue(k) + puts(buf, "]") + end + puts(buf, ' = ') + self:putValue(t[k]) + end + end + + local mt = getmetatable(t) + if type(mt) == 'table' then + if seqLen + keysLen > 0 then puts(buf, ',') end + tabify(self) + puts(buf, ' = ') + self:putValue(mt) + end + + self.level = self.level - 1 + + if keysLen > 0 or type(mt) == 'table' then + tabify(self) + elseif seqLen > 0 then + puts(buf, ' ') + end + + puts(buf, '}') + end + + else + puts(buf, fmt('<%s %d>', tv, self:getId(v))) + end +end + + + + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or (math.huge) + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local cycles = {} + countCycles(root, cycles) + + local inspector = setmetatable({ + buf = { n = 0 }, + ids = {}, + cycles = cycles, + depth = depth, + level = 0, + newline = newline, + indent = indent, + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buf) +end + +setmetatable(inspect, { + __call = function(_, root, options) + return inspect.inspect(root, options) + end, +}) + +return inspect diff --git a/common/lib/json.lua b/common/lib/json.lua new file mode 100644 index 0000000..52b9530 --- /dev/null +++ b/common/lib/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 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(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 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 res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + 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, idx +end + + +return json diff --git a/common/socket.lua b/common/socket.lua index 1dc93c4..d11e17d 100644 --- a/common/socket.lua +++ b/common/socket.lua @@ -35,6 +35,19 @@ function socket.connect(address) return socket.new(enet) end +function socket.encode(t) + local p + if t.bin then + local bin = t.bin + t.bin = nil + p = json.encode(t) + p = p .. bin + else + p = json.encode(t) + end + return p +end + -- function socket:broadcast(data, channel, mode, dest) -- local dest = dest or self.peers -- for _, peer in pairs(dest) do @@ -42,36 +55,44 @@ end -- end -- end -local function decode(packet) - local bytedata = love.data.newByteData(packet) - local data = ffi.cast("uint8_t *", bytedata:getFFIPointer()) - - assert(data[0] == 91) -- [ - local entry = { {}, {} } - local entryi = 1 - local out = {} - local headersize = nil - - for i = 1, bytedata:getSize() do - local char = string.char(data[i]) - if char == "]" then - headersize = i + 1 - break - elseif char == "=" then - entryi = 2 - elseif char == ";" then - out[table.concat(entry[1])] = table.concat(entry[2]) - entry = { {}, {} } - entryi = 1 - else - table.insert(entry[entryi], char) - end - end - - local binsize = bytedata:getSize() - headersize - if binsize ~= 0 then out.bin = love.data.newDataView(bytedata, headersize, binsize) end - - return out +--local function decode(packet) +-- local bytedata = love.data.newByteData(packet) +-- local data = ffi.cast("uint8_t *", bytedata:getFFIPointer()) +-- +-- assert(data[0] == 91) -- [ +-- local entry = { {}, {} } +-- local entryi = 1 +-- local out = {} +-- local headersize = nil +-- +-- for i = 1, bytedata:getSize() do +-- local char = string.char(data[i]) +-- if char == "]" then +-- headersize = i + 1 +-- break +-- elseif char == "=" then +-- entryi = 2 +-- elseif char == ";" then +-- out[table.concat(entry[1])] = table.concat(entry[2]) +-- entry = { {}, {} } +-- entryi = 1 +-- else +-- table.insert(entry[entryi], char) +-- end +-- end +-- +-- local binsize = bytedata:getSize() - headersize +-- if binsize ~= 0 then out.bin = love.data.newDataView(bytedata, headersize, binsize) end +-- +-- return out +--end +-- +function socket.decode(p) + local t, idx = json.decode(p) + if idx <= #p then + t.bin = string.sub(p,idx,#p) + end + return t end function socket:peerdata(peer) return self.peerdatas[peer:index()] end @@ -83,7 +104,7 @@ function socket:service() local success, result = pcall(function() if event.type == "receive" then - local packet = decode(event.data) + local packet = socket.decode(event.data) self.onreceive:emit(event.peer, packet) elseif event.type == "connect" then self.peerdatas[peerid] = {} diff --git a/server/main.lua b/server/main.lua index 857a699..a682366 100644 --- a/server/main.lua +++ b/server/main.lua @@ -178,13 +178,16 @@ function love.update(dt) end for _, e in pairs(world:query("remote")) do - for property, _ in pairs(e.remote.edits) do - local packet = packets.entityremoteset(e.id, property, e.remote[property]) - e.remote.edits[property] = nil - for _, p in ipairs(world:query("player")) do - if p.view:entity(e.id) then p.master:send(packet) end - end - end + local sets = {} + for k, _ in pairs(e.remote.edits) do + sets[k] = e.remote[k] + e.remote.edits[k]=nil + end + local packet = packets.entityremoteset(e.id, sets) + + for _, p in ipairs(world:query("player")) do + if p.view:entity(e.id) then p.master:send(packet) end + end end end diff --git a/server/packets.lua b/server/packets.lua index 85a7c61..3366e2b 100644 --- a/server/packets.lua +++ b/server/packets.lua @@ -1,29 +1,57 @@ local packets = {} +local encode = loex.socket.encode + +function packets.joinsuccess(id, x, y, z) + return encode { + type="joinsuccess", + x=x, + y=y, + z=z, + id=id + } +end -function packets.joinsuccess(id, x, y, z) return ("[type=joinsuccess;x=%d;y=%d;z=%d;id=%s;]"):format(x, y, z, id) end - -function packets.joinfailure(cause) return ("[type=joinfailure;cause=%s;]"):format(cause) end +function packets.joinfailure(cause) + return encode { + type="joinfailure", + cause=cause + } +end function packets.chunkadd(tiles, cx, cy, cz) - return table.concat { ("[type=chunkadd;cx=%d;cy=%d;cz=%d;]"):format(cx, cy, cz), tiles } + return encode { type="chunkadd", cx=cx, cy=cy, cz=cz, bin=tiles } end -function packets.chunkremove(cx, cy, cz) return ("[type=chunkremove;cx=%d;cy=%d;cz=%d;]"):format(cx, cy, cz) end +function packets.chunkremove(cx, cy, cz) + return encode { type="chunkremove", cx=cx,cy=cy,cz=cz } +end -function packets.placed(x, y, z, t) return ("[type=placed;x=%d;y=%d;z=%d;t=%d;]"):format(x, y, z, t) end +function packets.placed(x, y, z, t) + return encode { type="placed",x=x,y=y,z=z,t=t } +end -function packets.broken(x, y, z) return ("[type=broken;x=%d;y=%d;z=%d;]"):format(x, y, z) end +function packets.broken(x, y, z) + return encode { type="broken", x=x,y=y,z=z} +end -function packets.entityadd(id, x, y, z) return ("[type=entityadd;x=%f;y=%f;z=%f;id=%s;]"):format(x, y, z, id) end +function packets.entityadd(id, x, y, z) + return encode {type="entityadd", x=x,y=y,z=z,id=id} +end -function packets.entityremove(id) return ("[type=entityremove;id=%s;]"):format(id) end +function packets.entityremove(id) + return encode {type="entityremove", id=id} +end -- function packets.entitymove(id, x, y, z) -- return ("[type=entitymove;x=%f;y=%f;z=%f;id=%s;]"):format(x, y, z, id) -- end -function packets.entityremoteset(id, k, v) - return ("[type=entityremoteset;id=%s;property=%s;value=%f;]"):format(id, k, v) +function packets.entityremoteset(id, properties) + return encode { + type="entityremoteset", + id=id, + properties=properties + } end return packets From 38aa38dab129dd95277be01392c2501c70e35897 Mon Sep 17 00:00:00 2001 From: AV <48882585+DMClVG@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:23:32 +0200 Subject: [PATCH 2/7] complete restructuring of code --- client/main.lua | 97 ++---- client/scenes/errorscreen.lua | 16 - client/scenes/gameworld.lua | 374 --------------------- client/scenes/joinscreen.lua | 19 -- client/{scenes => screens}/chunkremesh.lua | 31 +- client/screens/cursormodel.lua | 43 +++ client/screens/errorscreen.lua | 21 ++ client/screens/gamescreen.lua | 361 ++++++++++++++++++++ client/screens/joinscreen.lua | 110 ++++++ client/{ => services}/nethandler.lua | 42 +-- client/services/player.lua | 39 +++ common/entity.lua | 25 -- common/init.lua | 1 + {client => common}/physics.lua | 27 +- common/signal.lua | 31 +- common/tiles.lua | 1 + common/world.lua | 72 +++- server/game.lua | 55 +++ server/main.lua | 194 +---------- server/packets.lua | 8 +- server/player.lua | 34 -- server/remote.lua | 17 - server/services/connection_manager.lua | 77 +++++ server/services/player.lua | 190 +++++++++++ server/services/sync.lua | 46 +++ 25 files changed, 1135 insertions(+), 796 deletions(-) delete mode 100644 client/scenes/errorscreen.lua delete mode 100644 client/scenes/gameworld.lua delete mode 100644 client/scenes/joinscreen.lua rename client/{scenes => screens}/chunkremesh.lua (81%) create mode 100644 client/screens/cursormodel.lua create mode 100644 client/screens/errorscreen.lua create mode 100644 client/screens/gamescreen.lua create mode 100644 client/screens/joinscreen.lua rename client/{ => services}/nethandler.lua (57%) create mode 100644 client/services/player.lua rename {client => common}/physics.lua (78%) create mode 100644 server/game.lua delete mode 100644 server/player.lua delete mode 100644 server/remote.lua create mode 100644 server/services/connection_manager.lua create mode 100644 server/services/player.lua create mode 100644 server/services/sync.lua diff --git a/client/main.lua b/client/main.lua index 96f34a9..b55b29c 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,32 +1,19 @@ if arg[#arg] == "vsc_debug" then require("lldebugger").start() end package.path = package.path .. ";?/init.lua" -lg = love.graphics ---@diagnostic disable-next-line: missing-parameter -lg.setDefaultFilter("nearest") +love.graphics.setDefaultFilter("nearest") io.stdout:setvbuf("no") -CHANNEL_ONE = 0 -CHANNEL_EVENTS = 1 -CHANNEL_UPDATES = 2 - g3d = require("lib/g3d") -scene = require("lib/scene") -enet = require("enet") - require("common") -require("scenes/gameworld") -require("physics") - -packets = require("packets") -local nethandler = require("nethandler") - -local focused = false +CHANNEL_ONE = 0 +CHANNEL_EVENTS = 1 +CHANNEL_UPDATES = 2 -local socket -_G.master = nil +local game function love.load(args) if #args < 3 then @@ -34,79 +21,55 @@ function love.load(args) love.event.quit(-1) return end - local address = args[1] .. ":" .. args[2] - username = args[3] + local username = args[3] - socket = loex.socket.connect(address) - socket.onconnect:catch(onconnect) - socket.ondisconnect:catch(ondisconnect) - socket.onreceive:catch(onreceive) + local socket = loex.socket.connect(address) + assert(socket) - font = love.graphics.newFont(50) + local font = love.graphics.newFont(23) love.graphics.setFont(font) - scene(require("scenes/joinscreen")) -end - -function onconnect(peer) - print("Connected!") - - master = peer - master:send(packets.join(username), CHANNEL_ONE) -end + game = {} -function ondisconnect(_) scene(require("scenes/errorscreen"), "disconnected :(") end + game.socket = socket + game.username = username -function onreceive(_, packet) - print("Received " .. packet.type) + game.ondraw = loex.signal.new() + game.onupdate = loex.signal.new() + game.onmousemoved = loex.signal.new() + game.onmousepressed = loex.signal.new() + game.onkeypressed = loex.signal.new() + game.onresize = loex.signal.new() + game.onquit = loex.signal.new() - local handle = nethandler[packet.type] - if not handle then - error("Unknown packet type " .. packet.type) - else - handle(scene(), packet) - end + require("screens.joinscreen").init(game) end function love.update(dt) - socket:service() - - local scene = scene() - if scene and scene.update then scene:update(dt) end + game.onupdate:emit(game, dt) end function love.draw() - local scene = scene() - if scene and scene.draw then scene:draw() end + game.ondraw:emit(game) end -function love.mousepressed() - if not focused then focused = true end +function love.mousepressed(x,y,button,istouch,presses) + game.onmousepressed:emit(game,x,y,button,istouch,presses) end -function love.focus(hasFocus) focused = hasFocus end - -function love.mousemoved(x, y, dx, dy) - local scene = scene() - if scene and focused and scene.mousemoved then scene:mousemoved(x, y, dx, dy) end +function love.mousemoved(x, y, dx, dy,istouch) + game.onmousemoved:emit(game,x,y,dx,dy,istouch) end -function love.keypressed(k) - if k == "escape" then - love.mouse.setRelativeMode(false) - focused = false - end +function love.keypressed(k,scancode,isrepeat) + game.onkeypressed:emit(game, k, scancode, isrepeat) end function love.resize(w, h) - g3d.camera.aspectRatio = w / h - g3d.camera.updateProjectionMatrix() + game.onresize:emit(game,w,h) end function love.quit() - if socket then - print("Disconnecting ....") - socket:disconnect() - end + game.onquit:emit(game) end diff --git a/client/scenes/errorscreen.lua b/client/scenes/errorscreen.lua deleted file mode 100644 index 7140a26..0000000 --- a/client/scenes/errorscreen.lua +++ /dev/null @@ -1,16 +0,0 @@ -local errorscreen = {} -local lg = love.graphics - -function errorscreen:init(cause) self.msg = cause end - -function errorscreen:draw() - lg.clear(1, 0, 0) - local w, h = lg.getWidth(), lg.getHeight() - - w = w - lg.getFont():getWidth(self.msg) - h = h - lg.getFont():getHeight() - - lg.print(self.msg, w / 2, h / 2) -end - -return errorscreen diff --git a/client/scenes/gameworld.lua b/client/scenes/gameworld.lua deleted file mode 100644 index cbc8e2b..0000000 --- a/client/scenes/gameworld.lua +++ /dev/null @@ -1,374 +0,0 @@ -local gameworld = {} -local size = loex.chunk.size -local floor = math.floor -local min = math.min -local threadpool = {} -local threadusage = 0 --- load up some threads so that chunk meshing won't block the main thread -for i = 1, 8 do - threadpool[i] = love.thread.newThread("scenes/chunkremesh.lua") -end -local texturepack = lg.newImage("assets/texturepack.png") - -local mouse = {} -- mouse state -local playerbox = { - x = 0, - y = 0, - z = 0, - w = 0.3, - d = 0.3, - h = 0.9, -} - -local playerobj = g3d.newModel("assets/player.obj", "assets/saul.png") - --- create the mesh for the block cursor -local cursor, cursormodel - -do - local a = -0.005 - local b = 1.005 - cursormodel = g3d.newModel { - { a, a, a }, - { b, a, a }, - { b, a, a }, - { a, a, a }, - { a, a, b }, - { a, a, b }, - { b, a, b }, - { a, a, b }, - { a, a, b }, - { b, a, b }, - { b, a, a }, - { b, a, a }, - - { a, b, a }, - { b, b, a }, - { b, b, a }, - { a, b, a }, - { a, b, b }, - { a, b, b }, - { b, b, b }, - { a, b, b }, - { a, b, b }, - { b, b, b }, - { b, b, a }, - { b, b, a }, - - { a, a, a }, - { a, b, a }, - { a, b, a }, - { b, a, a }, - { b, b, a }, - { b, b, a }, - { a, a, b }, - { a, b, b }, - { a, b, b }, - { b, a, b }, - { b, b, b }, - { b, b, b }, - } -end - -function gameworld:init(player) - self.master = master - local world = loex.world.new() - world.ontilemodified:catch(self.ontilemodified, self) - world.onentityinserted:catch(self.onentityinserted, self) - world.onentityremoved:catch(self.onentityremoved, self) - world.onchunkinserted:catch(self.onchunkinserted, self) - world.onchunkremoved:catch(self.onchunkremoved, self) - - self.world = world - - self.placequeue = {} - self.breakqueue = {} - self.remeshqueue = {} - self.remeshchannel = love.thread.newChannel() - self.frameremeshes = 0 - self.synctimer = 0 - - self.player = player - self.player:tag("player") - - self.world:insert(self.player) - - lg.setMeshCullMode("back") -end - -function gameworld:onchunkinserted(chunk) - local x, y, z = chunk.x, chunk.y, chunk.z - self:requestremesh(chunk) -end - -function gameworld:onchunkremoved(chunk) end -function gameworld:onentityinserted(entity) print(entity.id .. " added") end - -function gameworld:onentityremoved(entity) print(entity.id .. " removed") end - -function gameworld:update(dt) - local world = self.world - -- collect mouse inputs - mouse.wasleft, mouse.wasright = mouse.left, mouse.right - mouse.left, mouse.right = love.mouse.isDown(1), love.mouse.isDown(2) - mouse.leftclick, mouse.rightclick = mouse.left and not mouse.wasleft, mouse.right and not mouse.wasright - - local lagdelay = 0.5 - -- handle place and break timeouts - for key, places in pairs(self.placequeue) do - if love.timer.getTime() - places.timestamp > lagdelay then - self.world:tile(places.x, places.y, places.z, loex.tiles.air.id) - self.placequeue[key] = nil - end - end - - for key, breaks in pairs(self.breakqueue) do - if love.timer.getTime() - breaks.timestamp > lagdelay then - self.world:tile(breaks.x, breaks.y, breaks.z, breaks.prev) - self.breakqueue[key] = nil - end - end - - -- count how many threads are being used right now - for _, thread in ipairs(threadpool) do - local err = thread:getError() - assert(not err, err) - end - - -- listen for finished meshes on the thread channels - while self.remeshchannel:peek() do - local data = self.remeshchannel:pop() - if not data then break end - local c = self.world:chunk(loex.hash.spatial(data.cx, data.cy, data.cz)) - if c.model then c.model.mesh:release() end - c.model = nil - c.inremesh = false - if data.count > 0 then - c.model = g3d.newModel(data.count, texturepack) - c.model.mesh:setVertices(data.data) - c.model:setTranslation(data.cx * size, data.cy * size, data.cz * size) - end - threadusage = threadusage - 1 -- free up thread - end - - -- remesh the chunks in the queue - local remeshesquota = #self.remeshqueue - local remeshes = 0 - local offi = 0 - - while threadusage < #threadpool and #self.remeshqueue > 0 and remeshes < remeshesquota do - local c = self.remeshqueue[1 + offi] - remeshes = remeshes + 1 - - for _, thread in ipairs(threadpool) do - if not thread:isRunning() then - -- send over the neighboring chunks to the thread - -- so that voxels on the edges can face themselves properly - local n1, n2, n3, n4, n5, n6 = world:neighbourhood(c.x, c.y, c.z) - if not (n1 and n2 and n3 and n4 and n5 and n6) then - offi = offi + 1 - break - end - - n1, n2, n3, n4, n5, n6 = n1.data, n2.data, n3.data, n4.data, n5.data, n6.data - thread:start(self.remeshchannel, c.x, c.y, c.z, c.data, size, loex.tiles.id, n1, n2, n3, n4, n5, n6) - table.remove(self.remeshqueue, 1 + offi) - threadusage = threadusage + 1 -- use up thread - break - end - end - end - - local keyboard = love.keyboard - local speed, jumpforce, gravity = 5, 12, 42 - local dirx, diry, dirz = g3d.camera.getLookVector() - local move = { x = 0, y = 0, z = 0 } - local p = self.player - - if keyboard.isDown("w") then - move.x = dirx - move.y = diry - elseif keyboard.isDown("s") then - move.x = -dirx - move.y = -diry - end - - if keyboard.isDown("a") then - move.x = -diry - move.y = dirx - elseif keyboard.isDown("d") then - move.x = diry - move.y = -dirx - end - - p.vx, p.vy, _ = g3d.vectors.scalarMultiply(speed, g3d.vectors.normalize(move.x, move.y, move.z)) - p.vz = p.vz - gravity * dt - - local onground = moveandcollide(self.world, p, playerbox, dt) - - g3d.camera.position[1] = p.x - g3d.camera.position[2] = p.y - g3d.camera.position[3] = p.z + 0.7 - g3d.camera.lookInDirection() - - if onground and keyboard.isDown("space") then p.vz = p.vz + jumpforce end - - local syncinterval = 1 / 20 - self.synctimer = self.synctimer + dt - if self.synctimer >= syncinterval then - self.master:send(packets.move(p.x, p.y, p.z), CHANNEL_UPDATES, "unreliable") - self.synctimer = 0 - end - - -- casts a ray from the camera five blocks in the look vector - -- finds the first intersecting block - cursor = nil - do - local dx, dy, dz = g3d.camera.getLookVector() - local x, y, z = g3d.camera.position[1], g3d.camera.position[2], g3d.camera.position[3] - local ox, oy, oz = x, y, z - - local inf = 99999999 - local clipdistance = 10 - local epsilon = 0.00001 - - while true do - local maxx, maxy, maxz = inf, inf, inf - if dx > 0 then - maxx = (floor(x) + 1 - x) / dx - elseif dx < 0 then - maxx = (floor(x) - x) / dx - end - if dy > 0 then - maxy = (floor(y) + 1 - y) / dy - elseif dy < 0 then - maxy = (floor(y) - y) / dy - end - if dz > 0 then - maxz = (floor(z) + 1 - z) / dz - elseif dz < 0 then - maxz = (floor(z) - z) / dz - end - - local step = min(maxx, min(maxy, maxz)) - x = x + dx * step * (1 + epsilon) - y = y + dy * step * (1 + epsilon) - z = z + dz * step * (1 + epsilon) - - if loex.utils.distance3d(ox, oy, oz, x, y, z, true) > clipdistance * clipdistance or step == 0 then break end - - local tx, ty, tz = floor(x), floor(y), floor(z) - local tile = world:tile(tx, ty, tz) - if tile == -1 then break end - if tile > 0 then - cursor = {} - cursor.placex, cursor.placey, cursor.placez = floor(x - dx * step), floor(y - dy * step), floor(z - dz * step) - cursor.x, cursor.y, cursor.z = tx, ty, tz - break - end - end - end - - local placetile = loex.tiles.bricks.id - - if mouse.leftclick and cursor then - local x, y, z = cursor.x, cursor.y, cursor.z - self.breakqueue[("%d/%d/%d"):format(x, y, z)] = { - x = x, - y = y, - z = z, - timestamp = love.timer.getTime(), - prev = self.world:tile(x, y, z), - } - self.master:send(packets.breaktile(x, y, z), CHANNEL_EVENTS, "reliable") - self.world:tile(x, y, z, loex.tiles.air.id) - end - - -- right click to place blocks - if mouse.rightclick and cursor then - local x, y, z = cursor.placex, cursor.placey, cursor.placez - local cube = { x = x + 0.5, y = y + 0.5, z = z + 0.5, w = 0.5, h = 0.5, d = 0.5 } - local translatedplayerbox = lume.clone(playerbox) - translatedplayerbox.x = translatedplayerbox.x + p.x - translatedplayerbox.y = translatedplayerbox.y + p.y - translatedplayerbox.z = translatedplayerbox.z + p.z - if not loex.utils.intersectbb(cube, translatedplayerbox) then - self.placequeue[("%d/%d/%d"):format(x, y, z)] = { - x = x, - y = y, - z = z, - timestamp = love.timer.getTime(), - t = placetile, - } - self.master:send(packets.place(x, y, z, placetile), CHANNEL_EVENTS, "reliable") - self.world:tile(x, y, z, placetile) - end - end -end - -function gameworld:draw() - lg.clear(lume.color("#4488ff")) - - lg.setColor(1, 1, 1) - for _, chunk in pairs(self.world.chunks) do - if chunk.model then chunk.model:draw() end - end - - lg.setMeshCullMode("none") - if cursor then - lg.setColor(0, 0, 0) - lg.setWireframe(true) - cursormodel:setTranslation(cursor.x, cursor.y, cursor.z) - cursormodel:draw() - lg.setWireframe(false) - end - - local camera = g3d.camera.position - lg.setColor(1, 1, 1) - for _, entity in pairs(self.world.entities) do - if entity ~= self.player then - playerobj:setTranslation(entity.x, entity.y, entity.z - 0.9) - playerobj:setRotation(0, 0, math.atan2(entity.y - camera[2], entity.x - camera[1])) - playerobj:setScale(0.1, 1, 0.6) - playerobj:draw() - end - end - - lg.setMeshCullMode("back") -end - -function gameworld:mousemoved(x, y, dx, dy) g3d.camera.firstPersonLook(dx, dy) end - -function gameworld:ontilemodified(x, y, z, _) - local spatial = loex.hash.spatial - local chunk = self.world:chunk(spatial(floor(x / size), floor(y / size), floor(z / size))) - assert(chunk) - - local tx, ty, tz = x % size, y % size, z % size - local cx, cy, cz = chunk.x, chunk.y, chunk.z - local world = self.world - - if tx >= size - 1 then self:requestremesh(world:chunk(spatial(cx + 1, cy, cz)), true) end - if tx <= 0 then self:requestremesh(world:chunk(spatial(cx - 1, cy, cz)), true) end - if ty >= size - 1 then self:requestremesh(world:chunk(spatial(cx, cy + 1, cz)), true) end - if ty <= 0 then self:requestremesh(world:chunk(spatial(cx, cy - 1, cz)), true) end - if tz >= size - 1 then self:requestremesh(world:chunk(spatial(cx, cy, cz + 1)), true) end - if tz <= 0 then self:requestremesh(world:chunk(spatial(cx, cy, cz - 1)), true) end - - self:requestremesh(chunk, true) -end - -function gameworld:requestremesh(c, priority) - -- don't add a nil chunk or a chunk that's already in the queue - local world = self.world - if not c or c.inremesh or not c.data then return end - - c.inremesh = true - if priority then - table.insert(self.remeshqueue, 1, c) - else - table.insert(self.remeshqueue, c) - end -end - -return gameworld diff --git a/client/scenes/joinscreen.lua b/client/scenes/joinscreen.lua deleted file mode 100644 index b249f48..0000000 --- a/client/scenes/joinscreen.lua +++ /dev/null @@ -1,19 +0,0 @@ -local joinscreen = {} -local lg = love.graphics - -function joinscreen:draw() - local w, h = lg.getWidth(), lg.getHeight() - - local msg = "Joining" - - for _ = 1, math.floor(love.timer.getTime()) % 4 do - msg = msg .. "." - end - - w = w - lg.getFont():getWidth(msg) - h = h - lg.getFont():getHeight() - - lg.print(msg, w / 2, h / 2) -end - -return joinscreen diff --git a/client/scenes/chunkremesh.lua b/client/screens/chunkremesh.lua similarity index 81% rename from client/scenes/chunkremesh.lua rename to client/screens/chunkremesh.lua index 71ab2f1..f3922a9 100644 --- a/client/scenes/chunkremesh.lua +++ b/client/screens/chunkremesh.lua @@ -2,7 +2,10 @@ require("love.math") require("love.data") local ffi = require("ffi") -local channel, cx, cy, cz, blockdata, size, tids, n1, n2, n3, n4, n5, n6 = ... +local channel, cx, cy, cz, blockdata, size, tids, +n1, n2, n3, n4, n5, n6, +c0, c1, c2, c3, c4, c5, c6 = ... + local blockdatapointer = ffi.cast("uint8_t *", blockdata:getFFIPointer()) local n1p = n1 and ffi.cast("uint8_t *", n1:getFFIPointer()) local n2p = n2 and ffi.cast("uint8_t *", n2:getFFIPointer()) @@ -11,9 +14,28 @@ local n4p = n4 and ffi.cast("uint8_t *", n4:getFFIPointer()) local n5p = n5 and ffi.cast("uint8_t *", n5:getFFIPointer()) local n6p = n6 and ffi.cast("uint8_t *", n6:getFFIPointer()) -local c1 = 1 -local c2 = 0.75 -local c3 = 0.5 +local c0p = c0 and ffi.cast("uint8_t *", c0:getFFIPointer()) +local c1p = c1 and ffi.cast("uint8_t *", c1:getFFIPointer()) +local c2p = c2 and ffi.cast("uint8_t *", c2:getFFIPointer()) +local c3p = c3 and ffi.cast("uint8_t *", c3:getFFIPointer()) +local c4p = c4 and ffi.cast("uint8_t *", c4:getFFIPointer()) +local c5p = c5 and ffi.cast("uint8_t *", c5:getFFIPointer()) +local c6p = c6 and ffi.cast("uint8_t *", c6:getFFIPointer()) + +local function getc(pointer, x, y, z) + if true then return 255 end + local i = x + size * y + size * size * z + + -- if this block is outside of the chunk, check the neighboring chunks if they exist + if x >= size then return c1p and getc(c1p, x % size, y % size, z % size) or -1 end + if x < 0 then return c2p and getc(c2p, x % size, y % size, z % size) or -1 end + if y >= size then return c3p and getc(c3p, x % size, y % size, z % size) or -1 end + if y < 0 then return c4p and getc(c4p, x % size, y % size, z % size) or -1 end + if z >= size then return c5p and getc(c5p, x % size, y % size, z % size) or -1 end + if z < 0 then return c6p and getc(c6p, x % size, y % size, z % size) or -1 end + + return pointer[i] +end local function gettile(pointer, x, y, z) local i = x + size * y + size * size * z @@ -70,6 +92,7 @@ if count > 0 then for i = start, stop, step do local primary = i % 2 == 1 local secondary = i > 2 and i < 6 + local c = getc(c0p, x,y,z) / 255 datapointer[dataindex].x = x + (mx == 1 and primary and 1 or 0) + (mx == 2 and secondary and 1 or 0) datapointer[dataindex].y = y + (my == 1 and primary and 1 or 0) + (my == 2 and secondary and 1 or 0) datapointer[dataindex].z = z + (mz == 1 and primary and 1 or 0) + (mz == 2 and secondary and 1 or 0) diff --git a/client/screens/cursormodel.lua b/client/screens/cursormodel.lua new file mode 100644 index 0000000..0dd56bd --- /dev/null +++ b/client/screens/cursormodel.lua @@ -0,0 +1,43 @@ +local a = -0.005 +local b = 1.005 +return g3d.newModel { + { a, a, a }, + { b, a, a }, + { b, a, a }, + { a, a, a }, + { a, a, b }, + { a, a, b }, + { b, a, b }, + { a, a, b }, + { a, a, b }, + { b, a, b }, + { b, a, a }, + { b, a, a }, + + { a, b, a }, + { b, b, a }, + { b, b, a }, + { a, b, a }, + { a, b, b }, + { a, b, b }, + { b, b, b }, + { a, b, b }, + { a, b, b }, + { b, b, b }, + { b, b, a }, + { b, b, a }, + + { a, a, a }, + { a, b, a }, + { a, b, a }, + { b, a, a }, + { b, b, a }, + { b, b, a }, + { a, a, b }, + { a, b, b }, + { a, b, b }, + { b, a, b }, + { b, b, b }, + { b, b, b }, +} + diff --git a/client/screens/errorscreen.lua b/client/screens/errorscreen.lua new file mode 100644 index 0000000..2b3c9aa --- /dev/null +++ b/client/screens/errorscreen.lua @@ -0,0 +1,21 @@ +local errorscreen = {} +local lg = love.graphics + +function errorscreen.init(g, cause) + g.errorscreen = {} + g.errorscreen.cause = cause + g.ondraw:catch(errorscreen.draw) +end + +function errorscreen.draw(g) + lg.clear(1, 0, 0) + local w, h = lg.getWidth(), lg.getHeight() + + local cause = g.errorscreen.cause + w = w - lg.getFont():getWidth(cause) + h = h - lg.getFont():getHeight() + + lg.print(cause, w / 2, h / 2) +end + +return errorscreen diff --git a/client/screens/gamescreen.lua b/client/screens/gamescreen.lua new file mode 100644 index 0000000..4d5c915 --- /dev/null +++ b/client/screens/gamescreen.lua @@ -0,0 +1,361 @@ +local packets = require("packets") + +local physics = loex.physics +local size = loex.chunk.size +local lg = love.graphics + +local floor = math.floor +local min = math.min + +local CEILING = 10 + +local gamescreen = {} + +function gamescreen.init(g, player) + g.gamescreen = { + cursor = {}, + cursormodel = require("screens.cursormodel"), + texturepack = lg.newImage("assets/texturepack.png"), + threadpool = {}, + threadusage = 0, + mouse = {}, + placequeue = {}, + breakqueue = {}, + remeshqueue = {}, + remeshchannel = love.thread.newChannel(), + frameremeshes = 0, + synctimer = 0, + player=player, + } + + player.box = { + x = 0, + y = 0, + z = 0, + w = 0.3, + d = 0.3, + h = 0.9, + } + + -- load up some threads so that chunk meshing won't block the main thread + for i = 1, 8 do + g.gamescreen.threadpool[i] = love.thread.newThread("screens/chunkremesh.lua") + end + + g.gamescreen.player_model = g3d.newModel("assets/player.obj", "assets/saul.png") + + g.gamescreen.gravity = 42 + + lg.setMeshCullMode("back") + love.audio.setDistanceModel("inverseclamped") + + g.ondraw:catch(gamescreen.draw) + g.onupdate:catch(gamescreen.update) + g.onmousemoved:catch(gamescreen.onmousemoved) + --g.onmousepressed:catch(gamescreen.onmousepressed) + g.world.ontilemodified:catch(gamescreen.ontilemodified, g) + g.world.onentityinserted:catch(gamescreen.onentityinserted, g) + g.world.onentityremoved:catch(gamescreen.onentityremoved, g) + g.world.onchunkinserted:catch(gamescreen.onchunkinserted, g) + g.world.onchunkremoved:catch(gamescreen.onchunkremoved, g) + + require("services.nethandler").init(g) + require("services.player").init(g) +end + + +function gamescreen.onchunkinserted(g, chunk) + local x, y, z = chunk.x, chunk.y, chunk.z + gamescreen.requestremesh(g,chunk) +end + +function gamescreen.onchunkremoved(g,chunk) end + +function gamescreen.onentityinserted(g,entity) + print(entity.id .. " added") +end + +function gamescreen.onentityremoved(g,entity) + print(entity.id .. " removed") +end + +function gamescreen.onresize(g, w, h) + g3d.camera.aspectRatio = w / h + g3d.camera.updateProjectionMatrix() +end + +function gamescreen.update(g, dt) + local self = g.gamescreen + local mouse = self.mouse + local threadpool = self.threadpool + + -- collect mouse inputs + mouse.wasleft, mouse.wasright = mouse.left, mouse.right + mouse.left, mouse.right = love.mouse.isDown(1), love.mouse.isDown(2) + mouse.leftclick, mouse.rightclick = mouse.left and not mouse.wasleft, mouse.right and not mouse.wasright + + local lagdelay = 0.5 + -- handle place and break timeouts + for key, places in pairs(self.placequeue) do + if love.timer.getTime() - places.timestamp > lagdelay then + g.world:tile(places.x, places.y, places.z, loex.tiles.air.id) + self.placequeue[key] = nil + end + end + + for key, breaks in pairs(self.breakqueue) do + if love.timer.getTime() - breaks.timestamp > lagdelay then + g.world:tile(breaks.x, breaks.y, breaks.z, breaks.prev) + self.breakqueue[key] = nil + end + end + + -- count how many threads are being used right now + for _, thread in ipairs(threadpool) do + local err = thread:getError() + assert(not err, err) + end + + -- listen for finished meshes on the thread channels + while self.remeshchannel:peek() do + local data = self.remeshchannel:pop() + if not data then break end + local c = g.world:chunk(loex.hash.spatial(data.cx, data.cy, data.cz)) + if c.model then c.model.mesh:release() end + c.model = nil + c.inremesh = false + if data.count > 0 then + c.model = g3d.newModel(data.count, self.texturepack) + c.model.mesh:setVertices(data.data) + c.model:setTranslation(data.cx * size, data.cy * size, data.cz * size) + end + self.threadusage = self.threadusage - 1 -- free up thread + end + + -- remesh the chunks in the queue + local remeshesquota = #self.remeshqueue + local remeshes = 0 + local offi = 0 + + while self.threadusage < #threadpool and #self.remeshqueue > 0 and remeshes < remeshesquota do + local c = self.remeshqueue[1 + offi] + remeshes = remeshes + 1 + + for _, thread in ipairs(threadpool) do + if not thread:isRunning() then + -- send over the neighboring chunks to the thread + -- so that voxels on the edges can face themselves properly + local n1, n2, n3, n4, n5, n6 = g.world:neighbourhood(c.x, c.y, c.z) + if not (n1 and n2 and n3 and n4 and n5 and n6) + or not g.world:chunk(loex.hash.spatial(c.x,c.y,CEILING-1)) then + offi = offi + 1 + break + end + + + c1, c2, c3, c4, c5, c6 = n1.cdata, n2.cdata, n3.cdata, n4.cdata, n5.cdata, n6.cdata + n1, n2, n3, n4, n5, n6 = n1.data, n2.data, n3.data, n4.data, n5.data, n6.data + thread:start(self.remeshchannel, c.x, c.y, c.z, c.data, size, loex.tiles.id, n1, n2, n3, n4, n5, n6, c.cdata, c1,c2,c3,c4,c5,c6) + table.remove(self.remeshqueue, 1 + offi) + self.threadusage = self.threadusage + 1 -- use up thread + break + end + end + end + + -- player movement + local keyboard = love.keyboard + local speed, jumpforce = 5, 12 + local dirx, diry, dirz = g3d.camera.getLookVector() + local move = { x = 0, y = 0, z = 0 } + local p = self.player + + if keyboard.isDown("w") then + move.x = move.x + dirx + move.y = move.y + diry + end + if keyboard.isDown("s") then + move.x = move.x - dirx + move.y = move.y - diry + end + + if keyboard.isDown("a") then + move.x = move.x - diry + move.y = move.y + dirx + end + if keyboard.isDown("d") then + move.x = move.x + diry + move.y = move.y - dirx + end + + p.vx, p.vy, _ = g3d.vectors.scalarMultiply(speed, g3d.vectors.normalize(move.x, move.y, move.z)) + p.vz = p.vz - self.gravity * dt + + local onground = physics.moveandcollide(g.world, p, p.box, dt) + + g3d.camera.position[1] = p.x + g3d.camera.position[2] = p.y + g3d.camera.position[3] = p.z + 0.7 + g3d.camera.lookInDirection() + + -- player jump + if onground and keyboard.isDown("space") then p.vz = jumpforce end + + local syncinterval = 1 / 20 + self.synctimer = self.synctimer + dt + if self.synctimer >= syncinterval then + g.master:send(packets.move(p.x, p.y, p.z), CHANNEL_UPDATES, "unreliable") + self.synctimer = 0 + end + + -- casts a ray from the camera five blocks in the look vector + -- finds the first intersecting block + self.cursor = nil + do + local dx, dy, dz = g3d.camera.getLookVector() + local x, y, z = g3d.camera.position[1], g3d.camera.position[2], g3d.camera.position[3] + local ox, oy, oz = x, y, z + + local inf = 99999999 + local clipdistance = 10 + local epsilon = 0.00001 + + while true do + local maxx, maxy, maxz = inf, inf, inf + if dx > 0 then + maxx = (floor(x) + 1 - x) / dx + elseif dx < 0 then + maxx = (floor(x) - x) / dx + end + if dy > 0 then + maxy = (floor(y) + 1 - y) / dy + elseif dy < 0 then + maxy = (floor(y) - y) / dy + end + if dz > 0 then + maxz = (floor(z) + 1 - z) / dz + elseif dz < 0 then + maxz = (floor(z) - z) / dz + end + + local step = min(maxx, min(maxy, maxz)) + x = x + dx * step * (1 + epsilon) + y = y + dy * step * (1 + epsilon) + z = z + dz * step * (1 + epsilon) + + if loex.utils.distance3d(ox, oy, oz, x, y, z, true) > clipdistance * clipdistance or step == 0 then break end + + local tx, ty, tz = floor(x), floor(y), floor(z) + local tile = g.world:tile(tx, ty, tz) + if tile == -1 then break end + if tile > 0 then + self.cursor = {} + self.cursor.placex, self.cursor.placey, self.cursor.placez = floor(x - dx * step), floor(y - dy * step), floor(z - dz * step) + self.cursor.x, self.cursor.y, self.cursor.z = tx, ty, tz + break + end + end + end + + local placetile = loex.tiles.slime.id + + -- left click to break block + if mouse.leftclick and self.cursor then + local x, y, z = self.cursor.x, self.cursor.y, self.cursor.z + self.breakqueue[("%d/%d/%d"):format(x, y, z)] = { + x = x, + y = y, + z = z, + timestamp = love.timer.getTime(), + prev = g.world:tile(x, y, z), + } + g.master:send(packets.breaktile(x, y, z), CHANNEL_EVENTS, "reliable") + g.world:tile(x, y, z, loex.tiles.air.id) + end + + -- right click to place block + if mouse.rightclick and self.cursor then + local x, y, z = self.cursor.placex, self.cursor.placey, self.cursor.placez + local cube = { x = x + 0.5, y = y + 0.5, z = z + 0.5, w = 0.5, h = 0.5, d = 0.5 } + local translatedplayerbox = lume.clone(p.box) + translatedplayerbox.x = translatedplayerbox.x + p.x + translatedplayerbox.y = translatedplayerbox.y + p.y + translatedplayerbox.z = translatedplayerbox.z + p.z + if not loex.utils.intersectbb(cube, translatedplayerbox) then + self.placequeue[("%d/%d/%d"):format(x, y, z)] = { + x = x, + y = y, + z = z, + timestamp = love.timer.getTime(), + t = placetile, + } + g.master:send(packets.place(x, y, z, placetile), CHANNEL_EVENTS, "reliable") + g.world:tile(x, y, z, placetile) + end + end +end + +function gamescreen.draw(g) + local self = g.gamescreen + lg.clear(lume.color("#4488ff")) + + lg.setColor(1, 1, 1) + for _, chunk in pairs(g.world.chunks) do + if chunk.model then chunk.model:draw() end + end + + lg.setMeshCullMode("none") + if self.cursor then + lg.setColor(0, 0, 0) + lg.setWireframe(true) + self.cursormodel:setTranslation(self.cursor.x, self.cursor.y, self.cursor.z) + self.cursormodel:draw() + lg.setWireframe(false) + end + lg.setMeshCullMode("back") + + -- draw crosshair + local cross = 4 + lg.setColor(1,1,1) + lg.rectangle("fill", (lg.getWidth()-cross)/2, (lg.getHeight()-cross)/2, cross, cross) +end + +function gamescreen.onmousemoved(g, x, y, dx, dy, istouch) + g3d.camera.firstPersonLook(dx, dy) +end + +function gamescreen.ontilemodified(g, x, y, z, _) + local spatial = loex.hash.spatial + local chunk = g.world:chunk(spatial(floor(x / size), floor(y / size), floor(z / size))) + assert(chunk) + + local tx, ty, tz = x % size, y % size, z % size + local cx, cy, cz = chunk.x, chunk.y, chunk.z + local world = g.world + + if tx >= size - 1 then gamescreen.requestremesh(g, world:chunk(spatial(cx + 1, cy, cz)), true) end + if tx <= 0 then gamescreen.requestremesh(g, world:chunk(spatial(cx - 1, cy, cz)), true) end + if ty >= size - 1 then gamescreen.requestremesh(g, world:chunk(spatial(cx, cy + 1, cz)), true) end + if ty <= 0 then gamescreen.requestremesh(g, world:chunk(spatial(cx, cy - 1, cz)), true) end + if tz >= size - 1 then gamescreen.requestremesh(g, world:chunk(spatial(cx, cy, cz + 1)), true) end + if tz <= 0 then gamescreen.requestremesh(g, world:chunk(spatial(cx, cy, cz - 1)), true) end + + gamescreen.requestremesh(g, chunk, true) +end + +function gamescreen.requestremesh(g, c, priority) + local self = g.gamescreen + -- don't add a nil chunk or a chunk that's already in the queue + local world = g.world + if not c or c.inremesh or not c.data then return end + if not c.cdata then c.cdata = love.data.newByteData(size^3) end + + c.inremesh = true + if priority then + table.insert(self.remeshqueue, 1, c) + else + table.insert(self.remeshqueue, c) + end +end + +return gamescreen diff --git a/client/screens/joinscreen.lua b/client/screens/joinscreen.lua new file mode 100644 index 0000000..97c65e2 --- /dev/null +++ b/client/screens/joinscreen.lua @@ -0,0 +1,110 @@ +local errorscreen = require("screens.errorscreen") +local gamescreen = require("screens.gamescreen") +local packets = require("packets") + +local lg = love.graphics + +local joinscreen = {} + +function joinscreen.init(g) + local signals = { + g.ondraw:catch(joinscreen.draw), + g.socket.onconnect:catch(joinscreen.socket_onconnect, g), + g.socket.onreceive:catch(joinscreen.socket_onreceive, g), + g.socket.ondisconnect:catch(joinscreen.socket_ondisconnect, g), + } + g.joinscreen = {signals=signals} + + -- base functions + g.onupdate:catch(function(g) g.socket:service() end) + g.onquit:catch(function(g) g.socket:disconnect() end) +end + +function joinscreen.cleanup(g) + for _, signal in ipairs(g.joinscreen.signals) do signal:destroy() end + g.joinscreen = nil +end + +function joinscreen.error(g, cause) + joinscreen.cleanup(g) + errorscreen.init(g, cause) +end + +function joinscreen.socket_onreceive(g, peer, d) + print("received " .. d.type) + if d.type == "joinsuccess" then + local spawnx, spawny, spawnz = d.x, d.y, d.z + assert(d.id) + + g.world = loex.world.new() + + local player = {id=d.id} + player.x, player.y, player.z = spawnx, spawny, spawnz + player.vx, player.vy, player.vz = 0,0,0 + player.username = g.username + + player.ssfootsteps = love.audio.newQueueableSource(48000, 16, 1) + player.ssfootsteps:setAttenuationDistances(1, 1) + + player.ssfootsteps_end = love.audio.newSource("assets/footsteps_end.wav", "static") + player.ssfootsteps_end:setAttenuationDistances(1, 1) + + g.world:insert(player) + g.world:tag(player, "player") + + print( + ("Joined under username " .. player.username .. " (ID: " .. player.id .. ") at spawn point %d, %d, %d"):format( + spawnx, + spawny, + spawnz + ) + ) + joinscreen.cleanup(g) + gamescreen.init(g, player) + elseif d.type == "joinfailure" then + joinscreen.error(g, d.cause) + else + joinscreen.error(g,"unexpected packet type " .. d.typed) + end +end + +function joinscreen.socket_onconnect(g, peer) + print("connected!") + + -- set master peer + g.master = peer + + -- send join + local join_packet = packets.join(g.username) + g.master:send(join_packet) +end + +function joinscreen.socket_ondisconnect(g) + joinscreen.error(g,"connection closed") +end + +function joinscreen.update(g) + g.socket:service() +end + +function joinscreen.draw() + local w, h = lg.getWidth(), lg.getHeight() + + local l = 3 + local s = "" + local k = math.floor(love.timer.getTime()) % l + for i = 0, l-1 do + if i == k then + s = s .. "O" + else + s = s .. "o" + end + end + + w = w - lg.getFont():getWidth(s) + h = h - lg.getFont():getHeight() + + lg.print(s, w / 2, h / 2) +end + +return joinscreen diff --git a/client/nethandler.lua b/client/services/nethandler.lua similarity index 57% rename from client/nethandler.lua rename to client/services/nethandler.lua index 0df20b1..c92676d 100644 --- a/client/nethandler.lua +++ b/client/services/nethandler.lua @@ -1,38 +1,31 @@ local nethandler = {} -function nethandler.joinsuccess(_, d) - local spawnx, spawny, spawnz = tonumber(d.x), tonumber(d.y), tonumber(d.z) - assert(d.id) - local player = loex.entity.new(spawnx, spawny, spawnz, d.id) - player.username = username +function nethandler.init(g) + g.socket.onreceive:catch(nethandler.onreceive, g) +end - print( - ("Joined under username " .. player.username .. " (ID: " .. player.id .. ") at spawn point %d, %d, %d"):format( - spawnx, - spawny, - spawnz - ) - ) +function nethandler.onreceive(g, peer, d) + if d.type == "joinsuccess" then return end -- FIXME - scene(require("scenes/gameworld"), player) + print("received " .. d.type) + nethandler[d.type](g, d) end -function nethandler.joinfailure(_, d) scene(require("scenes/errorscreen"), d.cause) end - function nethandler.broken(g, d) local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) local hash = ("%d/%d/%d"):format(x, y, z) - if not g.breakqueue[hash] then g.world:tile(x, y, z, loex.tiles.air.id, true) end - g.breakqueue[hash] = nil + if not g.gamescreen.breakqueue[hash] then g.world:tile(x, y, z, loex.tiles.air.id, true) end + g.gamescreen.breakqueue[hash] = nil end function nethandler.placed(g, d) + local gamescreen = require("screens.gamescreen") local x, y, z, t = tonumber(d.x), tonumber(d.y), tonumber(d.z), tonumber(d.t) local hash = ("%d/%d/%d"):format(x, y, z) - if not g.placequeue[hash] or g.placequeue[hash].t ~= t then + if not g.gamescreen.placequeue[hash] or g.gamescreen.placequeue[hash].t ~= t then g.world:tile(x, y, z, t) end - g.placequeue[hash] = nil + g.gamescreen.placequeue[hash] = nil end function nethandler.entitymove(g, d) @@ -46,19 +39,16 @@ function nethandler.entitymove(g, d) end function nethandler.entityadd(g, d) - if d.id == g.player.id then return end - - local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) - local entity = loex.entity.new(x, y, z, d.id) - g.world:insert(entity) end -function nethandler.entityremove(g, d) g.world:remove(d.id) end +function nethandler.entityremove(g, d) + g.world:remove(g.world:entity(d.id)) +end function nethandler.entityremoteset(g, d) local entity = g.world:entity(d.id) for k, v in pairs(d.properties) do - if not (entity.id == g.player.id and k:match("[xyz]")) then -- TODO: position correction + if not (entity.id == g.gamescreen.player.id and k:match("[xyz]")) then -- TODO: position correction entity[k] = v end end diff --git a/client/services/player.lua b/client/services/player.lua new file mode 100644 index 0000000..e12e423 --- /dev/null +++ b/client/services/player.lua @@ -0,0 +1,39 @@ +local player = {} +local lg = love.graphics + +function player.init(g) + g.socket.onreceive:catch(player.onreceive, g) + + g.onupdate:catch(player.onupdate) + g.ondraw:catch(player.ondraw) +end + +function player.onreceive(g, _, packet) + if packet.type == "entityadd" and packet.entity_type == "player" then + local e = { id=packet.id,x=packet.x,y=packet.y,z=packet.z, username=packet.username } + g.world:insert(e) + g.world:tag(e, "player") + end +end + +function player.onupdate(g) +end + +function player.ondraw(g) + local player_model = g.gamescreen.player_model + local camera = g3d.camera.position + + lg.setMeshCullMode("none") + lg.setColor(1, 1, 1) + + for _, e in pairs(g.world:query("player")) do + if e ~= g.gamescreen.player then + player_model:setTranslation(e.x, e.y, e.z - 0.9) + player_model:setRotation(0, 0, math.atan2(e.y - camera[2], e.x - camera[1])) + player_model:setScale(0.1, 1, 0.6) + player_model:draw() + end + end +end + +return player diff --git a/common/entity.lua b/common/entity.lua index 77c7528..e69de29 100644 --- a/common/entity.lua +++ b/common/entity.lua @@ -1,25 +0,0 @@ -local entity = {} -entity.__index = entity - -function entity.new(x, y, z, id) - local new = {} - new.id = id or lume.uuid() - new.x, new.y, new.z = x, y, z - new.vx, new.vy, new.vz = 0, 0, 0 - new.tags = {} - - setmetatable(new, entity) - return new -end - -function entity:tag(tag) self.tags[tag] = true end - -function entity:untag(tag) self.tags[tag] = nil end - -function entity:has(tag) return self.tags[tag] == true end - -function entity:destroy() end - -function entity:__tostring() return entity.type end - -return entity diff --git a/common/init.lua b/common/init.lua index 79e5995..47891c2 100644 --- a/common/init.lua +++ b/common/init.lua @@ -43,5 +43,6 @@ loex.entity = require(loex.lpath .. "entity") loex.world = require(loex.lpath .. "world") loex.brush = require(loex.lpath .. "brush") loex.socket = require(loex.lpath .. "socket") +loex.physics = require(loex.lpath .. "physics") return loex diff --git a/client/physics.lua b/common/physics.lua similarity index 78% rename from client/physics.lua rename to common/physics.lua index 82e1fe0..5170b7c 100644 --- a/client/physics.lua +++ b/common/physics.lua @@ -1,16 +1,28 @@ +local physics = {} + +local tiles = loex.tiles local cube = { w = 0.5, h = 0.5, d = 0.5, x = 0, y = 0, z = 0 } local epsilonx, epsilony, epsilonz = 0.0045, 0.002, 0.005 local utils = loex.utils -local min, max = math.min, math.max +local floor, min, max = math.floor, math.min, math.max local expandb, iterb, intersectbb = utils.expandb, utils.iterb, utils.intersectbb -function moveandcollide(world, e, box, dt) +function physics.intersect_point_world(world, x, y, z) + local i,j,k = floor(x), floor(y), floor(z) + return world:tile(i, j, k) > 0 +end + +function physics.moveandcollide(world, e, box, dt) + local bounce = 0 + local dx, dy, dz = e.vx * dt, e.vy * dt, e.vz * dt local x, y, z = e.x, e.y, e.z local w, h, d = box.w, box.h, box.d local onground = false + -- Z MOVEMENT + local collidedz = false local boxz = expandb(lume.clone(box), 0, 0, dz) boxz.x = boxz.x + x boxz.y = boxz.y + y @@ -25,14 +37,19 @@ function moveandcollide(world, e, box, dt) if dz < 0 then dz = min(max(dz, (k + 1 + epsilonz) - (z - h)), 0) onground = true + bounce = (bounce + (tiles.id[t].bounce or 0)) * 0.5 else dz = max(min(dz, (k - epsilonz) - (z + h)), 0) - end - e.vz = 0 + end + collidedz = true end end end + if collidedz then + e.vz = -e.vz * bounce + end + -- X MOVEMENT local boxx = expandb(lume.clone(box), dx, 0, dz) boxx.x = boxx.x + x boxx.y = boxx.y + y @@ -54,6 +71,7 @@ function moveandcollide(world, e, box, dt) end end + -- Y MOVEMENT local boxy = expandb(lume.clone(box), dx, dy, dz) boxy.x = boxy.x + x boxy.y = boxy.y + y @@ -81,3 +99,4 @@ function moveandcollide(world, e, box, dt) return onground end +return physics diff --git a/common/signal.lua b/common/signal.lua index c502b09..a48f5a1 100644 --- a/common/signal.lua +++ b/common/signal.lua @@ -1,22 +1,43 @@ local signal = {} signal.__index = signal +local signal_handle = {} +signal_handle.__index = signal_handle + +function signal_handle:destroy() + self.signal.subs[self.id] = nil +end + function signal.new() local new = {} new.subs = {} + new.idc = 0 setmetatable(new, signal) return new end -function signal:catch(handle, ...) table.insert(self.subs, { handle = handle, opt = { ... } }) end +function signal:catch(f, ...) + assert(f) + local handle = {signal=self, id=self.idc} + setmetatable(handle, signal_handle) + self.idc = self.idc + 1 + + self.subs[handle.id] = { f = f, opt = { ... } } + return handle +end function signal:emit(...) - for i = 1, #self.subs do - local sub = self.subs[i] + for id, sub in pairs(self.subs) do if #sub.opt ~= 0 then - sub.handle(unpack(sub.opt), ...) + + local args = { ... } + for n=#sub.opt,1,-1 do + table.insert(args, 1, sub.opt[n]) + end + + sub.f(unpack(args)) else - sub.handle(...) + sub.f(...) end end end diff --git a/common/tiles.lua b/common/tiles.lua index 7ec5b7d..af7d037 100644 --- a/common/tiles.lua +++ b/common/tiles.lua @@ -9,6 +9,7 @@ tiles.tiles = { bricks = { id = 5, tex = 3 }, leaves = { id = 6, tex = 16 }, log = { id = 7, tex = { 22, 22, 21, 21, 21, 21 } }, + slime = { id = 8, tex = 5, bounce=0.8 }, } local id = {} diff --git a/common/world.lua b/common/world.lua index e19ab23..bc5067f 100644 --- a/common/world.lua +++ b/common/world.lua @@ -9,7 +9,9 @@ world.__index = world function world.new() local new = {} new.chunks = {} + new.entities = {} + new.tagtables = {} new.ontilemodified = loex.signal.new() new.onentityinserted = loex.signal.new() @@ -28,34 +30,68 @@ function world:insert(e) self.onentityinserted:emit(e) end +function world:tag(e, tag) + local tagtable = self.tagtables[tag] + if not tagtable then + tagtable = {} + self.tagtables[tag] = tagtable + end + tagtable[e.id] = e + + return tags +end + +function world:untag(e, tag) + local tagtable = self.tagtables[tag] + if tagtable then + tagtable[e.id] = nil + end +end + +function world:tagged(e, tag) + local tagtable = self.tagtables[tag] + return tagtable and tagtable[e.id] +end + function world:remove(e) - assert(e) - local id - if type(e) == "table" then - id = e.id - else - id = e - e = self.entities[id] - end - assert(self.entities[id]) - self.entities[id] = nil + assert(self.entities[e.id], "entity does not exist") + local e = self.entities[e.id] + self.entities[e.id] = nil + + for _, tagtable in pairs(self.tagtables) do + tagtable[e.id] = nil + end self.onentityremoved:emit(e) end +local function intersect(a, b) + local t = {} + if a == nil or b == nil then + return t + end + + for k, _ in pairs(a) do + if b[k] ~= nil then + t[k]=true + end + end + return t +end + function world:query(...) - local res = {} local tags = { ... } - for _, e in pairs(self.entities) do - local hastags = true - for _, tag in ipairs(tags) do - hastags = hastags and e:has(tag) - end - if hastags then insert(res, e) end + if #tags == 0 then return self.entities end + + local query = self.tagtables[tags[1]] + + for i=2,#tags do + query = intersect(query, self.tagtables[tags[i]]) end - return res + return query or {} end function world:entity(id) return self.entities[id] end + function world:chunk(hash) return self.chunks[hash] end function world:insertchunk(c) diff --git a/server/game.lua b/server/game.lua new file mode 100644 index 0000000..88d49ed --- /dev/null +++ b/server/game.lua @@ -0,0 +1,55 @@ +local gen = require("gen") +local overworld = require("gen.overworld") + +local floor = math.floor +local tiles = loex.tiles +local size = loex.chunk.size + +local game = {} + +function game.new() + local new = {} + setmetatable(new, { __index = game }) + return new +end + +function game:init(socket) + self.socket = socket + + self.world = loex.world.new() + self.world.onentityinserted:catch(self.world_onentityinserted, self) + self.world.onentityremoved:catch(self.world_onentityremoved, self) + + self.genstate = gen.state.new(overworld.layers, 43242) + + self.gravity = 42 + + self.onupdate = loex.signal.new() + self.onshutdown = loex.signal.new() + + require("services.connection_manager").init(self) + require("services.player").init(self) + require("services.sync").init(self) +end + +function game:update(dt) + self.socket:service() + + self.onupdate:emit(self, dt) +end + +function game:world_onentityinserted(e) + print(e.id .. " added") +end + +function game:world_onentityremoved(e) + print(e.id .. " removed") +end + +function game:shutdown() + print("shutting down...") + self.socket:disconnect() + self.onshutdown:emit(self) +end + +return game diff --git a/server/main.lua b/server/main.lua index a682366..a8ff219 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,30 +1,15 @@ if arg[#arg] == "vsc_debug" then require("lldebugger").start() end package.path = package.path .. ";?/init.lua" +require("common") -IS_SERVER = true +-- IS_SERVER = true CHANNEL_ONE = 0 CHANNEL_CHUNKS = 1 CHANNEL_EVENTS = 3 CHANNEL_UPDATES = 4 -common = require("common") -packets = require("packets") -remote = require("remote") -gen = require("gen") -local player = require("player") - -banlist = {} -takenusernames = {} - -local socket -local world -local genstate - -local floor = math.floor -local tiles = loex.tiles -local size = loex.chunk.size -local overworld = require("gen.overworld") +local game function love.load(args) if #args < 1 then @@ -32,177 +17,20 @@ function love.load(args) return love.event.quit(-1) end - port = tonumber(args[1]) + local port = tonumber(args[1]) print("starting server on port " .. tostring(port) .. "...") + local socket = loex.socket.host(port, 64) - socket = loex.socket.host(port, 64) - socket.onconnect:catch(onconnect) - socket.ondisconnect:catch(ondisconnect) - socket.onreceive:catch(onreceive) - - world = loex.world.new() - world.onentityinserted:catch(world_onentityinserted) - world.onentityremoved:catch(world_onentityremoved) - world.ontilemodified:catch(world_ontilemodified) - - genstate = gen.state.new(overworld.layers, 43242) -end - -function world_onentityinserted(e) print(e.id .. " added") end - -function world_onentityremoved(e) - print(e.id .. " removed") - - for _, p in ipairs(world:query("player")) do - if p.view:entity(e.id) then p.view:remove(e.id) end - end -end - -function world_ontilemodified(x, y, z, t) - local packet - if t == loex.tiles.air.id then - packet = packets.broken(x, y, z) - else - packet = packets.placed(x, y, z, t) - end - - for _, p in ipairs(world:query("player")) do - if p.view:tile(x, y, z) >= 0 then p.master:send(packet) end - end -end - -function onconnect(peer) print("Connected!") end - -function ondisconnect(peer) - local peerdata = socket:peerdata(peer) - if peerdata.playerentity then - print(peerdata.playerentity.username .. " left the game :<") - world:remove(peerdata.playerentity) - takenusernames[peerdata.playerentity.username] = nil - end -end - -function handle_player_packet(player, packet) - local handles = { - ["move"] = function(p, d) - local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) - p.x = x - p.y = y - p.z = z - end, - ["place"] = function(p, d) - local x, y, z, t = tonumber(d.x), tonumber(d.y), tonumber(d.z), tonumber(d.t) - world:tile(x, y, z, t) - end, - ["breaktile"] = function(p, d) - local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) - world:tile(x, y, z, tiles.air.id) - end, - } - handles[packet.type](player, packet) -end - -function broadcast(packet) - for _, e in pairs(world.entities) do - if e:has("player") then e.master:send(packet) end - end + game = require("game").new() + game:init(socket) end -function onreceive(peer, packet) - print("Received " .. packet.type) - local peerdata = socket:peerdata(peer) - - if peerdata.playerentity == nil then - if packet.type == "join" then - local err = verify(packet.username) - if err then - peer:send(packets.joinfailure(err), CHANNEL_ONE) - peer:disconnect_later() - return - end - local p = player.entity(0, 0, 180, nil, packet.username, peer) - - peer:send(packets.joinsuccess(p.id, p.x, p.y, p.z), CHANNEL_ONE) - - world:insert(p) - peerdata.playerentity = p - - takenusernames[p.username] = true - - print(p.username .. " joined the game :>") - else - error("invalid packet for ghost peer") - end - else - handle_player_packet(peerdata.playerentity, packet) - end -end function love.update(dt) - socket:service() - local gendistance = 5 - for _, e in pairs(world.entities) do - if e:has("remote") then - e.remote.x = e.x - e.remote.y = e.y - e.remote.z = e.z - end - - if e:has("player") then - for i = -gendistance + floor(e.x / size), gendistance + floor(e.x / size) do - for j = -gendistance + floor(e.y / size), gendistance + floor(e.y / size) do - for k = 0, overworld.columnheight - 1 do - if not world:chunk(loex.hash.spatial(i, j, k)) then - local c = overworld:generate(genstate, i, j, k) - world:insertchunk(c) - end - end - end - end - - for _, c in pairs(world.chunks) do - if not player.inview(e, c.x * size, c.y * size, c.z * size) then - if e.view.chunks[c.hash] then e.view:removechunk(c.hash) end - else - if not e.view.chunks[c.hash] then e.view:insertchunk(c) end - end - end - for _, entity in pairs(world.entities) do - if not player.inview(e, entity.x, entity.y, entity.z) then - if e.view:entity(entity.id) then e.view:remove(entity) end - else - if not e.view:entity(entity.id) then e.view:insert(entity) end - end - end - end - end - - for _, e in pairs(world:query("remote")) do - local sets = {} - for k, _ in pairs(e.remote.edits) do - sets[k] = e.remote[k] - e.remote.edits[k]=nil - end - local packet = packets.entityremoteset(e.id, sets) - - for _, p in ipairs(world:query("player")) do - if p.view:entity(e.id) then p.master:send(packet) end - end - end + game:update(dt) end -function love.quit() socket:disconnect() end - -function verify(username) - local validUsername = "^[a-zA-Z_]+$" - - if banlist[username] then return "You're banned from this server. Cry about it :-(" end - - if #username < 3 then return "Username too short" end - - if #username > 15 then return "Username too long" end - - if not username:match(validUsername) then return "Invalid username (It has to be good)" end - - if takenusernames[username] then return "Username already taken. Try again :)" end +function love.quit() + game:shutdown() end + diff --git a/server/packets.lua b/server/packets.lua index 3366e2b..ec9da83 100644 --- a/server/packets.lua +++ b/server/packets.lua @@ -34,11 +34,11 @@ function packets.broken(x, y, z) return encode { type="broken", x=x,y=y,z=z} end -function packets.entityadd(id, x, y, z) - return encode {type="entityadd", x=x,y=y,z=z,id=id} +function packets.entityadd(id) + return encode {type="entityadd", id=id } end -function packets.entityremove(id) +function packets.entityremove(id) -- TODO: get rid of this packet return encode {type="entityremove", id=id} end @@ -50,7 +50,7 @@ function packets.entityremoteset(id, properties) return encode { type="entityremoteset", id=id, - properties=properties + properties=properties, } end diff --git a/server/player.lua b/server/player.lua deleted file mode 100644 index ed9ab8b..0000000 --- a/server/player.lua +++ /dev/null @@ -1,34 +0,0 @@ -local player = {} -local floor = math.floor -local size = loex.chunk.size -local remote = require("remote") - -function player.view_onchunkinserted(e, c) e.master:send(packets.chunkadd(c:dump(), c.x, c.y, c.z)) end - -function player.view_onchunkremoved(e, c) e.master:send(packets.chunkremove(c.x, c.y, c.z)) end - -function player.view_onentityinserted(p, e) p.master:send(packets.entityadd(e.id, e.x, e.y, e.z)) end - -function player.view_onentityremoved(p, e) p.master:send(packets.entityremove(e.id)) end - -function player.entity(x, y, z, id, username, master) - local new = remote(x, y, z, id) - new:tag("player") - new.username = username - new.master = master - new.view = loex.world.new() - new.view.onchunkinserted:catch(player.view_onchunkinserted, new) - new.view.onchunkremoved:catch(player.view_onchunkremoved, new) - new.view.onentityinserted:catch(player.view_onentityinserted, new) - new.view.onentityremoved:catch(player.view_onentityremoved, new) - return new -end - -function player.inview(e, x, y, z) - local range = 12 -- chunks - x, y, z = floor(x / size), floor(y / size), floor(z / size) - local px, py, pz = floor(e.x / size), floor(e.y / size), floor(e.z / size) - return loex.utils.distance3d(px, py, 0, x, y, 0, true) <= range * range -end - -return player diff --git a/server/remote.lua b/server/remote.lua deleted file mode 100644 index 12a24eb..0000000 --- a/server/remote.lua +++ /dev/null @@ -1,17 +0,0 @@ -local remote_mt = { - __index = function(t, k) return t.inner[k] end, - __newindex = function(t, k, v) - if t.inner[k] ~= v then - t.inner[k] = v - t.edits[k] = true - end - end, -} - -return function(x, y, z, id) - local e = loex.entity.new(x, y, z, id) - e:tag("remote") - e.remote = { inner = {}, edits = {} } - setmetatable(e.remote, remote_mt) - return e -end diff --git a/server/services/connection_manager.lua b/server/services/connection_manager.lua new file mode 100644 index 0000000..3039dd4 --- /dev/null +++ b/server/services/connection_manager.lua @@ -0,0 +1,77 @@ +local player = require("services.player") +local packets = require("packets") + +local connection_manager = {} + +function connection_manager.verify_username(g, username) + local validUsername = "^[a-zA-Z_]+$" + + if g.banlist[username] then return "You're banned from this server. Cry about it :-(" end + + if #username < 3 then return "Username too short" end + + if #username > 15 then return "Username too long" end + + if not username:match(validUsername) then return "Invalid username (It has to be good)" end + + if g.taken_usernames[username] then return "Username already taken. Try again :)" end +end + +function connection_manager.init(g) + g.banlist = {} -- TODO: load from list + g.taken_usernames = {} + + g.socket.onconnect:catch(connection_manager.socket_onconnect, g) + g.socket.ondisconnect:catch(connection_manager.socket_ondisconnect, g) + g.socket.onreceive:catch(connection_manager.socket_onreceive, g) +end + +function connection_manager.socket_onconnect(g, peer) + print("new connection!") +end + +function connection_manager.socket_ondisconnect(g, peer) + local socket = g.socket + + local peerdata = socket:peerdata(peer) + if peerdata.playerentity then + print(peerdata.playerentity.username .. " left the game :<") + g.world:tag(peerdata.playerentity, "destroyed") + g.taken_usernames[peerdata.playerentity.username] = nil + end +end + +function connection_manager.socket_onreceive(g, peer, packet) + print("received " .. packet.type) + + local socket = g.socket + local world = g.world + + local peerdata = socket:peerdata(peer) + + if peerdata.playerentity == nil then + if packet.type == "join" then + local err = connection_manager.verify_username(g, packet.username) + if err then + peer:send(packets.joinfailure(err), CHANNEL_ONE) + peer:disconnect_later() + return + end + + -- spawn player + local p = player.entity(g, world, lume.uuid(), 0, 0, 180, packet.username, peer) + + peer:send(packets.joinsuccess(p.id, p.x, p.y, p.z), CHANNEL_ONE) + + peerdata.playerentity = p + + g.taken_usernames[p.username] = true + + print(p.username .. " joined the game :>") + else + error("invalid packet for ghost peer") + end + end +end + +return connection_manager diff --git a/server/services/player.lua b/server/services/player.lua new file mode 100644 index 0000000..752c70d --- /dev/null +++ b/server/services/player.lua @@ -0,0 +1,190 @@ +local overworld = require("gen.overworld") +local packets = require("packets") +local socket = loex.socket + +local size = loex.chunk.size +local entity = loex.entity +local floor = math.floor + +local player = {} + +function player.init(g) + assert(g.master == nil) + g.onupdate:catch(player.update) + g.socket.onreceive:catch(player.socket_onreceive, g) + g.world.ontilemodified:catch(player.world_ontilemodified, g) + g.world.onentityremoved:catch(player.world_onentityremoved, g) +end + +function player.view_onchunkinserted(g, p, c) + p.master:send(packets.chunkadd(c:dump(), c.x, c.y, c.z)) +end + +function player.view_onchunkremoved(g, p, c) + p.master:send(packets.chunkremove(c.x, c.y, c.z)) +end + +function player.view_onentityinserted(g, p, e) + if g.world:tagged(p, "player") then + p.master:send(socket.encode { -- TODO + type="entityadd", + id=e.id, + x=p.x,y=p.y,z=p.z, + username=p.username, + entity_type="player" + }) + end +end + +function player.view_onentityremoved(g, p, e) + p.master:send(packets.entityremove(e.id)) +end + +function player.entity(g, w, id, x, y, z, username, master) + local e = {id=id} + + e.last_throw = os.time() + e.x, e.y, e.z = x, y, z + e.vx, e.vy, e.vz = 0, 0, 0 + e.box = { + x = 0, + y = 0, + z = 0, + w = 0.3, + d = 0.3, + h = 0.9, + } + e.username = username + e.master = master + + e.view = loex.world.new() + e.view.onchunkinserted:catch(player.view_onchunkinserted, g, e) + e.view.onchunkremoved:catch(player.view_onchunkremoved, g, e) + e.view.onentityinserted:catch(player.view_onentityinserted, g, e) + e.view.onentityremoved:catch(player.view_onentityremoved, g, e) + + e.remote = {} + + w:insert(e) + w:tag(e, "box") + w:tag(e, "player") + + return e +end + +function player.inview(e, x, y, z) + local range = 12 -- chunks + x, y, z = floor(x / size), floor(y / size), floor(z / size) + local px, py, pz = floor(e.x / size), floor(e.y / size), floor(e.z / size) + return loex.utils.distance3d(px, py, 0, x, y, 0, true) <= range * range +end + +function player.socket_onreceive(g, peer, d) + if d.type == "join" then return end -- FIXME + local p = g.socket:peerdata(peer).playerentity + if not p then return end + + local world = g.world + + local handles = { + ["move"] = function(p, d) + local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) + p.x = x + p.y = y + p.z = z + end, + ["place"] = function(p, d) + local x, y, z, t = tonumber(d.x), tonumber(d.y), tonumber(d.z), tonumber(d.t) + world:tile(x, y, z, t) + end, + ["breaktile"] = function(p, d) + local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) + world:tile(x, y, z, loex.tiles.air.id) + end, + } + handles[d.type](p, d) +end + +function player.world_onentityremoved(g, e) + local world = g.world + for _, p in pairs(world:query("player")) do + if p.view:entity(e) then p.view:remove(e) end + end +end + +function player.world_ontilemodified(g, x, y, z, t) + local world = g.world + local packet + if t == loex.tiles.air.id then + packet = packets.broken(x, y, z) + else + packet = packets.placed(x, y, z, t) + end + + for _, p in pairs(world:query("player")) do + if p.view:tile(x, y, z) >= 0 then + p.master:send(packet) + end + end +end + +function player.update(g, dt) + local world = g.world + local genstate = g.genstate + local gendistance = 5 + + for _, e in pairs(g.world:query("player")) do + + -- generate world + for i = -gendistance + floor(e.x / size), gendistance + floor(e.x / size) do + for j = -gendistance + floor(e.y / size), gendistance + floor(e.y / size) do + for k = 0, overworld.columnheight - 1 do + if not world:chunk(loex.hash.spatial(i, j, k)) then + local c = overworld:generate(genstate, i, j, k) + world:insertchunk(c) + end + end + end + end + + -- throw snowballs + --if os.time() - e.last_throw > 2 then + -- local new_snowball = snowball.entity(g:next_id(), e.x, e.y, e.z) + -- world:insert(new_snowball) + -- e.last_throw = os.time() + --end + + -- compute chunks in player view + for _, c in pairs(world.chunks) do + if not player.inview(e, c.x * size, c.y * size, c.z * size) then + if e.view.chunks[c.hash] then e.view:removechunk(c.hash) end + else + if not e.view.chunks[c.hash] then e.view:insertchunk(c) end + end + end + + -- compute entities in player view + for _, entity in pairs(world.entities) do + if entity ~= e then + if not player.inview(e, entity.x, entity.y, entity.z) then + if e.view:entity(entity.id) then e.view:remove(entity) end + else + if not e.view:entity(entity.id) then + e.view:insert(entity) + else + if not (entity.remote.x == entity.x and entity.remote.y == entity.y and entity.remote.z == entity.z) then + e.master:send(socket.encode { + type="entityremoteset", + id=entity.id, + properties={x=entity.x,y=entity.y,z=entity.z}, + }) + entity.remote.x, entity.remote.y, entity.remote.z = entity.x, entity.y, entity.z + end + end + end + end + end + end +end + +return player diff --git a/server/services/sync.lua b/server/services/sync.lua new file mode 100644 index 0000000..2d3dec7 --- /dev/null +++ b/server/services/sync.lua @@ -0,0 +1,46 @@ +local sync = {} +local packets = require "packets" + +function sync.init(g) + g.onupdate:catch(sync.update) +end + +function sync.update(g, dt) + local world = g.world + local socket = g.socket + + local genstate = g.genstate + + for _, e in pairs(world.entities) do + +-- -- sync edits +-- local p_edits, p_edits_count = e:property_edits() +-- local t_edits, t_edits_count = e:tag_edits() +-- +-- if p_edits_count > 0 or t_edits_count > 0 then +-- local packet = packets.entityremoteset(e.id, p_edits, t_edits) +-- e:clear_tag_edits() +-- e:clear_property_edits() +-- +-- for _, p in ipairs(world:query("player")) do +-- if p.view:entity(e.id) then p.master:send(packet) end +-- end +-- end + + -- sync destroys + if g.world:tagged(e, "destroyed") then + for _, p in pairs(world:query("player")) do + if p.view:entity(e.id) then + p.view:remove(e) + end + end + end + end + + -- remove destroyed entities + for _, e in pairs(world:query("destroyed")) do + world:remove(e) + end +end + +return sync From b2d4fc2ec7658807d18dc5c200cd2a1c7fffaedc Mon Sep 17 00:00:00 2001 From: AV <48882585+DMClVG@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:26:15 +0200 Subject: [PATCH 3/7] placing and walking sounds --- client/assets/audio/footsteps/footstep-01.wav | Bin 0 -> 41536 bytes client/assets/audio/footsteps/footstep-02.wav | Bin 0 -> 29886 bytes client/assets/audio/footsteps/footstep-03.wav | Bin 0 -> 29886 bytes client/assets/audio/footsteps/footstep-04.wav | Bin 0 -> 45044 bytes client/assets/audio/footsteps/footstep-05.wav | Bin 0 -> 29886 bytes client/assets/audio/footsteps/footstep-06.wav | Bin 0 -> 32218 bytes client/assets/audio/footsteps/footstep-07.wav | Bin 0 -> 29886 bytes client/assets/audio/footsteps/footstep-08.wav | Bin 0 -> 32218 bytes client/assets/audio/footsteps/footstep-09.wav | Bin 0 -> 29886 bytes client/assets/place.wav | Bin 0 -> 24470 bytes client/screens/gamescreen.lua | 37 ++++++++++++++++++ client/services/nethandler.lua | 1 + 12 files changed, 38 insertions(+) create mode 100644 client/assets/audio/footsteps/footstep-01.wav create mode 100644 client/assets/audio/footsteps/footstep-02.wav create mode 100644 client/assets/audio/footsteps/footstep-03.wav create mode 100644 client/assets/audio/footsteps/footstep-04.wav create mode 100644 client/assets/audio/footsteps/footstep-05.wav create mode 100644 client/assets/audio/footsteps/footstep-06.wav create mode 100644 client/assets/audio/footsteps/footstep-07.wav create mode 100644 client/assets/audio/footsteps/footstep-08.wav create mode 100644 client/assets/audio/footsteps/footstep-09.wav create mode 100644 client/assets/place.wav diff --git a/client/assets/audio/footsteps/footstep-01.wav b/client/assets/audio/footsteps/footstep-01.wav new file mode 100644 index 0000000000000000000000000000000000000000..75ecbf70f1f448bdd3e42b57fc9e95272f93b564 GIT binary patch literal 41536 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 literal 0 HcmV?d00001 diff --git a/client/assets/audio/footsteps/footstep-02.wav b/client/assets/audio/footsteps/footstep-02.wav new file mode 100644 index 0000000000000000000000000000000000000000..3e31f1c55c269c182b94d07b9c0afd7a60ec0b1b GIT binary patch literal 29886 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 V33B!Z^1(_&JVT5?azKn^6ae$@eChxI literal 0 HcmV?d00001 diff --git a/client/assets/audio/footsteps/footstep-03.wav b/client/assets/audio/footsteps/footstep-03.wav new file mode 100644 index 0000000000000000000000000000000000000000..7defcf69c4b3dcc0359a0e885e95cf5b741a6ed5 GIT binary patch literal 29886 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 literal 0 HcmV?d00001 diff --git a/client/assets/audio/footsteps/footstep-04.wav b/client/assets/audio/footsteps/footstep-04.wav new file mode 100644 index 0000000000000000000000000000000000000000..bb54eca2544fb66ea09c597aed520690a1ce79e1 GIT binary patch literal 45044 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

mt9GIr4kuMK0gjlDPo6!(v%cdisI&L}JN& z#y^%>kgR(IbFn?S$7s3^whoSBwK=QyCmHv$qose|j^bZp-1;KKV65Dq zzG&_9(<3NL`sG@FXY9SKS>nxHJ;j!_N-0$hbL2|sd;qJT5J$t%GdMU+p7nuV+uL zUrDE|X(|7*HPx2rs;Xyg36$lRt*wzlDV5YY^=;rg@+|jPa;Z7`KX0iyx+=Z4#3~yo z@mOK4rmdyU($*RaDYa5*Cg1<5n-%HFb8r6g+FR9RSsmvK=dZ%_FWZ@YRqbo@K;5Fg)zP*0ROQxarrMH{YFbuV zVx{BS-=#FG_LVg#|5t4)CHTKftMF4J?UHp$$&}iQ9%?=%C)MbyT>H6>BZZp3QkSKL zmlCK!SdJ>KqBbJSb1Pg{c3QDoxxGBIL{!NTCCAjrtd3gOptmfuY;V=-QYtl)EM;7k zU$ea|vqWaRV}HGEn8DbEAp=GfBygHq^`9&rL0Hol1pS&*0dT6N-wjtkg9#< zcWM~fRDVC$LG3-&5&!ACLQPs+)}zFI_@K1S6>3l+rUr4<(WQ1utF78mA*YmPh4mV( zRWgeo{dpTw!y1|VZyi=F5-e6&$x#(LYO`3yh}AVp43={FIUY+~R=Te&q4Wl6XJw^j zODbfPF+V0N~z(zw683wl0tshNVaTq$p>XD71{l~Ze_hH ztyJH&%PY6#%<|l{rIJnA+Oihq|56q;yip>#1_32Qqz*Oct56w#8gEe9O;zWzjis$s zUM9a%Mpfh5?Wk%}wW7Rw?PJUJU+&NE(zAy{DwZz2#4nGnx$ZB^{W(unl1T5fL`_xe z>UW9o@+y&RiPPGiwn9&QS1hfRQMI+gMQsgL+al-xd|Y{Sd9|9TxI%uVublmJO{JdY z-m=E!5mlYbYt_a>)_@uY13p Kv(KkiQU3$955si; literal 0 HcmV?d00001 diff --git a/client/assets/audio/footsteps/footstep-05.wav b/client/assets/audio/footsteps/footstep-05.wav new file mode 100644 index 0000000000000000000000000000000000000000..f40b51a56d04f9d4975558bfbf20bce45ec09243 GIT binary patch literal 29886 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 literal 0 HcmV?d00001 diff --git a/client/assets/audio/footsteps/footstep-06.wav b/client/assets/audio/footsteps/footstep-06.wav new file mode 100644 index 0000000000000000000000000000000000000000..df5dc1502c962cb1e670743c00fffe2839753ad7 GIT binary patch literal 32218 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 UZ0}RMbv=;Irn84VrgvlYFIHMN4*&oF literal 0 HcmV?d00001 diff --git a/client/assets/audio/footsteps/footstep-07.wav b/client/assets/audio/footsteps/footstep-07.wav new file mode 100644 index 0000000000000000000000000000000000000000..99957026e767ecb5f96a3d7d804ee8e9adf3c6aa GIT binary patch literal 29886 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 SP1nP&v(vMazNdc;ss91>MTB+$ literal 0 HcmV?d00001 diff --git a/client/assets/audio/footsteps/footstep-09.wav b/client/assets/audio/footsteps/footstep-09.wav new file mode 100644 index 0000000000000000000000000000000000000000..3a103018b343ea369b9525c6401e54843abcb266 GIT binary patch literal 29886 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 literal 0 HcmV?d00001 diff --git a/client/assets/place.wav b/client/assets/place.wav new file mode 100644 index 0000000000000000000000000000000000000000..66e123671a30cc65492dc65de612c71e5ab52393 GIT binary patch literal 24470 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 syncinterval then @@ -291,10 +318,20 @@ function gamescreen.update(g, dt) } g.master:send(packets.place(x, y, z, placetile), CHANNEL_EVENTS, "reliable") g.world:tile(x, y, z, placetile) + gamescreen.play_place_sound(g,x, y, z) end end end +function gamescreen.play_place_sound(g,x,y,z) + local self = g.gamescreen + local source = love.audio.newSource(self.place_sound) + source:setPosition(x, y, z) + source:setAttenuationDistances(0.5,2000000) + source:setRolloff(0.3) + source:play() +end + function gamescreen.draw(g) local self = g.gamescreen lg.clear(lume.color("#4488ff")) diff --git a/client/services/nethandler.lua b/client/services/nethandler.lua index c92676d..0c028cc 100644 --- a/client/services/nethandler.lua +++ b/client/services/nethandler.lua @@ -24,6 +24,7 @@ function nethandler.placed(g, d) local hash = ("%d/%d/%d"):format(x, y, z) if not g.gamescreen.placequeue[hash] or g.gamescreen.placequeue[hash].t ~= t then g.world:tile(x, y, z, t) + gamescreen.play_place_sound(g, x, y, z) end g.gamescreen.placequeue[hash] = nil end From fd9304924c39d636fb5e63d78685ff127d6ef691 Mon Sep 17 00:00:00 2001 From: AV <48882585+DMClVG@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:26:49 +0200 Subject: [PATCH 4/7] add icon.svg --- icon.svg | 504 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 icon.svg diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..f1c440a --- /dev/null +++ b/icon.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ö + + V + + X + + + From 0cc2a62644c851dac58fca8ab73d14059ece3475 Mon Sep 17 00:00:00 2001 From: AV <48882585+DMClVG@users.noreply.github.com> Date: Sat, 28 Oct 2023 15:38:22 +0200 Subject: [PATCH 5/7] remove player.obj --- client/assets/player.obj | 15 --------------- client/quad.lua | 13 +++++++++++++ client/screens/gamescreen.lua | 2 +- client/services/player.lua | 4 ++-- 4 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 client/assets/player.obj create mode 100644 client/quad.lua diff --git a/client/assets/player.obj b/client/assets/player.obj deleted file mode 100644 index e246a0f..0000000 --- a/client/assets/player.obj +++ /dev/null @@ -1,15 +0,0 @@ -# Blender 3.2.2 -# www.blender.org -o Plane -v 0.000000 1.000000 4.000000 -v -0.000000 1.000000 0.000000 -v 0.000000 -1.000000 4.000000 -v -0.000000 -1.000000 0.000000 -vn -1.0000 -0.0000 -0.0000 -vt 1.001373 0.000100 -vt 1.001373 0.999900 -vt -0.001373 0.000100 -vt -0.001373 0.999900 -s 0 -f 2/2/1 3/3/1 1/1/1 -f 2/2/1 4/4/1 3/3/1 diff --git a/client/quad.lua b/client/quad.lua new file mode 100644 index 0000000..92cf7b1 --- /dev/null +++ b/client/quad.lua @@ -0,0 +1,13 @@ +local a = -1.0 +local b = 1.0 +return function (texture) + return g3d.newModel ({ + {0, a, a, 1, 1}, + {0, b, b, 0, 0}, + {0, b, a, 0, 1}, + + {0, a, a, 1, 1}, + {0, a, b, 1, 0}, + {0, b, b, 0, 0}, +}, texture) +end diff --git a/client/screens/gamescreen.lua b/client/screens/gamescreen.lua index e388e84..ee22156 100644 --- a/client/screens/gamescreen.lua +++ b/client/screens/gamescreen.lua @@ -42,8 +42,8 @@ function gamescreen.init(g, player) g.gamescreen.threadpool[i] = love.thread.newThread("screens/chunkremesh.lua") end - g.gamescreen.player_model = g3d.newModel("assets/player.obj", "assets/saul.png") g.gamescreen.place_sound = love.sound.newSoundData("assets/place.wav") + g.gamescreen.player_model = quad(lg.newImage("assets/saul.png")) g.gamescreen.footstep_sounds = { love.sound.newSoundData("assets/audio/footsteps/footstep-01.wav"), love.sound.newSoundData("assets/audio/footsteps/footstep-02.wav"), diff --git a/client/services/player.lua b/client/services/player.lua index e12e423..8b831a5 100644 --- a/client/services/player.lua +++ b/client/services/player.lua @@ -28,9 +28,9 @@ function player.ondraw(g) for _, e in pairs(g.world:query("player")) do if e ~= g.gamescreen.player then - player_model:setTranslation(e.x, e.y, e.z - 0.9) + player_model:setTranslation(e.x, e.y, e.z) player_model:setRotation(0, 0, math.atan2(e.y - camera[2], e.x - camera[1])) - player_model:setScale(0.1, 1, 0.6) + player_model:setScale(1, 0.6, 0.9) player_model:draw() end end From 0dc49a9a8826a9ef11cdc47b257eff153f00a69c Mon Sep 17 00:00:00 2001 From: AV <48882585+DMClVG@users.noreply.github.com> Date: Sat, 28 Oct 2023 15:40:01 +0200 Subject: [PATCH 6/7] add snowballs --- client/assets/{ => audio}/place.wav | Bin client/assets/audio/snowball_throw.wav | Bin 0 -> 49178 bytes client/assets/quad.obj | 15 ----- client/assets/snowball.png | Bin 0 -> 659 bytes client/main.lua | 1 + client/screens/gamescreen.lua | 28 +++++++++- client/screens/joinscreen.lua | 4 +- common/services/snowball.lua | 62 +++++++++++++++++++++ server/game.lua | 1 + server/services/player.lua | 73 +++++++++++++++---------- 10 files changed, 135 insertions(+), 49 deletions(-) rename client/assets/{ => audio}/place.wav (100%) create mode 100644 client/assets/audio/snowball_throw.wav delete mode 100644 client/assets/quad.obj create mode 100644 client/assets/snowball.png create mode 100644 common/services/snowball.lua diff --git a/client/assets/place.wav b/client/assets/audio/place.wav similarity index 100% rename from client/assets/place.wav rename to client/assets/audio/place.wav diff --git a/client/assets/audio/snowball_throw.wav b/client/assets/audio/snowball_throw.wav new file mode 100644 index 0000000000000000000000000000000000000000..dd0d8c330f97b306f48e1ef56d0eeb519a6e1852 GIT binary patch literal 49178 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 literal 0 HcmV?d00001 diff --git a/client/main.lua b/client/main.lua index b55b29c..8848125 100644 --- a/client/main.lua +++ b/client/main.lua @@ -31,6 +31,7 @@ function love.load(args) love.graphics.setFont(font) game = {} + game.gravity = 42 -- TODO game.socket = socket game.username = username diff --git a/client/screens/gamescreen.lua b/client/screens/gamescreen.lua index ee22156..a4c3b2f 100644 --- a/client/screens/gamescreen.lua +++ b/client/screens/gamescreen.lua @@ -1,6 +1,8 @@ local packets = require("packets") +local quad = require("quad") local physics = loex.physics +local socket = loex.socket local size = loex.chunk.size local lg = love.graphics @@ -42,8 +44,9 @@ function gamescreen.init(g, player) g.gamescreen.threadpool[i] = love.thread.newThread("screens/chunkremesh.lua") end - g.gamescreen.place_sound = love.sound.newSoundData("assets/place.wav") g.gamescreen.player_model = quad(lg.newImage("assets/saul.png")) + g.gamescreen.snowball_model = quad(lg.newImage("assets/snowball.png")) + g.gamescreen.place_sound = love.sound.newSoundData("assets/audio/place.wav") g.gamescreen.footstep_sounds = { love.sound.newSoundData("assets/audio/footsteps/footstep-01.wav"), love.sound.newSoundData("assets/audio/footsteps/footstep-02.wav"), @@ -65,6 +68,7 @@ function gamescreen.init(g, player) g.onupdate:catch(gamescreen.update) g.onmousemoved:catch(gamescreen.onmousemoved) --g.onmousepressed:catch(gamescreen.onmousepressed) + g.onkeypressed:catch(gamescreen.onkeypressed) g.world.ontilemodified:catch(gamescreen.ontilemodified, g) g.world.onentityinserted:catch(gamescreen.onentityinserted, g) g.world.onentityremoved:catch(gamescreen.onentityremoved, g) @@ -73,6 +77,7 @@ function gamescreen.init(g, player) require("services.nethandler").init(g) require("services.player").init(g) + require("common.services.snowball").init(g) end @@ -209,8 +214,6 @@ function gamescreen.update(g, dt) p.ssfootsteps:queue(self.footstep_sounds[math.random(1, #self.footstep_sounds)]) assert(p.ssfootsteps:play()) end - elseif p.ssfootsteps:isPlaying() then - --p.ssfootsteps_end:play() end g3d.camera.position[1] = p.x @@ -357,10 +360,29 @@ function gamescreen.draw(g) lg.rectangle("fill", (lg.getWidth()-cross)/2, (lg.getHeight()-cross)/2, cross, cross) end +function gamescreen.throw_snowball(g) + local x, y, z = g3d.camera.position[1], g3d.camera.position[2], g3d.camera.position[3] + local dx, dy, dz = g3d.camera.getLookVector() + local force = 30 + g.master:send(socket.encode { + type="snowball_throw", + x=x,y=y,z=z, + vx=dx*force,vy=dy*force,vz=dz*force + }) + g.gamescreen.player.sssnowball_throw:play() +end + function gamescreen.onmousemoved(g, x, y, dx, dy, istouch) g3d.camera.firstPersonLook(dx, dy) end +function gamescreen.onkeypressed(g, k) + if k == "q" then + print("thrown snowball") + gamescreen.throw_snowball(g) + end +end + function gamescreen.ontilemodified(g, x, y, z, _) local spatial = loex.hash.spatial local chunk = g.world:chunk(spatial(floor(x / size), floor(y / size), floor(z / size))) diff --git a/client/screens/joinscreen.lua b/client/screens/joinscreen.lua index 97c65e2..1c513f1 100644 --- a/client/screens/joinscreen.lua +++ b/client/screens/joinscreen.lua @@ -45,9 +45,7 @@ function joinscreen.socket_onreceive(g, peer, d) player.ssfootsteps = love.audio.newQueueableSource(48000, 16, 1) player.ssfootsteps:setAttenuationDistances(1, 1) - - player.ssfootsteps_end = love.audio.newSource("assets/footsteps_end.wav", "static") - player.ssfootsteps_end:setAttenuationDistances(1, 1) + player.sssnowball_throw = love.audio.newSource("assets/audio/snowball_throw.wav", "static") g.world:insert(player) g.world:tag(player, "player") diff --git a/common/services/snowball.lua b/common/services/snowball.lua new file mode 100644 index 0000000..e9a766b --- /dev/null +++ b/common/services/snowball.lua @@ -0,0 +1,62 @@ +local physics = loex.physics +local lg = love.graphics + +local snowball = {} + +function snowball.init(g) + g.onupdate:catch(snowball.onupdate) -- client side prediction also + if g.master then + g.socket.onreceive:catch(snowball.onreceive_client, g) + g.ondraw:catch(snowball.ondraw) + end +end + +function snowball.onreceive_client(g, peer, packet) + if packet.type == "entityadd" and packet.entity_type == "snowball" then + print("SNOWBALL ADDED") + snowball.entity(g, packet.id, packet.x, packet.y, packet.z, packet.vx, packet.vy, packet.vz) + end +end + +function snowball.ondraw(g) + local snowball_model = g.gamescreen.snowball_model + local camera = g3d.camera.position + + lg.setMeshCullMode("none") + lg.setColor(1, 1, 1) + + for _, e in pairs(g.world:query("snowball")) do + if e ~= g.gamescreen.player then + local dx,dy,dz = e.x-camera[1], e.y-camera[2], e.z-camera[3] + snowball_model:setTranslation(e.x, e.y, e.z) + snowball_model:setRotation(0, -math.atan2(dz, math.sqrt(dx*dx+dy*dy)), math.atan2(e.y - camera[2], e.x - camera[1])) + snowball_model:setScale(0.4, 0.4, 0.4) + snowball_model:draw() + end + end +end + +function snowball.onupdate(g, dt) + for _, e in pairs(g.world:query("snowball")) do + e.vz = e.vz - g.gravity * dt + e.x = e.x + e.vx * dt + e.y = e.y + e.vy * dt + e.z = e.z + e.vz * dt + + if g.master == nil and physics.intersect_point_world(g.world, e.x, e.y, e.z) then + g.world:tag(e, "destroyed") + end + end +end + +function snowball.entity(g, id, x, y, z, vx, vy, vz) + local e = {id=id} + e.x, e.y, e.z = x, y, z + e.vx, e.vy, e.vz = vx, vy, vz + + g.world:insert(e) + g.world:tag(e, "snowball") + return e +end + +return snowball diff --git a/server/game.lua b/server/game.lua index 88d49ed..ea91685 100644 --- a/server/game.lua +++ b/server/game.lua @@ -30,6 +30,7 @@ function game:init(socket) require("services.connection_manager").init(self) require("services.player").init(self) require("services.sync").init(self) + require("common.services.snowball").init(self) end function game:update(dt) diff --git a/server/services/player.lua b/server/services/player.lua index 752c70d..454e276 100644 --- a/server/services/player.lua +++ b/server/services/player.lua @@ -1,7 +1,8 @@ local overworld = require("gen.overworld") local packets = require("packets") -local socket = loex.socket +local snowball = require("common.services.snowball") +local socket = loex.socket local size = loex.chunk.size local entity = loex.entity local floor = math.floor @@ -25,14 +26,23 @@ function player.view_onchunkremoved(g, p, c) end function player.view_onentityinserted(g, p, e) - if g.world:tagged(p, "player") then - p.master:send(socket.encode { -- TODO + -- TODO: need abstraction for this + if g.world:tagged(e, "player") then + p.master:send(socket.encode { type="entityadd", id=e.id, - x=p.x,y=p.y,z=p.z, - username=p.username, + x=e.x,y=e.y,z=e.z, + username=e.username, entity_type="player" }) + elseif g.world:tagged(e, "snowball") then + p.master:send(socket.encode { + type="entityadd", + id=e.id, + x=e.x,y=e.y,z=e.z, + vx=e.vx,vy=e.vy,vz=e.vz, + entity_type="snowball", + }) end end @@ -101,6 +111,16 @@ function player.socket_onreceive(g, peer, d) local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) world:tile(x, y, z, loex.tiles.air.id) end, + ["snowball_throw"] = function(p, d) + -- throw snowballs + --if os.time() - p.last_throw > 0.05 then + print("thrown snowball") + p.last_throw = os.time() + + local e = snowball.entity(g, lume.uuid(), d.x, d.y, d.z, d.vx, d.vy, d.vz) + e.remote = {} + --end + end } handles[d.type](p, d) end @@ -133,11 +153,11 @@ function player.update(g, dt) local genstate = g.genstate local gendistance = 5 - for _, e in pairs(g.world:query("player")) do + for _, p in pairs(g.world:query("player")) do -- generate world - for i = -gendistance + floor(e.x / size), gendistance + floor(e.x / size) do - for j = -gendistance + floor(e.y / size), gendistance + floor(e.y / size) do + for i = -gendistance + floor(p.x / size), gendistance + floor(p.x / size) do + for j = -gendistance + floor(p.y / size), gendistance + floor(p.y / size) do for k = 0, overworld.columnheight - 1 do if not world:chunk(loex.hash.spatial(i, j, k)) then local c = overworld:generate(genstate, i, j, k) @@ -147,44 +167,41 @@ function player.update(g, dt) end end - -- throw snowballs - --if os.time() - e.last_throw > 2 then - -- local new_snowball = snowball.entity(g:next_id(), e.x, e.y, e.z) - -- world:insert(new_snowball) - -- e.last_throw = os.time() - --end -- compute chunks in player view for _, c in pairs(world.chunks) do - if not player.inview(e, c.x * size, c.y * size, c.z * size) then - if e.view.chunks[c.hash] then e.view:removechunk(c.hash) end + if not player.inview(p, c.x * size, c.y * size, c.z * size) then + if p.view.chunks[c.hash] then p.view:removechunk(c.hash) end else - if not e.view.chunks[c.hash] then e.view:insertchunk(c) end + if not p.view.chunks[c.hash] then p.view:insertchunk(c) end end end -- compute entities in player view - for _, entity in pairs(world.entities) do - if entity ~= e then - if not player.inview(e, entity.x, entity.y, entity.z) then - if e.view:entity(entity.id) then e.view:remove(entity) end + for _, e in pairs(world.entities) do + if e ~= p then + if not player.inview(p, e.x, e.y, e.z) then + if p.view:entity(e.id) then p.view:remove(e) end else - if not e.view:entity(entity.id) then - e.view:insert(entity) + if not p.view:entity(e.id) then + p.view:insert(e) else - if not (entity.remote.x == entity.x and entity.remote.y == entity.y and entity.remote.z == entity.z) then - e.master:send(socket.encode { + -- TODO: this is bad, really bad + if not (e.remote.x == e.x and e.remote.y == e.y and e.remote.z == e.z) then + p.master:send(socket.encode { type="entityremoteset", - id=entity.id, - properties={x=entity.x,y=entity.y,z=entity.z}, + id=e.id, + properties={x=e.x,y=e.y,z=e.z}, }) - entity.remote.x, entity.remote.y, entity.remote.z = entity.x, entity.y, entity.z end end end end end end + for _, e in pairs(world.entities) do + if e.remote then e.remote.x, e.remote.y, e.remote.z = e.x, e.y, e.z end + end end return player From 2c7fdc841538c7e6676c07c1b65e6e644d15fbc5 Mon Sep 17 00:00:00 2001 From: AV <48882585+DMClVG@users.noreply.github.com> Date: Sat, 28 Oct 2023 15:47:09 +0200 Subject: [PATCH 7/7] format code --- client/main.lua | 54 ++---- client/packets.lua | 16 +- client/quad.lua | 18 +- client/screens/chunkremesh.lua | 8 +- client/screens/cursormodel.lua | 73 ++++--- client/screens/errorscreen.lua | 10 +- client/screens/gamescreen.lua | 253 +++++++++++++------------ client/screens/joinscreen.lua | 136 +++++++------ client/services/nethandler.lua | 37 ++-- client/services/player.lua | 31 ++- common/physics.lua | 28 ++- common/services/snowball.lua | 66 +++---- common/signal.lua | 29 ++- common/socket.lua | 28 ++- common/tiles.lua | 2 +- common/world.lua | 60 +++--- server/game.lua | 38 ++-- server/main.lua | 16 +- server/packets.lua | 58 +++--- server/services/connection_manager.lua | 28 ++- server/services/player.lua | 236 ++++++++++++----------- server/services/sync.lua | 63 +++--- 22 files changed, 618 insertions(+), 670 deletions(-) diff --git a/client/main.lua b/client/main.lua index 8848125..a30d03b 100644 --- a/client/main.lua +++ b/client/main.lua @@ -25,52 +25,40 @@ function love.load(args) local username = args[3] local socket = loex.socket.connect(address) - assert(socket) + assert(socket) local font = love.graphics.newFont(23) love.graphics.setFont(font) - game = {} - game.gravity = 42 -- TODO + game = {} + game.gravity = 42 -- TODO - game.socket = socket - game.username = username + game.socket = socket + game.username = username - game.ondraw = loex.signal.new() - game.onupdate = loex.signal.new() - game.onmousemoved = loex.signal.new() - game.onmousepressed = loex.signal.new() - game.onkeypressed = loex.signal.new() - game.onresize = loex.signal.new() - game.onquit = loex.signal.new() + game.ondraw = loex.signal.new() + game.onupdate = loex.signal.new() + game.onmousemoved = loex.signal.new() + game.onmousepressed = loex.signal.new() + game.onkeypressed = loex.signal.new() + game.onresize = loex.signal.new() + game.onquit = loex.signal.new() - require("screens.joinscreen").init(game) + require("screens.joinscreen").init(game) end -function love.update(dt) - game.onupdate:emit(game, dt) -end +function love.update(dt) game.onupdate:emit(game, dt) end -function love.draw() - game.ondraw:emit(game) -end +function love.draw() game.ondraw:emit(game) end -function love.mousepressed(x,y,button,istouch,presses) - game.onmousepressed:emit(game,x,y,button,istouch,presses) +function love.mousepressed(x, y, button, istouch, presses) + game.onmousepressed:emit(game, x, y, button, istouch, presses) end -function love.mousemoved(x, y, dx, dy,istouch) - game.onmousemoved:emit(game,x,y,dx,dy,istouch) -end +function love.mousemoved(x, y, dx, dy, istouch) game.onmousemoved:emit(game, x, y, dx, dy, istouch) end -function love.keypressed(k,scancode,isrepeat) - game.onkeypressed:emit(game, k, scancode, isrepeat) -end +function love.keypressed(k, scancode, isrepeat) game.onkeypressed:emit(game, k, scancode, isrepeat) end -function love.resize(w, h) - game.onresize:emit(game,w,h) -end +function love.resize(w, h) game.onresize:emit(game, w, h) end -function love.quit() - game.onquit:emit(game) -end +function love.quit() game.onquit:emit(game) end diff --git a/client/packets.lua b/client/packets.lua index 84c81df..52c6c6d 100644 --- a/client/packets.lua +++ b/client/packets.lua @@ -1,20 +1,12 @@ local packets = {} local encode = loex.socket.encode -function packets.place(x, y, z, t) - return encode {type="place",x=x,y=y,z=z,t=t} -end +function packets.place(x, y, z, t) return encode { type = "place", x = x, y = y, z = z, t = t } end -function packets.breaktile(x, y, z) - return encode {type="breaktile",x=x,y=y,z=z} -end +function packets.breaktile(x, y, z) return encode { type = "breaktile", x = x, y = y, z = z } end -function packets.join(username) - return encode {type="join", username=username} -end +function packets.join(username) return encode { type = "join", username = username } end -function packets.move(x, y, z) - return encode {type="move", x=x,y=y,z=z} -end +function packets.move(x, y, z) return encode { type = "move", x = x, y = y, z = z } end return packets diff --git a/client/quad.lua b/client/quad.lua index 92cf7b1..0579383 100644 --- a/client/quad.lua +++ b/client/quad.lua @@ -1,13 +1,13 @@ local a = -1.0 local b = 1.0 -return function (texture) - return g3d.newModel ({ - {0, a, a, 1, 1}, - {0, b, b, 0, 0}, - {0, b, a, 0, 1}, +return function(texture) + return g3d.newModel({ + { 0, a, a, 1, 1 }, + { 0, b, b, 0, 0 }, + { 0, b, a, 0, 1 }, - {0, a, a, 1, 1}, - {0, a, b, 1, 0}, - {0, b, b, 0, 0}, -}, texture) + { 0, a, a, 1, 1 }, + { 0, a, b, 1, 0 }, + { 0, b, b, 0, 0 }, + }, texture) end diff --git a/client/screens/chunkremesh.lua b/client/screens/chunkremesh.lua index f3922a9..eb32159 100644 --- a/client/screens/chunkremesh.lua +++ b/client/screens/chunkremesh.lua @@ -2,9 +2,7 @@ require("love.math") require("love.data") local ffi = require("ffi") -local channel, cx, cy, cz, blockdata, size, tids, -n1, n2, n3, n4, n5, n6, -c0, c1, c2, c3, c4, c5, c6 = ... +local channel, cx, cy, cz, blockdata, size, tids, n1, n2, n3, n4, n5, n6, c0, c1, c2, c3, c4, c5, c6 = ... local blockdatapointer = ffi.cast("uint8_t *", blockdata:getFFIPointer()) local n1p = n1 and ffi.cast("uint8_t *", n1:getFFIPointer()) @@ -23,7 +21,7 @@ local c5p = c5 and ffi.cast("uint8_t *", c5:getFFIPointer()) local c6p = c6 and ffi.cast("uint8_t *", c6:getFFIPointer()) local function getc(pointer, x, y, z) - if true then return 255 end + if true then return 255 end local i = x + size * y + size * size * z -- if this block is outside of the chunk, check the neighboring chunks if they exist @@ -92,7 +90,7 @@ if count > 0 then for i = start, stop, step do local primary = i % 2 == 1 local secondary = i > 2 and i < 6 - local c = getc(c0p, x,y,z) / 255 + local c = getc(c0p, x, y, z) / 255 datapointer[dataindex].x = x + (mx == 1 and primary and 1 or 0) + (mx == 2 and secondary and 1 or 0) datapointer[dataindex].y = y + (my == 1 and primary and 1 or 0) + (my == 2 and secondary and 1 or 0) datapointer[dataindex].z = z + (mz == 1 and primary and 1 or 0) + (mz == 2 and secondary and 1 or 0) diff --git a/client/screens/cursormodel.lua b/client/screens/cursormodel.lua index 0dd56bd..4d0bc28 100644 --- a/client/screens/cursormodel.lua +++ b/client/screens/cursormodel.lua @@ -1,43 +1,42 @@ local a = -0.005 local b = 1.005 return g3d.newModel { - { a, a, a }, - { b, a, a }, - { b, a, a }, - { a, a, a }, - { a, a, b }, - { a, a, b }, - { b, a, b }, - { a, a, b }, - { a, a, b }, - { b, a, b }, - { b, a, a }, - { b, a, a }, + { a, a, a }, + { b, a, a }, + { b, a, a }, + { a, a, a }, + { a, a, b }, + { a, a, b }, + { b, a, b }, + { a, a, b }, + { a, a, b }, + { b, a, b }, + { b, a, a }, + { b, a, a }, - { a, b, a }, - { b, b, a }, - { b, b, a }, - { a, b, a }, - { a, b, b }, - { a, b, b }, - { b, b, b }, - { a, b, b }, - { a, b, b }, - { b, b, b }, - { b, b, a }, - { b, b, a }, + { a, b, a }, + { b, b, a }, + { b, b, a }, + { a, b, a }, + { a, b, b }, + { a, b, b }, + { b, b, b }, + { a, b, b }, + { a, b, b }, + { b, b, b }, + { b, b, a }, + { b, b, a }, - { a, a, a }, - { a, b, a }, - { a, b, a }, - { b, a, a }, - { b, b, a }, - { b, b, a }, - { a, a, b }, - { a, b, b }, - { a, b, b }, - { b, a, b }, - { b, b, b }, - { b, b, b }, + { a, a, a }, + { a, b, a }, + { a, b, a }, + { b, a, a }, + { b, b, a }, + { b, b, a }, + { a, a, b }, + { a, b, b }, + { a, b, b }, + { b, a, b }, + { b, b, b }, + { b, b, b }, } - diff --git a/client/screens/errorscreen.lua b/client/screens/errorscreen.lua index 2b3c9aa..d5e9c1f 100644 --- a/client/screens/errorscreen.lua +++ b/client/screens/errorscreen.lua @@ -1,17 +1,17 @@ local errorscreen = {} local lg = love.graphics -function errorscreen.init(g, cause) - g.errorscreen = {} - g.errorscreen.cause = cause - g.ondraw:catch(errorscreen.draw) +function errorscreen.init(g, cause) + g.errorscreen = {} + g.errorscreen.cause = cause + g.ondraw:catch(errorscreen.draw) end function errorscreen.draw(g) lg.clear(1, 0, 0) local w, h = lg.getWidth(), lg.getHeight() - local cause = g.errorscreen.cause + local cause = g.errorscreen.cause w = w - lg.getFont():getWidth(cause) h = h - lg.getFont():getHeight() diff --git a/client/screens/gamescreen.lua b/client/screens/gamescreen.lua index a4c3b2f..79aef75 100644 --- a/client/screens/gamescreen.lua +++ b/client/screens/gamescreen.lua @@ -14,87 +14,82 @@ local CEILING = 10 local gamescreen = {} function gamescreen.init(g, player) - g.gamescreen = { - cursor = {}, - cursormodel = require("screens.cursormodel"), - texturepack = lg.newImage("assets/texturepack.png"), - threadpool = {}, - threadusage = 0, - mouse = {}, - placequeue = {}, - breakqueue = {}, - remeshqueue = {}, - remeshchannel = love.thread.newChannel(), - frameremeshes = 0, - synctimer = 0, - player=player, - } - - player.box = { - x = 0, - y = 0, - z = 0, - w = 0.3, - d = 0.3, - h = 0.9, - } - - -- load up some threads so that chunk meshing won't block the main thread - for i = 1, 8 do - g.gamescreen.threadpool[i] = love.thread.newThread("screens/chunkremesh.lua") - end - - g.gamescreen.player_model = quad(lg.newImage("assets/saul.png")) - g.gamescreen.snowball_model = quad(lg.newImage("assets/snowball.png")) - g.gamescreen.place_sound = love.sound.newSoundData("assets/audio/place.wav") - g.gamescreen.footstep_sounds = { - love.sound.newSoundData("assets/audio/footsteps/footstep-01.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-02.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-03.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-04.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-05.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-06.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-07.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-08.wav"), - love.sound.newSoundData("assets/audio/footsteps/footstep-09.wav"), - } - - g.gamescreen.gravity = 42 + g.gamescreen = { + cursor = {}, + cursormodel = require("screens.cursormodel"), + texturepack = lg.newImage("assets/texturepack.png"), + threadpool = {}, + threadusage = 0, + mouse = {}, + placequeue = {}, + breakqueue = {}, + remeshqueue = {}, + remeshchannel = love.thread.newChannel(), + frameremeshes = 0, + synctimer = 0, + player = player, + } + + player.box = { + x = 0, + y = 0, + z = 0, + w = 0.3, + d = 0.3, + h = 0.9, + } + + -- load up some threads so that chunk meshing won't block the main thread + for i = 1, 8 do + g.gamescreen.threadpool[i] = love.thread.newThread("screens/chunkremesh.lua") + end + + g.gamescreen.player_model = quad(lg.newImage("assets/saul.png")) + g.gamescreen.snowball_model = quad(lg.newImage("assets/snowball.png")) + g.gamescreen.place_sound = love.sound.newSoundData("assets/audio/place.wav") + g.gamescreen.footstep_sounds = { + love.sound.newSoundData("assets/audio/footsteps/footstep-01.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-02.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-03.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-04.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-05.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-06.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-07.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-08.wav"), + love.sound.newSoundData("assets/audio/footsteps/footstep-09.wav"), + } + + g.gamescreen.gravity = 42 lg.setMeshCullMode("back") - love.audio.setDistanceModel("inverseclamped") + love.audio.setDistanceModel("inverseclamped") - g.ondraw:catch(gamescreen.draw) - g.onupdate:catch(gamescreen.update) - g.onmousemoved:catch(gamescreen.onmousemoved) - --g.onmousepressed:catch(gamescreen.onmousepressed) - g.onkeypressed:catch(gamescreen.onkeypressed) + g.ondraw:catch(gamescreen.draw) + g.onupdate:catch(gamescreen.update) + g.onmousemoved:catch(gamescreen.onmousemoved) + --g.onmousepressed:catch(gamescreen.onmousepressed) + g.onkeypressed:catch(gamescreen.onkeypressed) g.world.ontilemodified:catch(gamescreen.ontilemodified, g) g.world.onentityinserted:catch(gamescreen.onentityinserted, g) g.world.onentityremoved:catch(gamescreen.onentityremoved, g) g.world.onchunkinserted:catch(gamescreen.onchunkinserted, g) g.world.onchunkremoved:catch(gamescreen.onchunkremoved, g) - require("services.nethandler").init(g) - require("services.player").init(g) - require("common.services.snowball").init(g) + require("services.nethandler").init(g) + require("services.player").init(g) + require("common.services.snowball").init(g) end - function gamescreen.onchunkinserted(g, chunk) local x, y, z = chunk.x, chunk.y, chunk.z - gamescreen.requestremesh(g,chunk) + gamescreen.requestremesh(g, chunk) end -function gamescreen.onchunkremoved(g,chunk) end +function gamescreen.onchunkremoved(g, chunk) end -function gamescreen.onentityinserted(g,entity) - print(entity.id .. " added") -end +function gamescreen.onentityinserted(g, entity) print(entity.id .. " added") end -function gamescreen.onentityremoved(g,entity) - print(entity.id .. " removed") -end +function gamescreen.onentityremoved(g, entity) print(entity.id .. " removed") end function gamescreen.onresize(g, w, h) g3d.camera.aspectRatio = w / h @@ -102,9 +97,9 @@ function gamescreen.onresize(g, w, h) end function gamescreen.update(g, dt) - local self = g.gamescreen - local mouse = self.mouse - local threadpool = self.threadpool + local self = g.gamescreen + local mouse = self.mouse + local threadpool = self.threadpool -- collect mouse inputs mouse.wasleft, mouse.wasright = mouse.left, mouse.right @@ -163,16 +158,37 @@ function gamescreen.update(g, dt) -- send over the neighboring chunks to the thread -- so that voxels on the edges can face themselves properly local n1, n2, n3, n4, n5, n6 = g.world:neighbourhood(c.x, c.y, c.z) - if not (n1 and n2 and n3 and n4 and n5 and n6) - or not g.world:chunk(loex.hash.spatial(c.x,c.y,CEILING-1)) then + if + not (n1 and n2 and n3 and n4 and n5 and n6) or not g.world:chunk(loex.hash.spatial(c.x, c.y, CEILING - 1)) + then offi = offi + 1 break end - c1, c2, c3, c4, c5, c6 = n1.cdata, n2.cdata, n3.cdata, n4.cdata, n5.cdata, n6.cdata n1, n2, n3, n4, n5, n6 = n1.data, n2.data, n3.data, n4.data, n5.data, n6.data - thread:start(self.remeshchannel, c.x, c.y, c.z, c.data, size, loex.tiles.id, n1, n2, n3, n4, n5, n6, c.cdata, c1,c2,c3,c4,c5,c6) + thread:start( + self.remeshchannel, + c.x, + c.y, + c.z, + c.data, + size, + loex.tiles.id, + n1, + n2, + n3, + n4, + n5, + n6, + c.cdata, + c1, + c2, + c3, + c4, + c5, + c6 + ) table.remove(self.remeshqueue, 1 + offi) self.threadusage = self.threadusage + 1 -- use up thread break @@ -180,7 +196,7 @@ function gamescreen.update(g, dt) end end - -- player movement + -- player movement local keyboard = love.keyboard local speed, jumpforce = 5, 12 local dirx, diry, dirz = g3d.camera.getLookVector() @@ -190,7 +206,7 @@ function gamescreen.update(g, dt) if keyboard.isDown("w") then move.x = move.x + dirx move.y = move.y + diry - end + end if keyboard.isDown("s") then move.x = move.x - dirx move.y = move.y - diry @@ -199,7 +215,7 @@ function gamescreen.update(g, dt) if keyboard.isDown("a") then move.x = move.x - diry move.y = move.y + dirx - end + end if keyboard.isDown("d") then move.x = move.x + diry move.y = move.y - dirx @@ -209,27 +225,27 @@ function gamescreen.update(g, dt) p.vz = p.vz - self.gravity * dt local onground = physics.moveandcollide(g.world, p, p.box, dt) - if onground and (move.x ~= 0 or move.y ~= 0) then - if not p.ssfootsteps:isPlaying() then - p.ssfootsteps:queue(self.footstep_sounds[math.random(1, #self.footstep_sounds)]) - assert(p.ssfootsteps:play()) - end - end + if onground and (move.x ~= 0 or move.y ~= 0) then + if not p.ssfootsteps:isPlaying() then + p.ssfootsteps:queue(self.footstep_sounds[math.random(1, #self.footstep_sounds)]) + assert(p.ssfootsteps:play()) + end + end g3d.camera.position[1] = p.x g3d.camera.position[2] = p.y g3d.camera.position[3] = p.z + 0.7 g3d.camera.lookInDirection() - -- player jump + -- player jump if onground and keyboard.isDown("space") then p.vz = jumpforce end - do + do local dx, dy, dz = g3d.camera.getLookVector() local x, y, z = g3d.camera.position[1], g3d.camera.position[2], g3d.camera.position[3] - love.audio.setPosition(x,y,z) - love.audio.setOrientation(dx,dy,dz, 0,0,1) - end + love.audio.setPosition(x, y, z) + love.audio.setOrientation(dx, dy, dz, 0, 0, 1) + end local syncinterval = 1 / 20 self.synctimer = self.synctimer + dt @@ -280,7 +296,8 @@ function gamescreen.update(g, dt) if tile == -1 then break end if tile > 0 then self.cursor = {} - self.cursor.placex, self.cursor.placey, self.cursor.placez = floor(x - dx * step), floor(y - dy * step), floor(z - dz * step) + self.cursor.placex, self.cursor.placey, self.cursor.placez = + floor(x - dx * step), floor(y - dy * step), floor(z - dz * step) self.cursor.x, self.cursor.y, self.cursor.z = tx, ty, tz break end @@ -289,7 +306,7 @@ function gamescreen.update(g, dt) local placetile = loex.tiles.slime.id - -- left click to break block + -- left click to break block if mouse.leftclick and self.cursor then local x, y, z = self.cursor.x, self.cursor.y, self.cursor.z self.breakqueue[("%d/%d/%d"):format(x, y, z)] = { @@ -321,22 +338,22 @@ function gamescreen.update(g, dt) } g.master:send(packets.place(x, y, z, placetile), CHANNEL_EVENTS, "reliable") g.world:tile(x, y, z, placetile) - gamescreen.play_place_sound(g,x, y, z) + gamescreen.play_place_sound(g, x, y, z) end end end -function gamescreen.play_place_sound(g,x,y,z) - local self = g.gamescreen - local source = love.audio.newSource(self.place_sound) - source:setPosition(x, y, z) - source:setAttenuationDistances(0.5,2000000) - source:setRolloff(0.3) - source:play() +function gamescreen.play_place_sound(g, x, y, z) + local self = g.gamescreen + local source = love.audio.newSource(self.place_sound) + source:setPosition(x, y, z) + source:setAttenuationDistances(0.5, 2000000) + source:setRolloff(0.3) + source:play() end function gamescreen.draw(g) - local self = g.gamescreen + local self = g.gamescreen lg.clear(lume.color("#4488ff")) lg.setColor(1, 1, 1) @@ -354,33 +371,35 @@ function gamescreen.draw(g) end lg.setMeshCullMode("back") - -- draw crosshair - local cross = 4 - lg.setColor(1,1,1) - lg.rectangle("fill", (lg.getWidth()-cross)/2, (lg.getHeight()-cross)/2, cross, cross) + -- draw crosshair + local cross = 4 + lg.setColor(1, 1, 1) + lg.rectangle("fill", (lg.getWidth() - cross) / 2, (lg.getHeight() - cross) / 2, cross, cross) end function gamescreen.throw_snowball(g) local x, y, z = g3d.camera.position[1], g3d.camera.position[2], g3d.camera.position[3] local dx, dy, dz = g3d.camera.getLookVector() - local force = 30 - g.master:send(socket.encode { - type="snowball_throw", - x=x,y=y,z=z, - vx=dx*force,vy=dy*force,vz=dz*force - }) - g.gamescreen.player.sssnowball_throw:play() + local force = 30 + g.master:send(socket.encode { + type = "snowball_throw", + x = x, + y = y, + z = z, + vx = dx * force, + vy = dy * force, + vz = dz * force, + }) + g.gamescreen.player.sssnowball_throw:play() end -function gamescreen.onmousemoved(g, x, y, dx, dy, istouch) - g3d.camera.firstPersonLook(dx, dy) -end +function gamescreen.onmousemoved(g, x, y, dx, dy, istouch) g3d.camera.firstPersonLook(dx, dy) end -function gamescreen.onkeypressed(g, k) - if k == "q" then - print("thrown snowball") - gamescreen.throw_snowball(g) - end +function gamescreen.onkeypressed(g, k) + if k == "q" then + print("thrown snowball") + gamescreen.throw_snowball(g) + end end function gamescreen.ontilemodified(g, x, y, z, _) @@ -403,11 +422,11 @@ function gamescreen.ontilemodified(g, x, y, z, _) end function gamescreen.requestremesh(g, c, priority) - local self = g.gamescreen + local self = g.gamescreen -- don't add a nil chunk or a chunk that's already in the queue local world = g.world if not c or c.inremesh or not c.data then return end - if not c.cdata then c.cdata = love.data.newByteData(size^3) end + if not c.cdata then c.cdata = love.data.newByteData(size ^ 3) end c.inremesh = true if priority then diff --git a/client/screens/joinscreen.lua b/client/screens/joinscreen.lua index 1c513f1..f8d28a8 100644 --- a/client/screens/joinscreen.lua +++ b/client/screens/joinscreen.lua @@ -7,96 +7,94 @@ local lg = love.graphics local joinscreen = {} function joinscreen.init(g) - local signals = { - g.ondraw:catch(joinscreen.draw), - g.socket.onconnect:catch(joinscreen.socket_onconnect, g), - g.socket.onreceive:catch(joinscreen.socket_onreceive, g), - g.socket.ondisconnect:catch(joinscreen.socket_ondisconnect, g), - } - g.joinscreen = {signals=signals} - - -- base functions - g.onupdate:catch(function(g) g.socket:service() end) - g.onquit:catch(function(g) g.socket:disconnect() end) + local signals = { + g.ondraw:catch(joinscreen.draw), + g.socket.onconnect:catch(joinscreen.socket_onconnect, g), + g.socket.onreceive:catch(joinscreen.socket_onreceive, g), + g.socket.ondisconnect:catch(joinscreen.socket_ondisconnect, g), + } + g.joinscreen = { signals = signals } + + -- base functions + g.onupdate:catch(function(g) g.socket:service() end) + g.onquit:catch(function(g) g.socket:disconnect() end) end function joinscreen.cleanup(g) - for _, signal in ipairs(g.joinscreen.signals) do signal:destroy() end - g.joinscreen = nil + for _, signal in ipairs(g.joinscreen.signals) do + signal:destroy() + end + g.joinscreen = nil end function joinscreen.error(g, cause) - joinscreen.cleanup(g) - errorscreen.init(g, cause) + joinscreen.cleanup(g) + errorscreen.init(g, cause) end function joinscreen.socket_onreceive(g, peer, d) - print("received " .. d.type) - if d.type == "joinsuccess" then - local spawnx, spawny, spawnz = d.x, d.y, d.z - assert(d.id) - - g.world = loex.world.new() - - local player = {id=d.id} - player.x, player.y, player.z = spawnx, spawny, spawnz - player.vx, player.vy, player.vz = 0,0,0 - player.username = g.username - - player.ssfootsteps = love.audio.newQueueableSource(48000, 16, 1) - player.ssfootsteps:setAttenuationDistances(1, 1) - player.sssnowball_throw = love.audio.newSource("assets/audio/snowball_throw.wav", "static") - - g.world:insert(player) - g.world:tag(player, "player") - - print( - ("Joined under username " .. player.username .. " (ID: " .. player.id .. ") at spawn point %d, %d, %d"):format( - spawnx, - spawny, - spawnz - ) - ) - joinscreen.cleanup(g) - gamescreen.init(g, player) - elseif d.type == "joinfailure" then - joinscreen.error(g, d.cause) - else - joinscreen.error(g,"unexpected packet type " .. d.typed) - end + print("received " .. d.type) + if d.type == "joinsuccess" then + local spawnx, spawny, spawnz = d.x, d.y, d.z + assert(d.id) + + g.world = loex.world.new() + + local player = { id = d.id } + player.x, player.y, player.z = spawnx, spawny, spawnz + player.vx, player.vy, player.vz = 0, 0, 0 + player.username = g.username + + player.ssfootsteps = love.audio.newQueueableSource(48000, 16, 1) + player.ssfootsteps:setAttenuationDistances(1, 1) + player.sssnowball_throw = love.audio.newSource("assets/audio/snowball_throw.wav", "static") + + g.world:insert(player) + g.world:tag(player, "player") + + print( + ("Joined under username " .. player.username .. " (ID: " .. player.id .. ") at spawn point %d, %d, %d"):format( + spawnx, + spawny, + spawnz + ) + ) + joinscreen.cleanup(g) + gamescreen.init(g, player) + elseif d.type == "joinfailure" then + joinscreen.error(g, d.cause) + else + joinscreen.error(g, "unexpected packet type " .. d.typed) + end end function joinscreen.socket_onconnect(g, peer) - print("connected!") + print("connected!") - -- set master peer - g.master = peer + -- set master peer + g.master = peer - -- send join - local join_packet = packets.join(g.username) - g.master:send(join_packet) + -- send join + local join_packet = packets.join(g.username) + g.master:send(join_packet) end -function joinscreen.socket_ondisconnect(g) - joinscreen.error(g,"connection closed") -end +function joinscreen.socket_ondisconnect(g) joinscreen.error(g, "connection closed") end -function joinscreen.update(g) - g.socket:service() -end +function joinscreen.update(g) g.socket:service() end function joinscreen.draw() local w, h = lg.getWidth(), lg.getHeight() - local l = 3 - local s = "" - local k = math.floor(love.timer.getTime()) % l - for i = 0, l-1 do - if i == k then - s = s .. "O" - else - s = s .. "o" - end + local l = 3 + local s = "" + local k = math.floor(love.timer.getTime()) % l + for i = 0, l - 1 do + if i == k then + s = s .. "O" + else + s = s .. "o" + end end w = w - lg.getFont():getWidth(s) diff --git a/client/services/nethandler.lua b/client/services/nethandler.lua index 0c028cc..b87c3df 100644 --- a/client/services/nethandler.lua +++ b/client/services/nethandler.lua @@ -1,14 +1,12 @@ local nethandler = {} -function nethandler.init(g) - g.socket.onreceive:catch(nethandler.onreceive, g) -end +function nethandler.init(g) g.socket.onreceive:catch(nethandler.onreceive, g) end function nethandler.onreceive(g, peer, d) - if d.type == "joinsuccess" then return end -- FIXME + if d.type == "joinsuccess" then return end -- FIXME - print("received " .. d.type) - nethandler[d.type](g, d) + print("received " .. d.type) + nethandler[d.type](g, d) end function nethandler.broken(g, d) @@ -19,13 +17,13 @@ function nethandler.broken(g, d) end function nethandler.placed(g, d) - local gamescreen = require("screens.gamescreen") + local gamescreen = require("screens.gamescreen") local x, y, z, t = tonumber(d.x), tonumber(d.y), tonumber(d.z), tonumber(d.t) local hash = ("%d/%d/%d"):format(x, y, z) - if not g.gamescreen.placequeue[hash] or g.gamescreen.placequeue[hash].t ~= t then - g.world:tile(x, y, z, t) - gamescreen.play_place_sound(g, x, y, z) - end + if not g.gamescreen.placequeue[hash] or g.gamescreen.placequeue[hash].t ~= t then + g.world:tile(x, y, z, t) + gamescreen.play_place_sound(g, x, y, z) + end g.gamescreen.placequeue[hash] = nil end @@ -39,20 +37,17 @@ function nethandler.entitymove(g, d) entity.z = z end -function nethandler.entityadd(g, d) -end +function nethandler.entityadd(g, d) end -function nethandler.entityremove(g, d) - g.world:remove(g.world:entity(d.id)) -end +function nethandler.entityremove(g, d) g.world:remove(g.world:entity(d.id)) end function nethandler.entityremoteset(g, d) local entity = g.world:entity(d.id) - for k, v in pairs(d.properties) do - if not (entity.id == g.gamescreen.player.id and k:match("[xyz]")) then -- TODO: position correction - entity[k] = v - end - end + for k, v in pairs(d.properties) do + if not (entity.id == g.gamescreen.player.id and k:match("[xyz]")) then -- TODO: position correction + entity[k] = v + end + end end function nethandler.chunkadd(g, d) diff --git a/client/services/player.lua b/client/services/player.lua index 8b831a5..159f388 100644 --- a/client/services/player.lua +++ b/client/services/player.lua @@ -2,36 +2,35 @@ local player = {} local lg = love.graphics function player.init(g) - g.socket.onreceive:catch(player.onreceive, g) + g.socket.onreceive:catch(player.onreceive, g) - g.onupdate:catch(player.onupdate) - g.ondraw:catch(player.ondraw) + g.onupdate:catch(player.onupdate) + g.ondraw:catch(player.ondraw) end function player.onreceive(g, _, packet) - if packet.type == "entityadd" and packet.entity_type == "player" then - local e = { id=packet.id,x=packet.x,y=packet.y,z=packet.z, username=packet.username } - g.world:insert(e) - g.world:tag(e, "player") - end + if packet.type == "entityadd" and packet.entity_type == "player" then + local e = { id = packet.id, x = packet.x, y = packet.y, z = packet.z, username = packet.username } + g.world:insert(e) + g.world:tag(e, "player") + end end -function player.onupdate(g) -end +function player.onupdate(g) end function player.ondraw(g) - local player_model = g.gamescreen.player_model - local camera = g3d.camera.position + local player_model = g.gamescreen.player_model + local camera = g3d.camera.position lg.setMeshCullMode("none") lg.setColor(1, 1, 1) for _, e in pairs(g.world:query("player")) do if e ~= g.gamescreen.player then - player_model:setTranslation(e.x, e.y, e.z) - player_model:setRotation(0, 0, math.atan2(e.y - camera[2], e.x - camera[1])) - player_model:setScale(1, 0.6, 0.9) - player_model:draw() + player_model:setTranslation(e.x, e.y, e.z) + player_model:setRotation(0, 0, math.atan2(e.y - camera[2], e.x - camera[1])) + player_model:setScale(1, 0.6, 0.9) + player_model:draw() end end end diff --git a/common/physics.lua b/common/physics.lua index 5170b7c..b60424d 100644 --- a/common/physics.lua +++ b/common/physics.lua @@ -1,19 +1,19 @@ local physics = {} -local tiles = loex.tiles +local tiles = loex.tiles local cube = { w = 0.5, h = 0.5, d = 0.5, x = 0, y = 0, z = 0 } local epsilonx, epsilony, epsilonz = 0.0045, 0.002, 0.005 local utils = loex.utils local floor, min, max = math.floor, math.min, math.max local expandb, iterb, intersectbb = utils.expandb, utils.iterb, utils.intersectbb -function physics.intersect_point_world(world, x, y, z) - local i,j,k = floor(x), floor(y), floor(z) - return world:tile(i, j, k) > 0 +function physics.intersect_point_world(world, x, y, z) + local i, j, k = floor(x), floor(y), floor(z) + return world:tile(i, j, k) > 0 end function physics.moveandcollide(world, e, box, dt) - local bounce = 0 + local bounce = 0 local dx, dy, dz = e.vx * dt, e.vy * dt, e.vz * dt local x, y, z = e.x, e.y, e.z @@ -21,8 +21,8 @@ function physics.moveandcollide(world, e, box, dt) local onground = false - -- Z MOVEMENT - local collidedz = false + -- Z MOVEMENT + local collidedz = false local boxz = expandb(lume.clone(box), 0, 0, dz) boxz.x = boxz.x + x boxz.y = boxz.y + y @@ -37,19 +37,17 @@ function physics.moveandcollide(world, e, box, dt) if dz < 0 then dz = min(max(dz, (k + 1 + epsilonz) - (z - h)), 0) onground = true - bounce = (bounce + (tiles.id[t].bounce or 0)) * 0.5 + bounce = (bounce + (tiles.id[t].bounce or 0)) * 0.5 else dz = max(min(dz, (k - epsilonz) - (z + h)), 0) - end - collidedz = true + end + collidedz = true end end end - if collidedz then - e.vz = -e.vz * bounce - end + if collidedz then e.vz = -e.vz * bounce end - -- X MOVEMENT + -- X MOVEMENT local boxx = expandb(lume.clone(box), dx, 0, dz) boxx.x = boxx.x + x boxx.y = boxx.y + y @@ -71,7 +69,7 @@ function physics.moveandcollide(world, e, box, dt) end end - -- Y MOVEMENT + -- Y MOVEMENT local boxy = expandb(lume.clone(box), dx, dy, dz) boxy.x = boxy.x + x boxy.y = boxy.y + y diff --git a/common/services/snowball.lua b/common/services/snowball.lua index e9a766b..3ef501d 100644 --- a/common/services/snowball.lua +++ b/common/services/snowball.lua @@ -4,59 +4,61 @@ local lg = love.graphics local snowball = {} function snowball.init(g) - g.onupdate:catch(snowball.onupdate) -- client side prediction also - if g.master then - g.socket.onreceive:catch(snowball.onreceive_client, g) - g.ondraw:catch(snowball.ondraw) - end + g.onupdate:catch(snowball.onupdate) -- client side prediction also + if g.master then + g.socket.onreceive:catch(snowball.onreceive_client, g) + g.ondraw:catch(snowball.ondraw) + end end function snowball.onreceive_client(g, peer, packet) - if packet.type == "entityadd" and packet.entity_type == "snowball" then - print("SNOWBALL ADDED") - snowball.entity(g, packet.id, packet.x, packet.y, packet.z, packet.vx, packet.vy, packet.vz) - end + if packet.type == "entityadd" and packet.entity_type == "snowball" then + print("SNOWBALL ADDED") + snowball.entity(g, packet.id, packet.x, packet.y, packet.z, packet.vx, packet.vy, packet.vz) + end end function snowball.ondraw(g) - local snowball_model = g.gamescreen.snowball_model - local camera = g3d.camera.position + local snowball_model = g.gamescreen.snowball_model + local camera = g3d.camera.position lg.setMeshCullMode("none") lg.setColor(1, 1, 1) for _, e in pairs(g.world:query("snowball")) do if e ~= g.gamescreen.player then - local dx,dy,dz = e.x-camera[1], e.y-camera[2], e.z-camera[3] - snowball_model:setTranslation(e.x, e.y, e.z) - snowball_model:setRotation(0, -math.atan2(dz, math.sqrt(dx*dx+dy*dy)), math.atan2(e.y - camera[2], e.x - camera[1])) - snowball_model:setScale(0.4, 0.4, 0.4) - snowball_model:draw() + local dx, dy, dz = e.x - camera[1], e.y - camera[2], e.z - camera[3] + snowball_model:setTranslation(e.x, e.y, e.z) + snowball_model:setRotation( + 0, + -math.atan2(dz, math.sqrt(dx * dx + dy * dy)), + math.atan2(e.y - camera[2], e.x - camera[1]) + ) + snowball_model:setScale(0.4, 0.4, 0.4) + snowball_model:draw() end end end function snowball.onupdate(g, dt) - for _, e in pairs(g.world:query("snowball")) do - e.vz = e.vz - g.gravity * dt - e.x = e.x + e.vx * dt - e.y = e.y + e.vy * dt - e.z = e.z + e.vz * dt - - if g.master == nil and physics.intersect_point_world(g.world, e.x, e.y, e.z) then - g.world:tag(e, "destroyed") - end - end + for _, e in pairs(g.world:query("snowball")) do + e.vz = e.vz - g.gravity * dt + e.x = e.x + e.vx * dt + e.y = e.y + e.vy * dt + e.z = e.z + e.vz * dt + + if g.master == nil and physics.intersect_point_world(g.world, e.x, e.y, e.z) then g.world:tag(e, "destroyed") end + end end function snowball.entity(g, id, x, y, z, vx, vy, vz) - local e = {id=id} - e.x, e.y, e.z = x, y, z - e.vx, e.vy, e.vz = vx, vy, vz + local e = { id = id } + e.x, e.y, e.z = x, y, z + e.vx, e.vy, e.vz = vx, vy, vz - g.world:insert(e) - g.world:tag(e, "snowball") - return e + g.world:insert(e) + g.world:tag(e, "snowball") + return e end return snowball diff --git a/common/signal.lua b/common/signal.lua index a48f5a1..5aa6320 100644 --- a/common/signal.lua +++ b/common/signal.lua @@ -4,36 +4,33 @@ signal.__index = signal local signal_handle = {} signal_handle.__index = signal_handle -function signal_handle:destroy() - self.signal.subs[self.id] = nil -end +function signal_handle:destroy() self.signal.subs[self.id] = nil end function signal.new() local new = {} new.subs = {} - new.idc = 0 + new.idc = 0 setmetatable(new, signal) return new end -function signal:catch(f, ...) - assert(f) - local handle = {signal=self, id=self.idc} - setmetatable(handle, signal_handle) - self.idc = self.idc + 1 +function signal:catch(f, ...) + assert(f) + local handle = { signal = self, id = self.idc } + setmetatable(handle, signal_handle) + self.idc = self.idc + 1 - self.subs[handle.id] = { f = f, opt = { ... } } - return handle + self.subs[handle.id] = { f = f, opt = { ... } } + return handle end function signal:emit(...) for id, sub in pairs(self.subs) do if #sub.opt ~= 0 then - - local args = { ... } - for n=#sub.opt,1,-1 do - table.insert(args, 1, sub.opt[n]) - end + local args = { ... } + for n = #sub.opt, 1, -1 do + table.insert(args, 1, sub.opt[n]) + end sub.f(unpack(args)) else diff --git a/common/socket.lua b/common/socket.lua index d11e17d..c366aa6 100644 --- a/common/socket.lua +++ b/common/socket.lua @@ -36,16 +36,16 @@ function socket.connect(address) end function socket.encode(t) - local p - if t.bin then - local bin = t.bin - t.bin = nil - p = json.encode(t) - p = p .. bin - else - p = json.encode(t) - end - return p + local p + if t.bin then + local bin = t.bin + t.bin = nil + p = json.encode(t) + p = p .. bin + else + p = json.encode(t) + end + return p end -- function socket:broadcast(data, channel, mode, dest) @@ -88,11 +88,9 @@ end --end -- function socket.decode(p) - local t, idx = json.decode(p) - if idx <= #p then - t.bin = string.sub(p,idx,#p) - end - return t + local t, idx = json.decode(p) + if idx <= #p then t.bin = string.sub(p, idx, #p) end + return t end function socket:peerdata(peer) return self.peerdatas[peer:index()] end diff --git a/common/tiles.lua b/common/tiles.lua index af7d037..88c5bbf 100644 --- a/common/tiles.lua +++ b/common/tiles.lua @@ -9,7 +9,7 @@ tiles.tiles = { bricks = { id = 5, tex = 3 }, leaves = { id = 6, tex = 16 }, log = { id = 7, tex = { 22, 22, 21, 21, 21, 21 } }, - slime = { id = 8, tex = 5, bounce=0.8 }, + slime = { id = 8, tex = 5, bounce = 0.8 }, } local id = {} diff --git a/common/world.lua b/common/world.lua index bc5067f..ea30126 100644 --- a/common/world.lua +++ b/common/world.lua @@ -11,7 +11,7 @@ function world.new() new.chunks = {} new.entities = {} - new.tagtables = {} + new.tagtables = {} new.ontilemodified = loex.signal.new() new.onentityinserted = loex.signal.new() @@ -31,61 +31,55 @@ function world:insert(e) end function world:tag(e, tag) - local tagtable = self.tagtables[tag] - if not tagtable then - tagtable = {} - self.tagtables[tag] = tagtable - end - tagtable[e.id] = e - - return tags + local tagtable = self.tagtables[tag] + if not tagtable then + tagtable = {} + self.tagtables[tag] = tagtable + end + tagtable[e.id] = e + + return tags end function world:untag(e, tag) - local tagtable = self.tagtables[tag] - if tagtable then - tagtable[e.id] = nil - end + local tagtable = self.tagtables[tag] + if tagtable then tagtable[e.id] = nil end end function world:tagged(e, tag) - local tagtable = self.tagtables[tag] - return tagtable and tagtable[e.id] + local tagtable = self.tagtables[tag] + return tagtable and tagtable[e.id] end function world:remove(e) assert(self.entities[e.id], "entity does not exist") - local e = self.entities[e.id] + local e = self.entities[e.id] self.entities[e.id] = nil - for _, tagtable in pairs(self.tagtables) do - tagtable[e.id] = nil - end + for _, tagtable in pairs(self.tagtables) do + tagtable[e.id] = nil + end self.onentityremoved:emit(e) end local function intersect(a, b) - local t = {} - if a == nil or b == nil then - return t - end - - for k, _ in pairs(a) do - if b[k] ~= nil then - t[k]=true - end - end - return t + local t = {} + if a == nil or b == nil then return t end + + for k, _ in pairs(a) do + if b[k] ~= nil then t[k] = true end + end + return t end function world:query(...) local tags = { ... } - if #tags == 0 then return self.entities end + if #tags == 0 then return self.entities end local query = self.tagtables[tags[1]] - for i=2,#tags do - query = intersect(query, self.tagtables[tags[i]]) + for i = 2, #tags do + query = intersect(query, self.tagtables[tags[i]]) end return query or {} end diff --git a/server/game.lua b/server/game.lua index ea91685..6be4523 100644 --- a/server/game.lua +++ b/server/game.lua @@ -8,13 +8,13 @@ local size = loex.chunk.size local game = {} function game.new() - local new = {} - setmetatable(new, { __index = game }) - return new + local new = {} + setmetatable(new, { __index = game }) + return new end function game:init(socket) - self.socket = socket + self.socket = socket self.world = loex.world.new() self.world.onentityinserted:catch(self.world_onentityinserted, self) @@ -22,35 +22,31 @@ function game:init(socket) self.genstate = gen.state.new(overworld.layers, 43242) - self.gravity = 42 + self.gravity = 42 - self.onupdate = loex.signal.new() - self.onshutdown = loex.signal.new() + self.onupdate = loex.signal.new() + self.onshutdown = loex.signal.new() - require("services.connection_manager").init(self) - require("services.player").init(self) - require("services.sync").init(self) - require("common.services.snowball").init(self) + require("services.connection_manager").init(self) + require("services.player").init(self) + require("services.sync").init(self) + require("common.services.snowball").init(self) end function game:update(dt) self.socket:service() - self.onupdate:emit(self, dt) + self.onupdate:emit(self, dt) end -function game:world_onentityinserted(e) - print(e.id .. " added") -end +function game:world_onentityinserted(e) print(e.id .. " added") end -function game:world_onentityremoved(e) - print(e.id .. " removed") -end +function game:world_onentityremoved(e) print(e.id .. " removed") end function game:shutdown() - print("shutting down...") - self.socket:disconnect() - self.onshutdown:emit(self) + print("shutting down...") + self.socket:disconnect() + self.onshutdown:emit(self) end return game diff --git a/server/main.lua b/server/main.lua index a8ff219..3a8514b 100644 --- a/server/main.lua +++ b/server/main.lua @@ -9,7 +9,7 @@ CHANNEL_CHUNKS = 1 CHANNEL_EVENTS = 3 CHANNEL_UPDATES = 4 -local game +local game function love.load(args) if #args < 1 then @@ -21,16 +21,10 @@ function love.load(args) print("starting server on port " .. tostring(port) .. "...") local socket = loex.socket.host(port, 64) - game = require("game").new() - game:init(socket) + game = require("game").new() + game:init(socket) end +function love.update(dt) game:update(dt) end -function love.update(dt) - game:update(dt) -end - -function love.quit() - game:shutdown() -end - +function love.quit() game:shutdown() end diff --git a/server/packets.lua b/server/packets.lua index ec9da83..4fc299f 100644 --- a/server/packets.lua +++ b/server/packets.lua @@ -1,45 +1,35 @@ local packets = {} local encode = loex.socket.encode -function packets.joinsuccess(id, x, y, z) - return encode { - type="joinsuccess", - x=x, - y=y, - z=z, - id=id - } +function packets.joinsuccess(id, x, y, z) + return encode { + type = "joinsuccess", + x = x, + y = y, + z = z, + id = id, + } end -function packets.joinfailure(cause) - return encode { - type="joinfailure", - cause=cause - } +function packets.joinfailure(cause) + return encode { + type = "joinfailure", + cause = cause, + } end -function packets.chunkadd(tiles, cx, cy, cz) - return encode { type="chunkadd", cx=cx, cy=cy, cz=cz, bin=tiles } -end +function packets.chunkadd(tiles, cx, cy, cz) return encode { type = "chunkadd", cx = cx, cy = cy, cz = cz, bin = tiles } end -function packets.chunkremove(cx, cy, cz) - return encode { type="chunkremove", cx=cx,cy=cy,cz=cz } -end +function packets.chunkremove(cx, cy, cz) return encode { type = "chunkremove", cx = cx, cy = cy, cz = cz } end -function packets.placed(x, y, z, t) - return encode { type="placed",x=x,y=y,z=z,t=t } -end +function packets.placed(x, y, z, t) return encode { type = "placed", x = x, y = y, z = z, t = t } end -function packets.broken(x, y, z) - return encode { type="broken", x=x,y=y,z=z} -end +function packets.broken(x, y, z) return encode { type = "broken", x = x, y = y, z = z } end -function packets.entityadd(id) - return encode {type="entityadd", id=id } -end +function packets.entityadd(id) return encode { type = "entityadd", id = id } end function packets.entityremove(id) -- TODO: get rid of this packet - return encode {type="entityremove", id=id} + return encode { type = "entityremove", id = id } end -- function packets.entitymove(id, x, y, z) @@ -47,11 +37,11 @@ end -- end function packets.entityremoteset(id, properties) - return encode { - type="entityremoteset", - id=id, - properties=properties, - } + return encode { + type = "entityremoteset", + id = id, + properties = properties, + } end return packets diff --git a/server/services/connection_manager.lua b/server/services/connection_manager.lua index 3039dd4..7382019 100644 --- a/server/services/connection_manager.lua +++ b/server/services/connection_manager.lua @@ -1,4 +1,4 @@ -local player = require("services.player") +local player = require("services.player") local packets = require("packets") local connection_manager = {} @@ -17,26 +17,24 @@ function connection_manager.verify_username(g, username) if g.taken_usernames[username] then return "Username already taken. Try again :)" end end -function connection_manager.init(g) - g.banlist = {} -- TODO: load from list - g.taken_usernames = {} +function connection_manager.init(g) + g.banlist = {} -- TODO: load from list + g.taken_usernames = {} - g.socket.onconnect:catch(connection_manager.socket_onconnect, g) - g.socket.ondisconnect:catch(connection_manager.socket_ondisconnect, g) - g.socket.onreceive:catch(connection_manager.socket_onreceive, g) + g.socket.onconnect:catch(connection_manager.socket_onconnect, g) + g.socket.ondisconnect:catch(connection_manager.socket_ondisconnect, g) + g.socket.onreceive:catch(connection_manager.socket_onreceive, g) end -function connection_manager.socket_onconnect(g, peer) - print("new connection!") -end +function connection_manager.socket_onconnect(g, peer) print("new connection!") end function connection_manager.socket_ondisconnect(g, peer) - local socket = g.socket + local socket = g.socket local peerdata = socket:peerdata(peer) if peerdata.playerentity then print(peerdata.playerentity.username .. " left the game :<") - g.world:tag(peerdata.playerentity, "destroyed") + g.world:tag(peerdata.playerentity, "destroyed") g.taken_usernames[peerdata.playerentity.username] = nil end end @@ -44,8 +42,8 @@ end function connection_manager.socket_onreceive(g, peer, packet) print("received " .. packet.type) - local socket = g.socket - local world = g.world + local socket = g.socket + local world = g.world local peerdata = socket:peerdata(peer) @@ -58,7 +56,7 @@ function connection_manager.socket_onreceive(g, peer, packet) return end - -- spawn player + -- spawn player local p = player.entity(g, world, lume.uuid(), 0, 0, 180, packet.username, peer) peer:send(packets.joinsuccess(p.id, p.x, p.y, p.z), CHANNEL_ONE) diff --git a/server/services/player.lua b/server/services/player.lua index 454e276..73d0bcf 100644 --- a/server/services/player.lua +++ b/server/services/player.lua @@ -10,60 +10,60 @@ local floor = math.floor local player = {} function player.init(g) - assert(g.master == nil) - g.onupdate:catch(player.update) - g.socket.onreceive:catch(player.socket_onreceive, g) + assert(g.master == nil) + g.onupdate:catch(player.update) + g.socket.onreceive:catch(player.socket_onreceive, g) g.world.ontilemodified:catch(player.world_ontilemodified, g) g.world.onentityremoved:catch(player.world_onentityremoved, g) end -function player.view_onchunkinserted(g, p, c) - p.master:send(packets.chunkadd(c:dump(), c.x, c.y, c.z)) -end - -function player.view_onchunkremoved(g, p, c) - p.master:send(packets.chunkremove(c.x, c.y, c.z)) -end - -function player.view_onentityinserted(g, p, e) - -- TODO: need abstraction for this - if g.world:tagged(e, "player") then - p.master:send(socket.encode { - type="entityadd", - id=e.id, - x=e.x,y=e.y,z=e.z, - username=e.username, - entity_type="player" - }) - elseif g.world:tagged(e, "snowball") then - p.master:send(socket.encode { - type="entityadd", - id=e.id, - x=e.x,y=e.y,z=e.z, - vx=e.vx,vy=e.vy,vz=e.vz, - entity_type="snowball", - }) - end +function player.view_onchunkinserted(g, p, c) p.master:send(packets.chunkadd(c:dump(), c.x, c.y, c.z)) end + +function player.view_onchunkremoved(g, p, c) p.master:send(packets.chunkremove(c.x, c.y, c.z)) end + +function player.view_onentityinserted(g, p, e) + -- TODO: need abstraction for this + if g.world:tagged(e, "player") then + p.master:send(socket.encode { + type = "entityadd", + id = e.id, + x = e.x, + y = e.y, + z = e.z, + username = e.username, + entity_type = "player", + }) + elseif g.world:tagged(e, "snowball") then + p.master:send(socket.encode { + type = "entityadd", + id = e.id, + x = e.x, + y = e.y, + z = e.z, + vx = e.vx, + vy = e.vy, + vz = e.vz, + entity_type = "snowball", + }) + end end -function player.view_onentityremoved(g, p, e) - p.master:send(packets.entityremove(e.id)) -end +function player.view_onentityremoved(g, p, e) p.master:send(packets.entityremove(e.id)) end function player.entity(g, w, id, x, y, z, username, master) - local e = {id=id} - - e.last_throw = os.time() - e.x, e.y, e.z = x, y, z - e.vx, e.vy, e.vz = 0, 0, 0 - e.box = { - x = 0, - y = 0, - z = 0, - w = 0.3, - d = 0.3, - h = 0.9, - } + local e = { id = id } + + e.last_throw = os.time() + e.x, e.y, e.z = x, y, z + e.vx, e.vy, e.vz = 0, 0, 0 + e.box = { + x = 0, + y = 0, + z = 0, + w = 0.3, + d = 0.3, + h = 0.9, + } e.username = username e.master = master @@ -73,10 +73,10 @@ function player.entity(g, w, id, x, y, z, username, master) e.view.onentityinserted:catch(player.view_onentityinserted, g, e) e.view.onentityremoved:catch(player.view_onentityremoved, g, e) - e.remote = {} + e.remote = {} - w:insert(e) - w:tag(e, "box") + w:insert(e) + w:tag(e, "box") w:tag(e, "player") return e @@ -90,11 +90,11 @@ function player.inview(e, x, y, z) end function player.socket_onreceive(g, peer, d) - if d.type == "join" then return end -- FIXME - local p = g.socket:peerdata(peer).playerentity - if not p then return end + if d.type == "join" then return end -- FIXME + local p = g.socket:peerdata(peer).playerentity + if not p then return end - local world = g.world + local world = g.world local handles = { ["move"] = function(p, d) @@ -111,29 +111,29 @@ function player.socket_onreceive(g, peer, d) local x, y, z = tonumber(d.x), tonumber(d.y), tonumber(d.z) world:tile(x, y, z, loex.tiles.air.id) end, - ["snowball_throw"] = function(p, d) - -- throw snowballs - --if os.time() - p.last_throw > 0.05 then - print("thrown snowball") - p.last_throw = os.time() - - local e = snowball.entity(g, lume.uuid(), d.x, d.y, d.z, d.vx, d.vy, d.vz) - e.remote = {} - --end - end + ["snowball_throw"] = function(p, d) + -- throw snowballs + --if os.time() - p.last_throw > 0.05 then + print("thrown snowball") + p.last_throw = os.time() + + local e = snowball.entity(g, lume.uuid(), d.x, d.y, d.z, d.vx, d.vy, d.vz) + e.remote = {} + --end + end, } handles[d.type](p, d) end function player.world_onentityremoved(g, e) - local world = g.world + local world = g.world for _, p in pairs(world:query("player")) do if p.view:entity(e) then p.view:remove(e) end end end function player.world_ontilemodified(g, x, y, z, t) - local world = g.world + local world = g.world local packet if t == loex.tiles.air.id then packet = packets.broken(x, y, z) @@ -142,66 +142,64 @@ function player.world_ontilemodified(g, x, y, z, t) end for _, p in pairs(world:query("player")) do - if p.view:tile(x, y, z) >= 0 then - p.master:send(packet) - end + if p.view:tile(x, y, z) >= 0 then p.master:send(packet) end end end function player.update(g, dt) - local world = g.world - local genstate = g.genstate + local world = g.world + local genstate = g.genstate local gendistance = 5 - for _, p in pairs(g.world:query("player")) do - - -- generate world - for i = -gendistance + floor(p.x / size), gendistance + floor(p.x / size) do - for j = -gendistance + floor(p.y / size), gendistance + floor(p.y / size) do - for k = 0, overworld.columnheight - 1 do - if not world:chunk(loex.hash.spatial(i, j, k)) then - local c = overworld:generate(genstate, i, j, k) - world:insertchunk(c) - end - end - end - end - - - -- compute chunks in player view - for _, c in pairs(world.chunks) do - if not player.inview(p, c.x * size, c.y * size, c.z * size) then - if p.view.chunks[c.hash] then p.view:removechunk(c.hash) end - else - if not p.view.chunks[c.hash] then p.view:insertchunk(c) end - end - end - - -- compute entities in player view - for _, e in pairs(world.entities) do - if e ~= p then - if not player.inview(p, e.x, e.y, e.z) then - if p.view:entity(e.id) then p.view:remove(e) end - else - if not p.view:entity(e.id) then - p.view:insert(e) - else - -- TODO: this is bad, really bad - if not (e.remote.x == e.x and e.remote.y == e.y and e.remote.z == e.z) then - p.master:send(socket.encode { - type="entityremoteset", - id=e.id, - properties={x=e.x,y=e.y,z=e.z}, - }) - end - end - end - end - end - end - for _, e in pairs(world.entities) do - if e.remote then e.remote.x, e.remote.y, e.remote.z = e.x, e.y, e.z end - end + for _, p in pairs(g.world:query("player")) do + -- generate world + for i = -gendistance + floor(p.x / size), gendistance + floor(p.x / size) do + for j = -gendistance + floor(p.y / size), gendistance + floor(p.y / size) do + for k = 0, overworld.columnheight - 1 do + if not world:chunk(loex.hash.spatial(i, j, k)) then + local c = overworld:generate(genstate, i, j, k) + world:insertchunk(c) + end + end + end + end + + -- compute chunks in player view + for _, c in pairs(world.chunks) do + if not player.inview(p, c.x * size, c.y * size, c.z * size) then + if p.view.chunks[c.hash] then p.view:removechunk(c.hash) end + else + if not p.view.chunks[c.hash] then p.view:insertchunk(c) end + end + end + + -- compute entities in player view + for _, e in pairs(world.entities) do + if e ~= p then + if not player.inview(p, e.x, e.y, e.z) then + if p.view:entity(e.id) then p.view:remove(e) end + else + if not p.view:entity(e.id) then + p.view:insert(e) + else + -- TODO: this is bad, really bad + if not (e.remote.x == e.x and e.remote.y == e.y and e.remote.z == e.z) then + p.master:send(socket.encode { + type = "entityremoteset", + id = e.id, + properties = { x = e.x, y = e.y, z = e.z }, + }) + end + end + end + end + end + end + for _, e in pairs(world.entities) do + if e.remote then + e.remote.x, e.remote.y, e.remote.z = e.x, e.y, e.z + end + end end return player diff --git a/server/services/sync.lua b/server/services/sync.lua index 2d3dec7..fa09986 100644 --- a/server/services/sync.lua +++ b/server/services/sync.lua @@ -1,46 +1,41 @@ local sync = {} -local packets = require "packets" +local packets = require("packets") -function sync.init(g) - g.onupdate:catch(sync.update) -end +function sync.init(g) g.onupdate:catch(sync.update) end function sync.update(g, dt) - local world = g.world - local socket = g.socket + local world = g.world + local socket = g.socket - local genstate = g.genstate + local genstate = g.genstate for _, e in pairs(world.entities) do - --- -- sync edits --- local p_edits, p_edits_count = e:property_edits() --- local t_edits, t_edits_count = e:tag_edits() --- --- if p_edits_count > 0 or t_edits_count > 0 then --- local packet = packets.entityremoteset(e.id, p_edits, t_edits) --- e:clear_tag_edits() --- e:clear_property_edits() --- --- for _, p in ipairs(world:query("player")) do --- if p.view:entity(e.id) then p.master:send(packet) end --- end --- end - - -- sync destroys - if g.world:tagged(e, "destroyed") then - for _, p in pairs(world:query("player")) do - if p.view:entity(e.id) then - p.view:remove(e) - end - end - end + -- -- sync edits + -- local p_edits, p_edits_count = e:property_edits() + -- local t_edits, t_edits_count = e:tag_edits() + -- + -- if p_edits_count > 0 or t_edits_count > 0 then + -- local packet = packets.entityremoteset(e.id, p_edits, t_edits) + -- e:clear_tag_edits() + -- e:clear_property_edits() + -- + -- for _, p in ipairs(world:query("player")) do + -- if p.view:entity(e.id) then p.master:send(packet) end + -- end + -- end + + -- sync destroys + if g.world:tagged(e, "destroyed") then + for _, p in pairs(world:query("player")) do + if p.view:entity(e.id) then p.view:remove(e) end + end + end end - -- remove destroyed entities - for _, e in pairs(world:query("destroyed")) do - world:remove(e) - end + -- remove destroyed entities + for _, e in pairs(world:query("destroyed")) do + world:remove(e) + end end return sync