From 9466d7692a41abda47daf8af17c4ed7096a67f20 Mon Sep 17 00:00:00 2001 From: Auri Date: Sun, 6 Dec 2020 23:34:25 -0800 Subject: [PATCH] Serialization API, Server -> Clients messaging. - Added zepha.serialize & zepha.deserialize. Said methods are automatically used when message-passing. - Revised dump() to have better formatting and configurable spacing :) --- assets/base/script/init.lua | 1 + assets/base/script/modules/dump.lua | 79 +++++++-------- assets/base/script/modules/serialization.lua | 99 +++++++++++++++++++ src/client/conn/ClientNetworkInterpreter.cpp | 27 +++-- src/client/conn/ClientNetworkInterpreter.h | 5 - src/lua/modules/Message.cpp | 4 +- src/lua/modules/Message.h | 2 +- src/server/Server.cpp | 2 +- src/world/LocalWorld.cpp | 5 + src/world/LocalWorld.h | 2 + src/world/ServerWorld.cpp | 4 +- subgames/zeus/mods/auri_health/script/api.lua | 7 ++ .../zeus/mods/auri_health/script/core.lua | 11 +++ .../zeus/mods/auri_health/script/init.lua | 5 +- .../zeus_default/script/entity/rabbit.lua | 20 +++- 15 files changed, 208 insertions(+), 65 deletions(-) create mode 100644 assets/base/script/modules/serialization.lua create mode 100644 subgames/zeus/mods/auri_health/script/api.lua create mode 100644 subgames/zeus/mods/auri_health/script/core.lua diff --git a/assets/base/script/init.lua b/assets/base/script/init.lua index 1473c332..0073e313 100644 --- a/assets/base/script/init.lua +++ b/assets/base/script/init.lua @@ -9,6 +9,7 @@ runfile(_PATH .. "modules/after") runfile(_PATH .. "modules/vector") runfile(_PATH .. "modules/entity") runfile(_PATH .. "modules/callbacks") +runfile(_PATH .. "modules/serialization") -- Register base models (if not on main menu) if zepha.client or zepha.server then runfile(_PATH .. "game/init") end \ No newline at end of file diff --git a/assets/base/script/modules/dump.lua b/assets/base/script/modules/dump.lua index d4cbdf0a..56908d0a 100644 --- a/assets/base/script/modules/dump.lua +++ b/assets/base/script/modules/dump.lua @@ -1,56 +1,47 @@ -- Zepha Dump Function -- Version 1.0 --- dumpTbl [private] --- Recursive function to print the contents of a table. -local function dumpTbl(tbl, indent, scannedTables) - if not scannedTables then scannedTables = {} end - if not indent then indent = 0 end +-- +-- Pretty-prints a lua value, properly displaying tables +-- with indentation, table collapsing, and more. +-- +-- @param {any} val - The lua value to print. +-- @param {number | nil | false} nl - Controls the table expansion behavior. If left unspecified, +-- tables will expand if they contain more than 3 keys. If set to a number n, +-- tables will expand if they contain more than n keys, if set to false, tables will never expand. +-- @param {number | nil} idn - Internal value, do not assign manually. +-- - scannedTables[tbl] = true +_G["format"] = function(val, nl, idn) + idn = idn or 0 + if nl == nil then nl = 3 end - for k, v in pairs(tbl) do - if type(k) == "number" then - k = "[" .. k .. "]" + if type(val) == "function" or type(val) == "userdata" or type(val) == "thread" then return tostring(val) end + if type(val) == "string" then return '"' .. val .. '"' end + if type(val) == "nil" then return 'nil' end + if type(val) == "table" then + local strings = {} + + local num_keys = 0 + for _ in pairs(val) do num_keys = num_keys + 1 end + + local use_nl = nl == true and true or (type(nl) == 'number' and num_keys >= nl) + local new_idn = use_nl and idn + 1 or idn + local delim = use_nl and '\n' or ' ' + + for k, v in pairs(val) do + table.insert(strings, format(k, nl, new_idn) .. ' = ' .. format(v, nl, new_idn)) end - local indentString = string.rep(" ", indent) - local formatting = indentString .. k .. " = " + local outer_idn = '' + if use_nl and idn then for i = 1, idn do outer_idn = outer_idn .. '\t' end end + local inner_idn = use_nl and outer_idn .. '\t' or '' - if type(v) == "function" then - v = "" - elseif type(v) == "userdata" then - v = "" - elseif type(v) == "string" then - v = '"' .. v .. '"' - elseif type(v) == "boolean" then - v = tostring(v) - end - - if type(v) == "table" then - if scannedTables[v] ~= nil then - print(formatting .. "") - else - print(formatting .. "{") - dumpTbl(v, indent + 1, scannedTables) - print(indentString .. "}") - end - else - print(formatting .. v) - end + return '{' .. delim .. inner_idn .. table.concat(strings, ',' .. delim .. inner_idn) .. delim .. outer_idn .. '}' end + return val end --- dump --- Prints a human readable form of a value. -_G["dump"] = function(val) - if type(val) == "table" then - print("{") - dumpTbl(val, 1) - print("}") - return - end - if type(val) == "string" then print('"'..val..'"') return end - print(val) - return +_G["dump"] = function(val, nl) + print(format(val, nl)) end \ No newline at end of file diff --git a/assets/base/script/modules/serialization.lua b/assets/base/script/modules/serialization.lua new file mode 100644 index 00000000..e92f1be4 --- /dev/null +++ b/assets/base/script/modules/serialization.lua @@ -0,0 +1,99 @@ +zepha.serialize = function(data) + local data_type = type(data) + + if data_type == 'table' then + local values = {} + for k, v in pairs(data) do + table.insert(values, zepha.serialize(k) .. ' = ' .. zepha.serialize(v)) + end + return '{ ' .. table.concat(values, ', ') .. ' }' + end + + if data_type == 'number' then return tostring(data) end + if data_type == 'string' then return '"' .. data:gsub('"', '\"') .. '"' end + if data_type == 'boolean' then return data and 'true' or 'false' end + + if data_type ~= 'nil' then print('[ERR] Invalid serialization type: ' .. data_type) end + return 'nil' +end + +local function trim(str) + return str:match('^%s*(.-)%s*$') +end + +local function split(input, sep, op) + if sep == nil then sep = "%s" end + local t = {} + for str in string.gmatch(input, '([^'..sep..']+)') do + table.insert(t, op and op(str) or str) end + return t +end + +local function find_match(str, a, b, off) + local pattern = '[' .. a .. b .. ']' + if not off then off = 1 end + local nest = 0 + local found = str:find(pattern, off) + while found do + if str:sub(found, found) == a then nest = nest + 1 + else nest = nest - 1 end + off = found + 1 + if nest == 0 then return found + elseif nest < 0 then return nil end + found = str:find(pattern, off) + end +end + +zepha.deserialize = function(str) + str = trim(str) + + if str:sub(1, 1) == '{' then + local tbl = {} + str = trim(str:sub(2, #str - 1)) + + while #str > 0 do + local sep, key, val + + if str:sub(1, 1) == ',' then + str = trim(str:sub(2)) + end + + if str:sub(1, 1) == '{' then + -- Handling a table key + local e = find_match(str, '{', '}') + local tbl_key = str:sub(1, e) + key = zepha.deserialize(tbl_key) + str = trim(str:sub(str:find('=', e + 1) + 1)) + else + -- Handling a normal key + local end_ind = str:find('=') + key = zepha.deserialize(str:sub(1, end_ind - 1)) + str = trim(str:sub(end_ind + 1)) + end + + if str:sub(1, 1) == '{' then + -- Handling a table value + local e = find_match(str, '{', '}') + local tbl_val = str:sub(1, e) + val = zepha.deserialize(tbl_val) + str = trim(str:sub(e + 1)) + else + -- Handling a normal value + local end_ind = str:find(',') + val = zepha.deserialize(str:sub(1, (end_ind or #str + 1) - 1)) + str = trim(str:sub(end_ind or #str + 1)) + end + + tbl[key] = val + end + + return tbl + end + + if str:find('^"') then return str:sub(2, #str - 1) end + if str:find('^true') then return true end + if str:find('^false') then return false end + if str:find('^nil') then return nil end + + return tonumber(str) +end \ No newline at end of file diff --git a/src/client/conn/ClientNetworkInterpreter.cpp b/src/client/conn/ClientNetworkInterpreter.cpp index 559db4a3..48e711ba 100644 --- a/src/client/conn/ClientNetworkInterpreter.cpp +++ b/src/client/conn/ClientNetworkInterpreter.cpp @@ -66,17 +66,21 @@ void ClientNetworkInterpreter::receivedPacket(std::unique_ptr p) { << ". Is the server on a different protocol version?" << Log::endl; break; - case Packet::Type::SERVER_INFO:serverSideChunkGens = p->d.read(); + case Packet::Type::SERVER_INFO: + serverSideChunkGens = p->d.read(); break; - case Packet::Type::THIS_PLAYER_INFO:world.getPlayer()->handleAssertion(p->d); + case Packet::Type::THIS_PLAYER_INFO: + world.getPlayer()->handleAssertion(p->d); break; - case Packet::Type::PLAYER_ENT_INFO:world.handlePlayerEntPacket(std::move(p)); + case Packet::Type::PLAYER_ENT_INFO: + world.handlePlayerEntPacket(std::move(p)); break; case Packet::Type::CHUNK: - case Packet::Type::MAPBLOCK:world.handleWorldPacket(std::move(p)); + case Packet::Type::MAPBLOCK: + world.handleWorldPacket(std::move(p)); break; case Packet::Type::BLOCK_SET: { @@ -86,13 +90,16 @@ void ClientNetworkInterpreter::receivedPacket(std::unique_ptr p) { break; } - case Packet::Type::ENTITY_INFO:world.getActiveDimension().l()->serverEntitiesInfo(p->d); + case Packet::Type::ENTITY_INFO: + world.getActiveDimension().l()->serverEntitiesInfo(p->d); break; - case Packet::Type::ENTITY_REMOVED:world.getActiveDimension().l()->serverEntitiesRemoved(p->d); + case Packet::Type::ENTITY_REMOVED: + world.getActiveDimension().l()->serverEntitiesRemoved(p->d); break; - case Packet::Type::INV_DATA:onInvPacket(std::move(p)); + case Packet::Type::INV_DATA: + onInvPacket(std::move(p)); break; case Packet::Type::INV_INVALID: { @@ -100,6 +107,12 @@ void ClientNetworkInterpreter::receivedPacket(std::unique_ptr p) { std::string list = p->d.read(); throw std::runtime_error("Invalid inventory " + source + ":" + list + " was request by client."); } + case Packet::Type::MOD_MESSAGE: { + std::string channel = p->d.read(); + std::string message = p->d.read(); + world.handleModMessage(channel, message); + break; + } } } diff --git a/src/client/conn/ClientNetworkInterpreter.h b/src/client/conn/ClientNetworkInterpreter.h index 715e0cb5..afcb4c84 100644 --- a/src/client/conn/ClientNetworkInterpreter.h +++ b/src/client/conn/ClientNetworkInterpreter.h @@ -13,15 +13,10 @@ #include "client/conn/ServerConnection.h" class Model; - class Target; - class LocalWorld; - class PacketView; - class LocalPlayer; - class LocalSubgame; enum class NetPlayerField; diff --git a/src/lua/modules/Message.cpp b/src/lua/modules/Message.cpp index 458be0aa..62da6cc0 100644 --- a/src/lua/modules/Message.cpp +++ b/src/lua/modules/Message.cpp @@ -7,6 +7,6 @@ void Api::Module::Message::bind() { core.set_function("send_message", Util::bind_this(this, &Message::send_message)); } -void Api::Module::Message::send_message(const std::string& channel, const std::string& message) { - world.sendMessage(channel, message); +void Api::Module::Message::send_message(const std::string& channel, sol::object message) { + world.sendMessage(channel, core["serialize"](message)); } diff --git a/src/lua/modules/Message.h b/src/lua/modules/Message.h index 059ee3d8..5ef4ba45 100644 --- a/src/lua/modules/Message.h +++ b/src/lua/modules/Message.h @@ -12,7 +12,7 @@ namespace Api::Module { void bind() override; protected: - void send_message(const std::string& channel, const std::string& message); + void send_message(const std::string& channel, sol::object message); }; } diff --git a/src/server/Server.cpp b/src/server/Server.cpp index 81e5b5f8..fa469a75 100644 --- a/src/server/Server.cpp +++ b/src/server/Server.cpp @@ -162,7 +162,7 @@ void Server::playerPacketReceived(PacketView& p, PlayerPtr player) { case Packet::Type::MOD_MESSAGE: p.d.read(source).read(list); game->getParser().safe_function(game->getParser().core["trigger"], "message", - source, list, Api::Usertype::ServerPlayer(player)); + source, game->getParser().safe_function(game->getParser().core["deserialize"], list)); break; } } diff --git a/src/world/LocalWorld.cpp b/src/world/LocalWorld.cpp index ec34e47e..21d51290 100644 --- a/src/world/LocalWorld.cpp +++ b/src/world/LocalWorld.cpp @@ -71,6 +71,11 @@ void LocalWorld::handlePlayerEntPacket(std::unique_ptr p) { // getActiveDimension().playerEntities.emplace_back(p->d.read(), id, playerModel); } +void LocalWorld::handleModMessage(const std::string& channel, const std::string& message) { + game->getParser().safe_function(game->getParser().core["trigger"], "message", + channel, game->getParser().safe_function(game->getParser().core["deserialize"], message)); +} + void LocalWorld::commitChunk(std::shared_ptr c) { activeDimension->setChunk(std::move(c)); } diff --git a/src/world/LocalWorld.h b/src/world/LocalWorld.h index 97a8c565..4fe0f34a 100644 --- a/src/world/LocalWorld.h +++ b/src/world/LocalWorld.h @@ -35,6 +35,8 @@ public: void handlePlayerEntPacket(std::unique_ptr p); + void handleModMessage(const std::string& channel, const std::string& message); + void commitChunk(std::shared_ptr chunk); virtual void sendMessage(const std::string& channel, const std::string& message) override; diff --git a/src/world/ServerWorld.cpp b/src/world/ServerWorld.cpp index 1d881c3f..d4f2be00 100644 --- a/src/world/ServerWorld.cpp +++ b/src/world/ServerWorld.cpp @@ -240,5 +240,7 @@ void ServerWorld::sendChunksToPlayer(ServerPlayer& client) { } void ServerWorld::sendMessage(const std::string& channel, const std::string& message) { - std::cout << channel << ":" << message << std::endl; + auto p = Serializer().append(channel).append(message).packet(Packet::Type::MOD_MESSAGE); + for (auto& player : clients.players) + p.sendTo(player->getPeer(), Packet::Channel::ENTITY); } diff --git a/subgames/zeus/mods/auri_health/script/api.lua b/subgames/zeus/mods/auri_health/script/api.lua new file mode 100644 index 00000000..468e2e95 --- /dev/null +++ b/subgames/zeus/mods/auri_health/script/api.lua @@ -0,0 +1,7 @@ +health.damage_player = function(player, damage) + local health = health.health_values[player.id] + health.health = health.health - damage + health.buffer = damage + + zepha.send_message("@auri:health:damage", health) +end \ No newline at end of file diff --git a/subgames/zeus/mods/auri_health/script/core.lua b/subgames/zeus/mods/auri_health/script/core.lua new file mode 100644 index 00000000..f40565ec --- /dev/null +++ b/subgames/zeus/mods/auri_health/script/core.lua @@ -0,0 +1,11 @@ +health.health_values = {} + +if zepha.server then + zepha.bind('player_join', function(player) + health.health_values[player.id] = { health = 20, buffer = 0 } + end) +else + zepha.bind('message', function(channel, message) + dump(message) + end) +end \ No newline at end of file diff --git a/subgames/zeus/mods/auri_health/script/init.lua b/subgames/zeus/mods/auri_health/script/init.lua index f9b89d34..a5d719c8 100644 --- a/subgames/zeus/mods/auri_health/script/init.lua +++ b/subgames/zeus/mods/auri_health/script/init.lua @@ -1,7 +1,10 @@ local api = {} _G['health'] = api +runfile(_PATH .. 'api') +runfile(_PATH .. 'core') + if zepha.client then - runfile(_PATH .. "interface") + runfile(_PATH .. 'interface') api.default_render(true) end \ No newline at end of file diff --git a/subgames/zeus/mods/zeus_default/script/entity/rabbit.lua b/subgames/zeus/mods/zeus_default/script/entity/rabbit.lua index a430aa24..9e272afc 100644 --- a/subgames/zeus/mods/zeus_default/script/entity/rabbit.lua +++ b/subgames/zeus/mods/zeus_default/script/entity/rabbit.lua @@ -1,4 +1,4 @@ -local friendly = false +local ATTACK_INTERVAL = 1 zepha.register_entity("zeus:default:rabbit", { display = "model", @@ -21,8 +21,14 @@ zepha.register_entity("zeus:default:rabbit", { self.object.anims:set_anim("run") self.object.anims:play() + + self.attack_timer = 0 end, on_update = function(self, delta) + if self.attack_timer > 0 then + self.attack_timer = math.max(self.attack_timer - delta, 0) + end + local closest_player = nil for _, player in pairs(zepha.players) do if not closest_player or vector.dist(self.object.pos, player.pos) @@ -32,9 +38,13 @@ zepha.register_entity("zeus:default:rabbit", { end if not closest_player then return end + local offset = closest_player.pos - self.object.pos local dist = offset:length() - if dist > 16 then return end + if dist > 16 then + self.attack_timer = ATTACK_INTERVAL + return + end if dist > 0.5 then local dir = offset:unit() @@ -54,7 +64,11 @@ zepha.register_entity("zeus:default:rabbit", { 3 * math.cos(math.rad(self.object.yaw + 90)) } else - self.object.vel = V{} + self.object.vel = V {} + if self.attack_timer == 0 then + health.damage_player(closest_player, 1) + self.attack_timer = ATTACK_INTERVAL + end end end }) \ No newline at end of file