Working on entity Lua API.

This commit is contained in:
Quentin Bazin 2020-05-17 02:21:54 +02:00
parent 675bdb112f
commit 5e6103d00d
13 changed files with 342 additions and 19 deletions

View File

@ -18,11 +18,11 @@ Adds a listener to a specific type of event.
Example:
```lua
openminer:add_listener(EventType.OnBlockPlaced, function(pos, player, world, client, server)
openminer:add_listener(EventType.OnBlockPlaced, function(pos, block, player, world, client, server)
server:send_chat_message(0, "Block placed at " .. pos.x .. ";" .. pos.y .. ";" .. pos.z .. " by Client" .. player:client_id(), client);
end)
openminer:add_listener(EventType.OnBlockDigged, function(pos, player, world, client, server)
openminer:add_listener(EventType.OnBlockDigged, function(pos, block, player, world, client, server)
server:send_chat_message(0, "Block digged at " .. pos.x .. ";" .. pos.y .. ";" .. pos.z .. " by Client" .. player:client_id(), client);
end)
@ -35,7 +35,7 @@ end)
Possible events:
- `OnBlockPlaced`: `funcion(pos, player, world, client, server)`
- `OnBlockDigged`: `funcion(pos, player, world, client, server)`
- `OnBlockPlaced`: `funcion(pos, block, player, world, client, server)`
- `OnBlockDigged`: `funcion(pos, block, player, world, client, server)`
- `OnBlockActivated`: `function(pos, block, player, world, client, server)`

View File

@ -34,11 +34,11 @@ dofile("trees.lua")
dofile("biomes.lua")
dofile("dimensions.lua")
-- openminer:add_listener(EventType.OnBlockPlaced, function(pos, player, world, client, server)
-- openminer:add_listener(EventType.OnBlockPlaced, function(pos, block, player, world, client, server)
-- server:send_chat_message(0, "Block placed at " .. pos.x .. ";" .. pos.y .. ";" .. pos.z .. " by Client" .. player:client_id(), client);
-- end)
-- openminer:add_listener(EventType.OnBlockDigged, function(pos, player, world, client, server)
-- openminer:add_listener(EventType.OnBlockDigged, function(pos, block, player, world, client, server)
-- server:send_chat_message(0, "Block digged at " .. pos.x .. ";" .. pos.y .. ";" .. pos.z .. " by Client" .. player:client_id(), client);
-- end)
@ -141,3 +141,49 @@ function show_inventory(client, screen_width, screen_height, gui_scale)
gui:show(client)
end
mod:entity {
id = "item_drop",
properties = {
visual = {
type = "inventorycube",
size = 0.25,
origin = {0.125, 0.125, 0.125},
},
is_rotatable = true,
animation = {
{
type = "rotation",
axis = {0, 0, 1},
angle = 0.5
},
{
type = "translation",
delta = {0, 0, -0.0005},
min = -0.2,
max = 0,
loop = true
}
},
hitbox = {0, 0, 0, 0.25, 0.25, 0.25},
},
on_collision = function(entity, other)
if other:type() == "player" then
other:inventory():add_item(entity:properties():itemstack())
end
end,
}
openminer:add_listener(EventType.OnBlockDigged, function(pos, block, player, world, client, server)
mod:spawn_entity("item_drop", {
position = {pos.x, pos.y, pos.z},
dimension = world:dimension():id(),
itemstack = {block:string_id(), 1}
})
end)

View File

@ -117,6 +117,19 @@ Dimension &Registry::registerSerializedDimension(sf::Packet &packet) {
return m_dimensions.back();
}
entt::entity Registry::registerEntity(const std::string &stringID) {
auto it = m_entities.find(stringID);
if (it == m_entities.end()) {
entt::entity entity = m_entityRegistry.create();
m_entities.emplace(stringID, entity);
return entity;
}
else
gkError() << "Redefinition of entity '" + stringID + "', keeping the first one";
return static_cast<entt::entity>(0);
}
const Block &Registry::getBlockFromStringID(const std::string &stringID) {
if (stringID.empty()) return getBlock(0);
auto it = m_blocksID.find(stringID);
@ -166,6 +179,21 @@ const Biome &Registry::getBiomeFromStringID(const std::string &stringID) {
return getBiome(it->second);
}
entt::entity Registry::getEntityFromStringID(const std::string &stringID) {
if (stringID.empty()) {
gkError() << "Failed to get entity from empty string ID";
return (entt::entity)0;
}
auto it = m_entities.find(stringID);
if (it == m_entities.end()) {
gkError() << "Failed to get entity '" + stringID + "': Not found";
return (entt::entity)0;
}
return it->second;
}
const Recipe *Registry::getRecipe(const Inventory &inventory) const {
for (auto &recipe : m_recipes) {
if (recipe->isMatching(inventory))

View File

@ -31,6 +31,8 @@
#include <unordered_map>
#include <vector>
#include <entt/entt.hpp>
#include "Biome.hpp"
#include "Block.hpp"
#include "Dimension.hpp"
@ -82,6 +84,9 @@ class Registry : public ISerializable {
Dimension &registerDimension(const std::string &stringID, const std::string &label);
Dimension &registerSerializedDimension(sf::Packet &packet);
entt::registry &entityRegistry() { return m_entityRegistry; }
entt::entity registerEntity(const std::string &stringID);
const Block &getBlock(u16 id) const { return *m_blocks.at(id).get(); }
const Item &getItem(u16 id) const { return m_items.at(id); }
const Sky &getSky(u16 id) const { return m_skies.at(id); }
@ -94,6 +99,7 @@ class Registry : public ISerializable {
const Sky &getSkyFromStringID(const std::string &stringID);
const Tree &getTreeFromStringID(const std::string &stringID);
const Biome &getBiomeFromStringID(const std::string &stringID);
entt::entity getEntityFromStringID(const std::string &stringID);
const Recipe *getRecipe(const Inventory &inventory) const;
@ -127,6 +133,10 @@ class Registry : public ISerializable {
std::unordered_map<std::string, u16> m_biomesID;
std::unordered_map<std::string, u16> m_dimensionsID;
std::unordered_map<std::string, entt::entity> m_entities;
entt::registry m_entityRegistry;
enum class DataType {
Block,
Item,

View File

@ -44,6 +44,7 @@ class DrawableDef : public ISerializable {
InventoryCubeDef &addInventoryCube();
// FIXME
InventoryCubeDef &getInventoryCubeDef() { return m_cubes.back(); }
const InventoryCubeDef &getInventoryCubeDef() const { return m_cubes.back(); }
void serialize(sf::Packet &packet) const override;

View File

@ -65,9 +65,6 @@ class ServerApplication {
gk::GameClock m_clock;
gk::EventHandler *m_eventHandler = nullptr;
ScriptEngine m_scriptEngine;
ServerModLoader m_modLoader{m_scriptEngine};
Registry m_registry;
u16 m_port = 4242;
@ -75,6 +72,9 @@ class ServerApplication {
WorldController m_worldController{m_registry, m_clock};
PlayerList m_players;
ScriptEngine m_scriptEngine;
ServerModLoader m_modLoader{m_scriptEngine, m_registry, m_worldController};
Server m_server;
ServerCommandHandler m_serverCommandHandler{m_scriptEngine, m_server, m_worldController, m_players, m_registry};
};

View File

@ -43,6 +43,7 @@ void LuaMod::commit() {
case DefinitionType::Tree: m_biomeLoader.loadTree(it.second); break;
case DefinitionType::Biome: m_biomeLoader.loadBiome(it.second); break;
case DefinitionType::Dimension: m_dimensionLoader.loadDimension(it.second); break;
case DefinitionType::Entity: m_entityLoader.loadEntity(it.second); break;
default: break;
}
@ -62,7 +63,8 @@ void LuaMod::initUsertype(sol::state &lua) {
"sky", &LuaMod::registerSky,
"tree", &LuaMod::registerTree,
"biome", &LuaMod::registerBiome,
"dimension", &LuaMod::registerDimension
"dimension", &LuaMod::registerDimension,
"entity", &LuaMod::registerEntity
);
}
@ -98,3 +100,11 @@ void LuaMod::registerDimension(const sol::table &table) {
m_defs.emplace(DefinitionType::Dimension, table);
}
void LuaMod::registerEntity(const sol::table &table) {
m_defs.emplace(DefinitionType::Entity, table);
}
void LuaMod::spawnEntity(const std::string &entityID, const sol::table &table) {
m_entityLoader.spawnEntity(entityID, table);
}

View File

@ -32,17 +32,19 @@
#include "LuaBiomeLoader.hpp"
#include "LuaBlockLoader.hpp"
#include "LuaDimensionLoader.hpp"
#include "LuaEntityLoader.hpp"
#include "LuaItemLoader.hpp"
#include "LuaRecipeLoader.hpp"
#include "LuaSkyLoader.hpp"
class Registry;
class WorldController;
// This class is meant to be used ONLY in Lua
class LuaMod {
public:
// FIXME: Check if this name has already been used
// Check if this name matches [a-zA-Z0-9]+
// Check if this name != "group"
LuaMod(const std::string &id) : m_id(id) {}
LuaMod(const std::string &id, Registry &registry, WorldController &worldController)
: m_id(id), m_registry(registry), m_worldController(worldController) {}
void commit();
@ -61,6 +63,9 @@ class LuaMod {
void registerTree(const sol::table &table);
void registerBiome(const sol::table &table);
void registerDimension(const sol::table &table);
void registerEntity(const sol::table &table);
void spawnEntity(const std::string &entityID, const sol::table &table);
enum class DefinitionType {
Block,
@ -71,18 +76,24 @@ class LuaMod {
Tree,
Biome,
Dimension,
Entity,
};
std::queue<std::pair<DefinitionType, sol::table>> m_defs;
std::string m_id;
// TODO: Add registry instance to loaders in order to avoid using singleton
Registry &m_registry;
WorldController &m_worldController;
LuaBlockLoader m_blockLoader{*this};
LuaItemLoader m_itemLoader{*this};
LuaRecipeLoader m_recipeLoader;
LuaSkyLoader m_skyLoader{*this};
LuaBiomeLoader m_biomeLoader{*this};
LuaDimensionLoader m_dimensionLoader{*this};
LuaEntityLoader m_entityLoader{*this, m_worldController};
};
#endif // LUAMOD_HPP_

View File

@ -77,7 +77,7 @@ LuaMod &ServerModLoader::registerMod(const std::string &name) {
if (it != m_mods.end())
throw std::runtime_error("The mod '" + name + "' has already been loaded. Mod name must be unique.");
m_mods.emplace(name, name);
m_mods.emplace(name, LuaMod{name, m_registry, m_worldController});
return m_mods.at(name);
}

View File

@ -32,11 +32,14 @@
#include "LuaMod.hpp"
class Registry;
class ScriptEngine;
class WorldController;
class ServerModLoader {
public:
ServerModLoader(ScriptEngine &scriptEngine) : m_scriptEngine(scriptEngine) {}
ServerModLoader(ScriptEngine &scriptEngine, Registry &registry, WorldController &worldController)
: m_scriptEngine(scriptEngine), m_registry(registry), m_worldController(worldController) {}
void loadMods();
@ -44,6 +47,8 @@ class ServerModLoader {
private:
ScriptEngine &m_scriptEngine;
Registry &m_registry;
WorldController &m_worldController;
std::unordered_map<std::string, LuaMod> m_mods;
};

View File

@ -0,0 +1,162 @@
/*
* =====================================================================================
*
* OpenMiner
*
* Copyright (C) 2018-2020 Unarelith, Quentin Bazin <openminer@unarelith.net>
* Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md)
*
* This file is part of OpenMiner.
*
* OpenMiner is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* OpenMiner is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with OpenMiner; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* =====================================================================================
*/
#include <gk/core/Debug.hpp>
#include "AnimationComponent.hpp"
#include "DrawableDef.hpp"
#include "LuaEntityLoader.hpp"
#include "LuaMod.hpp"
#include "NetworkComponent.hpp"
#include "PositionComponent.hpp"
#include "Registry.hpp"
#include "RotationComponent.hpp"
void LuaEntityLoader::loadEntity(const sol::table &table) const {
std::string stringID = m_mod.id() + ":" + table["id"].get<std::string>();
entt::registry &registry = Registry::getInstance().entityRegistry();
entt::entity entity = Registry::getInstance().registerEntity(stringID);
// TODO: Create a struct to store these functions
sol::unsafe_function onSpawn = table["on_spawn"];
sol::unsafe_function onCollision = table["on_collision"];
sol::optional<sol::table> properties = table["properties"];
if (properties != sol::nullopt) {
sol::optional<sol::table> visual = properties.value()["visual"];
if (visual != sol::nullopt) {
std::string type = visual.value()["type"];
if (type == "inventorycube") {
auto &drawable = registry.emplace<DrawableDef>(entity);
auto &cube = drawable.addInventoryCube();
cube.size = visual.value()["size"].get_or(1.0f);
sol::optional<sol::table> origin = visual.value()["origin"];
if (origin != sol::nullopt) {
cube.origin = {origin.value()[1], origin.value()[2], origin.value()[3]};
}
}
else
gkError() << "For entity '" + stringID + "': Visual type '" + type + "' unknown";
}
bool isRotatable = properties.value()["is_rotatable"].get_or(false);
if (isRotatable)
registry.emplace<RotationComponent>(entity);
sol::optional<sol::table> animation = properties.value()["animation"];
if (animation != sol::nullopt) {
auto &animationComponent = registry.emplace<AnimationComponent>(entity);
for (auto &it : animation.value()) {
sol::table anim = it.second.as<sol::table>();
std::string type = anim["type"];
if (type == "rotation") {
sol::table axis = anim["axis"];
animationComponent.addRotation(axis[1], axis[2], axis[3], anim["angle"]);
}
else if (type == "translation") {
sol::table delta = anim["delta"];
animationComponent.addTranslation(delta[1], delta[2], delta[3], anim["min"], anim["max"], anim["loop"].get_or(true));
}
else
gkError() << "For entity '" + stringID + "': Animation type '" + type + "' unknown";
}
}
sol::optional<sol::table> hitboxTable = properties.value()["hitbox"];
if (hitboxTable != sol::nullopt) {
registry.emplace<gk::FloatBox>(entity,
hitboxTable.value()[1], hitboxTable.value()[2], hitboxTable.value()[3],
hitboxTable.value()[4], hitboxTable.value()[5], hitboxTable.value()[6]
);
}
}
}
#include "WorldController.hpp"
void LuaEntityLoader::spawnEntity(const std::string &entityID, const sol::table &table) const {
gk::Vector3f pos;
u16 dim;
sol::optional<sol::table> position = table["position"];
sol::optional<u16> dimension = table["dimension"];
if (position != sol::nullopt && dimension != sol::nullopt) {
pos = {position.value()[1], position.value()[2], position.value()[3]};
dim = dimension.value();
}
else {
gkError() << "In mod '" + m_mod.id() + "': Cannot spawn entity '" + entityID + "' without a position and a dimension";
return;
}
ItemStack stack;
sol::optional<sol::table> itemStack = table["itemstack"];
if (itemStack != sol::nullopt) {
stack.setItem(itemStack.value()[1]);
stack.setAmount(itemStack.value()[2].get_or(1));
}
// Create entity copy here
entt::registry &registry = Registry::getInstance().entityRegistry();
entt::entity entityModel = Registry::getInstance().getEntityFromStringID(entityID);
if (entityModel != (entt::entity)0) {
ServerWorld &world = m_worldController.getWorld(dim);
entt::registry &sceneRegistry = world.scene().registry();
entt::entity entity = sceneRegistry.create();
sceneRegistry.emplace<PositionComponent>(entity, pos.x, pos.y, pos.z, dim);
sceneRegistry.emplace<NetworkComponent>(entity, entity);
if (registry.has<RotationComponent>(entityModel))
sceneRegistry.emplace<RotationComponent>(entity);
auto *drawable = registry.try_get<DrawableDef>(entityModel);
if (drawable) {
auto &drawableDef = sceneRegistry.emplace<DrawableDef>(entity, *drawable);
if (stack.amount()) {
auto &cube = drawableDef.getInventoryCubeDef();
cube.blockID = stack.item().stringID();
}
}
auto *animation = registry.try_get<AnimationComponent>(entityModel);
if (animation)
sceneRegistry.emplace<AnimationComponent>(entity, *animation);
auto *hitbox = registry.try_get<gk::FloatBox>(entityModel);
if (hitbox)
sceneRegistry.emplace<gk::FloatBox>(entity, *hitbox);
if (stack.amount())
sceneRegistry.emplace<ItemStack>(entity, stack);
}
else
gkError() << "In mod '" + m_mod.id() + "': Cannot find entity with id '" + entityID + "'";
}

View File

@ -0,0 +1,48 @@
/*
* =====================================================================================
*
* OpenMiner
*
* Copyright (C) 2018-2020 Unarelith, Quentin Bazin <openminer@unarelith.net>
* Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md)
*
* This file is part of OpenMiner.
*
* OpenMiner is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* OpenMiner is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with OpenMiner; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* =====================================================================================
*/
#ifndef LUAENTITYLOADER_HPP_
#define LUAENTITYLOADER_HPP_
#include <sol.hpp>
class LuaMod;
class WorldController;
class LuaEntityLoader {
public:
LuaEntityLoader(LuaMod &mod, WorldController &worldController)
: m_mod(mod), m_worldController(worldController) {}
void loadEntity(const sol::table &table) const;
void spawnEntity(const std::string &entityID, const sol::table &table) const;
private:
LuaMod &m_mod;
WorldController &m_worldController;
};
#endif // LUAENTITYLOADER_HPP_

View File

@ -264,8 +264,9 @@ void ServerCommandHandler::setupCallbacks() {
ServerWorld &world = getWorldForClient(client.id);
world.setData(x, y, z, block >> 16);
world.setBlock(x, y, z, block & 0xffff);
const Block &blockDef = Registry::getInstance().getBlock(block & 0xffff);
m_scriptEngine.luaCore().onEvent(LuaEventType::OnBlockPlaced, glm::ivec3{x, y, z}, *player, world, client, *this);
m_scriptEngine.luaCore().onEvent(LuaEventType::OnBlockPlaced, glm::ivec3{x, y, z}, blockDef, *player, world, client, *this);
Network::Packet answer;
answer << Network::Command::BlockUpdate << x << y << z << block;
@ -282,10 +283,11 @@ void ServerCommandHandler::setupCallbacks() {
packet >> x >> y >> z;
ServerWorld &world = getWorldForClient(client.id);
world.onBlockDigged(x, y, z, Registry::getInstance().getBlock(world.getBlock(x, y, z)), *player);
const Block &blockDef = Registry::getInstance().getBlock(world.getBlock(x, y, z));
world.onBlockDigged(x, y, z, blockDef, *player);
world.setBlock(x, y, z, 0);
m_scriptEngine.luaCore().onEvent(LuaEventType::OnBlockDigged, glm::ivec3{x, y, z}, *player, world, client, *this);
m_scriptEngine.luaCore().onEvent(LuaEventType::OnBlockDigged, glm::ivec3{x, y, z}, blockDef, *player, world, client, *this);
Network::Packet answer;
answer << Network::Command::BlockUpdate << x << y << z << u32(0);