Merge branch 'main' into environments

This commit is contained in:
MihailRis 2026-07-26 23:56:00 +03:00
commit f9bc401236
134 changed files with 3186 additions and 883 deletions

View file

@ -37,6 +37,14 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/"
)
else()
set(LUAJIT_PACKAGE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}")
if(EXISTS "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit-2.1")
set(LUAJIT_INCLUDE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit-2.1")
else()
set(LUAJIT_INCLUDE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit")
endif()
add_library(luajit SHARED IMPORTED)
set_target_properties(
luajit
@ -45,8 +53,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/bin/lua51.dll"
IMPORTED_IMPLIB
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/lib/lua51.lib"
INTERFACE_INCLUDE_DIRECTORIES
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit"
INTERFACE_INCLUDE_DIRECTORIES "${LUAJIT_INCLUDE_DIR}"
)
endif()

View file

@ -173,6 +173,12 @@ void audio::initialize(
logger.info() << "initializing NoAudio backend";
backend = NoAudio::create().release();
}
if (settings.inputDevice.get() == "auto") {
auto inputDevices = audio::get_input_devices_names();
if (!inputDevices.empty()) {
settings.inputDevice.set(inputDevices.at(0));
}
}
struct {
std::string name;
NumberSetting* setting;
@ -210,7 +216,9 @@ void audio::initialize(
::input_device = backend->openInputDevice(
settings.inputDevice.get(), 44100, 1, 16
);
::input_device->startCapture();
if (::input_device != nullptr) {
::input_device->startCapture();
}
} else {
if (::input_device) {
::input_device->stopCapture();

View file

@ -22,9 +22,26 @@ namespace {
}
inline double power(double base, int64_t power) {
if (power == 0) {
return 1.0;
}
uint64_t exp;
if (power < 0) {
base = 1.0 / base;
exp = static_cast<uint64_t>(-(power + 1)) + 1;
} else {
exp = static_cast<uint64_t>(power);
}
double result = 1.0;
for (int64_t i = 0; i < power; i++) {
result *= base;
while (exp > 0) {
if (exp & 1)
result *= base;
base *= base;
exp >>= 1;
}
return result;
}

View file

@ -57,3 +57,7 @@ std::string parsing_error::errorLog() const {
ss << "^";
return ss.str();
}
std::runtime_error parsing_error::toRuntimeError() const {
return std::runtime_error("parsing error: " + errorLog());
}

View file

@ -73,4 +73,6 @@ public:
uint linestart
);
std::string errorLog() const;
std::runtime_error toRuntimeError() const;
};

View file

@ -49,9 +49,9 @@ static util::Buffer<ubyte> write_to_memory(uint width, uint height, const ubyte*
png_write_info(png_ptr, info_ptr);
auto row = std::make_unique<png_byte[]>(pixsize * width);
for (uint y = 0; y < height; y++) {
for (uint x = 0; x < width; x++) {
for (uint i = 0; i < pixsize; i++) {
for (int y = height-1; y >= 0; y--) {
for (int x = 0; x < width; x++) {
for (int i = 0; i < pixsize; i++) {
row[x * pixsize + i] =
(png_byte)data[(y * width + x) * pixsize + i];
}

View file

@ -30,6 +30,7 @@ namespace {
public:
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
: face(std::move(face)), buffer(std::move(buffer)) {
monospace = this->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
}
~FTFontFile() {

View file

@ -19,6 +19,12 @@ namespace vector_fonts {
virtual std::unique_ptr<Texture> renderPage(
int pageid, std::vector<Glyph>& glyphs, int size
) = 0;
bool isMonospace() const {
return monospace;
}
protected:
bool monospace = true;
};
void initialize();

View file

@ -385,10 +385,22 @@ public:
}
if (c == '(') {
nextChar();
// TODO: replace with array parsing after moving to dv::value's
std::string value = std::string(readUntil(')'));
int depth = 1;
size_t start = pos;
while (hasNext()) {
char c = nextChar();
if (c == '(') {
depth++;
} else if (c == ')') {
depth--;
if (depth == 0) {
goBack(1);
break;
}
}
}
expect(')');
return value;
return std::string(source.substr(start, pos - start - 1));
}
return std::string(readUntilWhitespace());
}
@ -528,3 +540,11 @@ std::string xml::stringify(
return ss.str();
}
std::string xml::stringify(
const Node& element, bool nice, const std::string& indentStr
) {
std::stringstream ss;
stringifyElement(ss, element, nice, indentStr, 0);
return ss.str();
}

View file

@ -119,6 +119,12 @@ namespace xml {
const std::string& indentStr = " "
);
std::string stringify(
const Node& element,
bool nice = true,
const std::string& indentStr = " "
);
/// @brief Read XML Document from string
/// @param filename file name will be shown in error messages
/// @param source xml source code string

View file

@ -188,6 +188,7 @@ public:
ContentUnitDefs<GeneratorDef> generators;
std::unique_ptr<DrawGroups> const drawGroups;
ResourceIndicesSet resourceIndices {};
std::set<std::string> components {};
Content(
std::unique_ptr<ContentIndices> indices,

View file

@ -178,7 +178,16 @@ void ContentUnitLoader<DefT>::loadUnit(
) {
auto folder = pack.folder;
auto configFile = folder / (defsDir + "/" + name + ".json");
if (io::exists(configFile)) loadUnit(def, full, configFile);
if (io::exists(configFile)) {
try {
loadUnit(def, full, configFile);
} catch (const std::runtime_error& err) {
throw std::runtime_error(
"file " + util::quote(configFile.string()) + ": " +
std::string(err.what())
);
}
}
}
void ContentLoader::loadBlockMaterial(
@ -497,6 +506,7 @@ void ContentLoader::loadScripts(Content& content) {
scripting::on_scripts_loading();
load_scripts(content, content.blocks);
load_scripts(content, content.items);
auto& tmpContent = content;
for (const auto& [packid, runtime] : content.getPacks()) {
auto env = runtime->getEnvironment();
@ -507,7 +517,7 @@ void ContentLoader::loadScripts(Content& content) {
// Load entity components
io::path componentsDir = folder / "scripts/components";
foreach_file(componentsDir, [&pack, env](const io::path& file) {
foreach_file(componentsDir, [&pack, env, &tmpContent](const io::path& file) {
auto name = pack.id + ":" + file.stem();
scripting::load_entity_component(
env,
@ -515,9 +525,21 @@ void ContentLoader::loadScripts(Content& content) {
file,
pack.id + ":scripts/components/" + file.name()
);
tmpContent.components.insert(name);
});
}
for (const auto& [eid, def] : content.entities.getDefs()) {
for (const auto& instance : def->components) {
if (content.components.find(instance.component) == content.components.end()) {
throw std::runtime_error(
"component " + instance.component +
" is not available (required by entity " + eid + ")"
);
}
}
}
scripting::on_content_loaded();
}

View file

@ -53,6 +53,9 @@ static void load_variant(
}
} else if (root.has("texture-faces")) {
const auto& texarr = root["texture-faces"];
if (texarr.length() < 6) {
throw std::runtime_error("texture-faces requires 6 elements");
}
for (uint i = 0; i < 6; i++) {
variant.textureFaces[i] = texarr[i].asString();
}

View file

@ -0,0 +1,63 @@
#include "AppScriptsControl.hpp"
#include "debug/Logger.hpp"
#include "engine/CoreParameters.hpp"
#include "io/io.hpp"
#include "Project.hpp"
#include "logic/scripting/scripting.hpp"
static debug::Logger logger("app-scripts");
AppScriptsControl::AppScriptsControl(
const CoreParameters& params, const Project& project
)
: project(project) {
io::path scriptFile =
params.scriptFile.empty()
? "project:start.lua"
: std::string("script:") + params.scriptFile.filename().u8string();
if (io::exists(scriptFile)) {
logger.info() << "starting script: " << params.scriptFile.u8string();
scriptCoroutine = scripting::start_app_script(scriptFile);
} else {
logger.warning() << "script does not exists: " << params.scriptFile.u8string();
}
if (!params.headless) {
loadProjectClientScript();
}
}
void AppScriptsControl::loadProjectClientScript() {
io::path scriptFile = "project:project_client.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project client script: " << scriptFile.string();
clientScript = scripting::load_client_project_script(scriptFile);
} else {
logger.warning() << "project client script does not exists: " << scriptFile.string();
}
}
void AppScriptsControl::onScreenChange(const std::string& name, bool show) {
if (clientScript) {
clientScript->onScreenChange(name, show);
}
}
void AppScriptsControl::tick() {
if (scriptCoroutine && scriptCoroutine->isActive()) {
scriptCoroutine->update();
}
}
void AppScriptsControl::terminate(std::string_view reason) {
if (scriptCoroutine->isActive()) {
scriptCoroutine->terminate();
logger.info() << "script has been terminated due to " << reason;
}
}
bool AppScriptsControl::isFinished() const {
return scriptCoroutine == nullptr || !scriptCoroutine->isActive();
}

View file

@ -0,0 +1,30 @@
#pragma once
#include "interfaces/Process.hpp"
#include <memory>
#include <string>
namespace scripting {
class IClientProjectScript;
}
struct CoreParameters;
struct Project;
class AppScriptsControl {
public:
AppScriptsControl(const CoreParameters& params, const Project& project);
void tick();
void loadProjectClientScript();
void terminate(std::string_view reason);
void onScreenChange(const std::string& name, bool show);
bool isFinished() const;
private:
const Project& project;
std::unique_ptr<scripting::IClientProjectScript> clientScript;
std::unique_ptr<Process> scriptCoroutine;
};

View file

@ -2,6 +2,7 @@
#include "data/dv_util.hpp"
#include "debug/Logger.hpp"
#include "engine/CoreParameters.hpp"
#include "io/io.hpp"
#include "io/path.hpp"
#include "logic/scripting/scripting.hpp"
@ -40,26 +41,6 @@ void Project::deserialize(const dv::value& src) {
}
}
void Project::loadProjectClientScript() {
io::path scriptFile = "project:project_client.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project client script";
clientScript = scripting::load_client_project_script(scriptFile);
} else {
logger.warning() << "project client script does not exists";
}
}
void Project::loadProjectStartScript() {
io::path scriptFile = "project:start.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project start script";
setupCoroutine = scripting::start_app_script(scriptFile);
} else {
logger.warning() << "project start script does not exists";
}
}
bool Permissions::has(const std::string& name) const {
return permissions.find(name) != permissions.end();
}

View file

@ -1,17 +1,12 @@
#pragma once
#include "interfaces/Serializable.hpp"
#include <set>
#include <string>
#include <vector>
#include <memory>
#include "interfaces/Process.hpp"
#include "interfaces/Serializable.hpp"
namespace scripting {
class IClientProjectScript;
}
struct Permissions {
static inline std::string DEBUGGING = "debugging";
static inline std::string NETWORK = "network";
@ -27,15 +22,10 @@ struct Project : Serializable {
std::string name;
std::string title;
std::vector<std::string> basePacks;
std::unique_ptr<scripting::IClientProjectScript> clientScript;
std::unique_ptr<Process> setupCoroutine;
Permissions permissions;
~Project();
dv::value serialize() const override;
void deserialize(const dv::value& src) override;
void loadProjectClientScript();
void loadProjectStartScript();
};

View file

@ -13,6 +13,7 @@
#include "content/ContentControl.hpp"
#include "core_defs.hpp"
#include "debug/Logger.hpp"
#include "devtools/AppScriptsControl.hpp"
#include "devtools/DebuggingServer.hpp"
#include "devtools/Editor.hpp"
#include "devtools/Project.hpp"
@ -191,10 +192,8 @@ void Engine::initialize(CoreParameters coreParameters) {
audio::set_input_device(name == "auto" ? "" : name);
}));
project->loadProjectStartScript();
if (!params.headless) {
project->loadProjectClientScript();
}
appScripts = std::make_unique<AppScriptsControl>(params, *project);
if (params.stdinCommands) {
cmd::start_stdin_cmd_reader(*this);
}
@ -263,9 +262,7 @@ void Engine::detachDebugger() {
}
void Engine::applicationTick() {
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
project->setupCoroutine->update();
}
appScripts->tick();
}
void Engine::updateFrontend() {
@ -353,6 +350,7 @@ void Engine::close() {
network.reset();
clearKeepedObjects();
project.reset();
appScripts.reset();
scripting::close();
logger.info() << "scripting finished";
if (!params.headless) {
@ -387,8 +385,8 @@ void Engine::loadProject() {
}
void Engine::setScreen(std::shared_ptr<Screen> screen) {
if (project->clientScript && this->screen) {
project->clientScript->onScreenChange(this->screen->getName(), false);
if (this->screen) {
appScripts->onScreenChange(this->screen->getName(), false);
}
// reset audio channels (stop all sources)
audio::reset_channel(audio::get_channel_index("regular"));
@ -397,8 +395,8 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
if (this->screen) {
this->screen->onOpen();
}
if (project->clientScript && this->screen) {
project->clientScript->onScreenChange(this->screen->getName(), true);
if (this->screen) {
appScripts->onScreenChange(this->screen->getName(), true);
window->setShouldRefresh();
}
}

View file

@ -12,6 +12,7 @@
class Assets;
class AssetsLoader;
class AssetsManagement;
class AppScriptsControl;
class ContentControl;
class EngineController;
class EnginePaths;
@ -55,6 +56,7 @@ class Engine : public util::ObjectsKeeper {
std::unique_ptr<Project> project;
std::unique_ptr<SettingsHandler> settingsHandler;
std::unique_ptr<AssetsManagement> assets;
std::unique_ptr<AppScriptsControl> appScripts;
std::shared_ptr<Screen> screen;
std::unique_ptr<ContentControl> content;
std::unique_ptr<EngineController> controller;
@ -184,5 +186,9 @@ public:
return debuggingServer.get();
}
AppScriptsControl& getAppScripts() {
return *appScripts;
}
void detachDebugger();
};

View file

@ -1,14 +1,17 @@
#include "Mainloop.hpp"
#include "Engine.hpp"
#include "debug/Logger.hpp"
#include "devtools/AppScriptsControl.hpp"
#include "devtools/Project.hpp"
#include "frontend/screens/MenuScreen.hpp"
#include "Engine.hpp"
#include "frontend/screens/LevelScreen.hpp"
#include "frontend/screens/MenuScreen.hpp"
#include "graphics/ui/elements/Container.hpp"
#include "graphics/ui/GUI.hpp"
#include "io/path.hpp"
#include "logic/scripting/scripting.hpp"
#include "window/Window.hpp"
#include "world/Level.hpp"
#include "graphics/ui/GUI.hpp"
#include "graphics/ui/elements/Container.hpp"
static debug::Logger logger("mainloop");
@ -19,6 +22,7 @@ void Mainloop::run() {
auto& time = engine.getTime();
auto& window = engine.getWindow();
auto& settings = engine.getSettings();
const auto& coreParams = engine.getCoreParameters();
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
if (level == nullptr) {
@ -35,9 +39,11 @@ void Mainloop::run() {
logger.info() << "starting menu screen";
engine.setScreen(std::make_shared<MenuScreen>(engine));
auto& appScripts = engine.getAppScripts();
logger.info() << "main loop started";
while (!window.isShouldClose()){
while (!window.isShouldClose()) {
time.update(window.time());
engine.applicationTick();
engine.updateFrontend();
@ -50,6 +56,10 @@ void Mainloop::run() {
settings.display.adaptiveFpsInMenu.get() &&
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
);
if (coreParams.testMode && appScripts.isFinished()) {
logger.info() << "test finished";
engine.quit();
}
}
logger.info() << "main loop stopped";
}

View file

@ -1,13 +1,14 @@
#include "ServerMainloop.hpp"
#include "Engine.hpp"
#include "logic/scripting/scripting.hpp"
#include "devtools/AppScriptsControl.hpp"
#include "logic/LevelController.hpp"
#include "interfaces/Process.hpp"
#include "debug/Logger.hpp"
#include "world/Level.hpp"
#include "world/World.hpp"
#include "util/platform.hpp"
#include "devtools/Project.hpp"
#include <chrono>
@ -32,21 +33,12 @@ void ServerMainloop::run() {
setLevel(std::move(level));
});
auto process = scripting::start_app_script(
"script:" + coreParams.scriptFile.filename().u8string()
);
double targetDelta = 1.0 / static_cast<double>(coreParams.tps);
double delta = targetDelta;
auto begin = system_clock::now();
auto startupTime = begin;
while (process->isActive()) {
if (engine.isQuitSignal()) {
process->terminate();
logger.info() << "script has been terminated due to quit signal";
break;
}
while (!engine.isQuitSignal() && !engine.getAppScripts().isFinished()) {
if (coreParams.testMode) {
time.step(delta);
} else {
@ -55,7 +47,6 @@ void ServerMainloop::run() {
duration_cast<microseconds>(now - startupTime).count() / 1e6);
delta = time.getDelta();
}
process->update();
if (controller) {
controller->getLevel()->getWorld()->updateTimers(delta);
controller->update(glm::min(delta, 0.2), false);

View file

@ -7,6 +7,9 @@
#include "graphics/ui/elements/InventoryView.hpp"
#include "graphics/ui/gui_xml.hpp"
#include "logic/scripting/scripting.hpp"
#include "debug/Logger.hpp"
static debug::Logger logger("ui-document");
UiDocument::UiDocument(
std::string id,
@ -17,6 +20,19 @@ UiDocument::UiDocument(
rebuildIndices();
}
UiDocument::~UiDocument() {
try {
scripting::on_ui_destroy(*this);
} catch (const std::exception& err) {
logger.error() << "an error occurred on calling on_destroy event for document '"
<< id << "': " << err.what();
} catch (...) {
logger.error() << "unknown exception caught on calling on_destroy "
"event for document '"
<< id << "'";
}
}
void UiDocument::rebuildIndices() {
map.clear();
gui::UINode::getIndices(root, map);

View file

@ -17,6 +17,7 @@ struct UiDocScript {
bool onopen : 1;
bool onprogress : 1;
bool onclose : 1;
bool ondestroy : 1;
};
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
@ -35,6 +36,8 @@ public:
scriptenv env
);
~UiDocument();
void rebuildIndices();
void pushIndices(const std::shared_ptr<gui::UINode>& node);

View file

@ -563,7 +563,7 @@ void Hud::add(const HudElement& element, const dv::value& argsArray) {
args.emplace_back(static_cast<integer_t>(blockPos[i]));
}
scripting::on_ui_open(
element.getDocument(),
*element.getDocument(),
std::move(args)
);
}
@ -578,7 +578,7 @@ void Hud::onRemove(const HudElement& element) {
if (invview) {
inventory = invview->getInventory().get();
}
scripting::on_ui_close(document, inventory);
scripting::on_ui_close(*document, inventory);
if (invview) {
invview->unbind();
}

View file

@ -86,7 +86,7 @@ UiDocument* menus::show(
);
auto document = documentPtr.get();
engine.getAssets()->store(std::move(documentPtr), fullname);
scripting::on_ui_open(document, std::move(args));
scripting::on_ui_open(*document, std::move(args));
menu->addPage(name, document->getRoot());
menu->setPage(name);
return document;
@ -99,13 +99,13 @@ void menus::show_process_panel(
auto menu = engine.getGUI().getMenu();
menu->reset();
auto doc =
auto document =
menus::show(engine, "process", {util::wstr2str_utf8(langs::get(text))});
std::dynamic_pointer_cast<Container>(doc->getRoot())
std::dynamic_pointer_cast<Container>(document->getRoot())
->listenInterval(0.01f, [=]() {
task->update();
uint tasksDone = task->getWorkDone();
scripting::on_ui_progress(doc, tasksDone, initialWork);
scripting::on_ui_progress(*document, tasksDone, initialWork);
});
}

View file

@ -26,6 +26,11 @@ Font::Font(
pages(std::move(pages)),
glyphs(std::move(glyphs)),
fontFile(std::move(fontFile)) {
if (this->fontFile.has_value()) {
if (auto fontPtr = this->fontFile->lock()) {
monospace = fontPtr->isMonospace();
}
}
}
Font::~Font() = default;
@ -34,6 +39,10 @@ int Font::getYOffset() const {
return yoffset;
}
bool Font::isMonospace() const {
return monospace;
}
int Font::getLineHeight() const {
return lineHeight;
}
@ -53,7 +62,7 @@ bool Font::isPrintableChar(uint codepoint) const {
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
auto font = this->font.has_value() ? this->font->lock() : nullptr;
if (font == nullptr) {
if (font == nullptr || font->isMonospace()) {
return std::min(text.length() - offset, length) * _glyphInterval;
}
int totalWidth = 0;
@ -153,6 +162,8 @@ static inline void draw_text(
const FontStylesScheme* styles,
size_t styleMapOffset
) {
bool is3d = std::is_same<Batch, Batch3D>();
static FontStylesScheme defStyles {{{}}, {0}};
if (styles == nullptr) {
@ -185,13 +196,26 @@ static inline void draw_text(
float advance = baseAdvance;
if (auto glyph = font.getGlyph(c)) {
yOffset = glyph->yOffset;
advance = glyph->xAdvance;
advance = glyph->xAdvance /
static_cast<float>(font.getLineHeight()) * 2.0f *
baseAdvance;
}
uint charpage = c >> 8;
if (charpage == page){
batch.texture(font.getPage(charpage));
draw_glyph(
batch, pos, glm::vec2(x, y - yOffset / static_cast<float>(font.getLineHeight())), c, right, up, interval, style
batch,
pos,
glm::vec2(
x,
y - yOffset * (is3d ? -1 : 1) /
static_cast<float>(font.getLineHeight())
),
c,
right,
up,
interval,
style
);
}
else if (charpage > page && charpage < next){

View file

@ -39,6 +39,7 @@ public:
);
~Font();
bool isMonospace() const;
int getLineHeight() const;
int getYOffset() const;
@ -94,6 +95,7 @@ private:
int lineHeight;
int yoffset;
int glyphInterval;
bool monospace = true;
std::vector<std::unique_ptr<Texture>> pages;
std::vector<Glyph> glyphs;
std::optional<std::weak_ptr<vector_fonts::FontFile>> fontFile;

View file

@ -9,8 +9,8 @@ class Font;
struct FontMetrics {
std::optional<std::weak_ptr<Font>> font;
int lineHeight;
int yoffset;
int lineHeight = 0;
int yoffset = 0;
int _glyphInterval = 8;
~FontMetrics();

View file

@ -1,5 +1,7 @@
#include "ImageData.hpp"
#include "debug/Logger.hpp"
#include <glm/glm.hpp>
#include <assert.h>
#include <stdexcept>
@ -7,6 +9,8 @@
#include <cmath>
#include <algorithm>
static debug::Logger logger("image-data");
ImageData::ImageData(ImageFormat format, uint width, uint height)
: format(format), width(width), height(height) {
size_t pixsize;
@ -106,47 +110,83 @@ std::unique_ptr<ImageData> ImageData::cropped(int x, int y, int width, int heigh
return subImage;
}
static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) {
const int left = 0;
const int right = width;
const int bottom = 0;
const int top = height;
static bool clip_line(
int& x0, int& y0, int& x1, int& y1, int width, int height
) {
if (width <= 0 || height <= 0) {
return false;
}
constexpr int INSIDE = 0;
constexpr int LEFT = 1;
constexpr int RIGHT = 2;
constexpr int BOTTOM = 4;
constexpr int TOP = 8;
int dx = x2 - x1;
int dy = y2 - y1;
const int xmin = 0;
const int ymin = 0;
const int xmax = width - 1;
const int ymax = height - 1;
float t0 = 0.0f;
float t1 = 1.0f;
auto outcode = [&](int x, int y) {
int code = INSIDE;
auto clip = [](int p, int q, float& t0, float& t1) {
if (p == 0) {
return q >= 0;
}
float t = static_cast<float>(q) / p;
if (p < 0) {
if (t > t1) return false;
if (t > t0) t0 = t;
} else {
if (t < t0) return false;
if (t < t1) t1 = t;
}
return true;
if (x < xmin)
code |= LEFT;
else if (x > xmax)
code |= RIGHT;
if (y < ymin)
code |= BOTTOM;
else if (y > ymax)
code |= TOP;
return code;
};
if (!clip(-dx, x1 - left, t0, t1)) return false;
if (!clip( dx, right - x1, t0, t1)) return false;
if (!clip(-dy, y1 - bottom, t0, t1)) return false;
if (!clip( dy, top - y1, t0, t1)) return false;
int c0 = outcode(x0, y0);
int c1 = outcode(x1, y1);
if (t1 < 1.0f) {
x2 = x1 + static_cast<int>(std::round(t1 * dx));
y2 = y1 + static_cast<int>(std::round(t1 * dy));
while (true) {
if (!(c0 | c1)) {
return true;
}
if (c0 & c1) {
return false;
}
const int out = c0 ? c0 : c1;
double x = 0.0;
double y = 0.0;
if (out & TOP) {
y = ymax;
x = x0 + (x1 - x0) * static_cast<double>(ymax - y0) / (y1 - y0);
} else if (out & BOTTOM) {
y = ymin;
x = x0 + (x1 - x0) * static_cast<double>(ymin - y0) / (y1 - y0);
} else if (out & RIGHT) {
x = xmax;
y = y0 + (y1 - y0) * static_cast<double>(xmax - x0) / (x1 - x0);
} else { // LEFT
x = xmin;
y = y0 + (y1 - y0) * static_cast<double>(xmin - x0) / (x1 - x0);
}
if (out == c0) {
x0 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
y0 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
c0 = outcode(x0, y0);
} else {
x1 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
y1 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
c1 = outcode(x1, y1);
}
}
if (t0 > 0.0f) {
x1 = x1 + static_cast<int>(std::round(t0 * dx));
y1 = y1 + static_cast<int>(std::round(t0 * dy));
}
return true;
}
static bool is_point_outside(int x, int y, int width, int height) {
return x < 0 || y < 0 || x >= width || y >= height;
}
template<uint channels>
@ -155,11 +195,24 @@ static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const gl
uint width = image.getWidth();
uint height = image.getHeight();
glm::ivec4 init {x1, y1, x2, y2};
if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width ||
y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) &&
!clip_line(x1, y1, x2, y2, width, height)) {
return;
}
// spam info for bug report
if (is_point_outside(x1, y1, width, height) || is_point_outside(x2, y2, width, height)) {
logger.warning() << "clip_line fault: [" << init.x << ", " << init.y
<< "] - [" << init.z << ", " << init.w << "] --> ["
<< x1 << ", " << y1 << "] - [" << x2 << ", " << y2
<< "]";
x1 = glm::clamp(x1, 0, static_cast<int>(width) - 1);
y1 = glm::clamp(y1, 0, static_cast<int>(height) - 1);
x2 = glm::clamp(x2, 0, static_cast<int>(width) - 1);
y2 = glm::clamp(y2, 0, static_cast<int>(height) - 1);
}
int dx = std::abs(x2 - x1);
int dy = -std::abs(y2 - y1);

View file

@ -22,6 +22,7 @@
#include "voxels/Block.hpp"
#include "voxels/Chunk.hpp"
#include "voxels/Chunks.hpp"
#include "voxels/blocks_agent.hpp"
#include "window/Camera.hpp"
#include "world/Level.hpp"
#include "WorldRenderer.hpp"
@ -98,7 +99,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
auto& chunks = *player.chunks;
const auto& start = camera.position;
float rayLength = 40.0f;
float length = 40.0f;
auto& blocks = level.content.getIndices()->blocks;
@ -107,7 +108,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
float averageDistance = 0.0f;
float averageAbsorption = 0.0f;
float speedOfSound = 300.0f;
float minDistance = rayLength;
float minDistance = length;
for (int i = 0; i < rays; i++) {
float u1 = random.randFloat();
float u2 = random.randFloat();
@ -121,12 +122,13 @@ void Decorator::updateAcoustics(const Camera& camera) {
glm::vec3 end;
glm::ivec3 norm;
glm::ivec3 iend;
auto vox = chunks.rayCast(start, dir, rayLength, end, norm, iend);
blocks_agent::RaycastSettings raycast {};
auto vox = chunks.rayCast(start, dir, length, end, norm, iend, raycast);
if (vox == nullptr) {
continue;
}
auto distance = glm::distance(start, end);
if (distance >= rayLength * 0.98f) {
if (distance >= length * 0.98f) {
continue;
}
if (distance < minDistance) {
@ -149,7 +151,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
averageAbsorption /= hit;
}
float decayTime = 20.0f * (averageDistance / rayLength);
float decayTime = 20.0f * (averageDistance / length);
float escapeRatio = static_cast<float>(rays - hit) / static_cast<float>(rays);
acoustics.reverbGain = glm::pow(hit / static_cast<float>(rays), 3.0f);

View file

@ -58,7 +58,10 @@ void TextsRenderer::renderNote(
}
opacity = preset.xrayOpacity;
}
auto& font = assets.require<Font>(FONT_DEFAULT);
auto specifiedFont = assets.get<Font>(preset.font);
auto& font =
specifiedFont ? *specifiedFont : assets.require<Font>(FONT_DEFAULT);
glm::vec3 xvec = note.getAxisX();
glm::vec3 yvec = note.getAxisY();
@ -76,7 +79,10 @@ void TextsRenderer::renderNote(
yvec = camera.up;
}
float scale =
(1.0f - preset.perspective) * glm::pow(glm::distance(camera.position, pos), 1.0f-preset.perspective);
(1.0f - preset.perspective) *
glm::pow(
glm::distance(camera.position, pos), 1.0f - preset.perspective
);
xvec *= 1.0f + scale;
yvec *= 1.0f + scale;
}
@ -118,6 +124,7 @@ void TextsRenderer::renderNote(
}
auto color = preset.color;
batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity));
font.draw(
batch,
text,

View file

@ -13,7 +13,7 @@ InlineFrame::~InlineFrame() = default;
void InlineFrame::setSrc(const std::string& src) {
this->src = src;
if (document) {
scripting::on_ui_close(document.get(), nullptr);
scripting::on_ui_close(*document, nullptr);
document = nullptr;
root = nullptr;
}
@ -31,7 +31,7 @@ void InlineFrame::setDocument(const std::shared_ptr<UiDocument>& document) {
root->setSize(size);
gui.postRunnable([this]() {
scripting::on_ui_open(this->document.get(), {});
scripting::on_ui_open(*this->document, {});
});
}

View file

@ -207,6 +207,9 @@ uint Label::getLinesNumber() const {
void Label::draw(const DrawContext& pctx, const Assets& assets) {
auto batch = pctx.getBatch2D();
auto font = assets.getShared<Font>(fontName);
if (font == nullptr) {
return;
}
cache.prepare(
font,
font->getMetrics(),

View file

@ -353,7 +353,9 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
auto font = assets.getShared<Font>(label->getFontName());
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
if (font != nullptr) {
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
}
glm::vec2 pos = calcPos();

View file

@ -173,8 +173,12 @@ bool io::write_binary_json(
}
dv::value io::read_json(const path& filename) {
std::string text = io::read_string(filename);
return json::parse(filename.string(), text);
auto text = io::read_string(filename);
try {
return json::parse(filename.string(), text);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
dv::value io::read_binary_json(const path& file) {
@ -184,7 +188,12 @@ dv::value io::read_binary_json(const path& file) {
}
dv::value io::read_toml(const path& file) {
return toml::parse(file.string(), io::read_string(file));
auto text = io::read_string(file);
try {
return toml::parse(file.string(), text);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
std::vector<std::string> io::read_list(const io::path& filename) {

View file

@ -15,8 +15,33 @@ public:
memory_streambuf(const memory_streambuf&) = delete;
memory_streambuf& operator=(const memory_streambuf&) = delete;
pos_type seekoff(off_type off, std::ios_base::seekdir way,
std::ios_base::openmode mode = std::ios_base::in) override {
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
char* base = eback();
char* end = egptr();
if (!base || !end) return pos_type(off_type(-1));
std::ptrdiff_t current = gptr() - base;
std::ptrdiff_t newpos;
if (way == std::ios_base::beg) newpos = off;
else if (way == std::ios_base::cur) newpos = current + off;
else if (way == std::ios_base::end) newpos = (end - base) + off;
else return pos_type(off_type(-1));
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
setg(base, base + newpos, end);
return pos_type(off_type(newpos));
}
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
return seekoff(off_type(sp), std::ios_base::beg, mode);
}
protected:
int_type underflow() override {
if (gptr() < egptr()) {
return traits_type::to_int_type(*gptr());
}
return traits_type::eof();
}
@ -36,7 +61,7 @@ private:
class memory_view_streambuf : public std::streambuf {
public:
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
: buffer(std::move(buffer)) {
: buffer(buffer) {
char* base = const_cast<char*>(this->buffer.data());
char* end = base + this->buffer.size();
setg(base, base, end);
@ -45,8 +70,33 @@ public:
memory_view_streambuf(const memory_view_streambuf&) = delete;
memory_view_streambuf& operator=(const memory_view_streambuf&) = delete;
pos_type seekoff(off_type off, std::ios_base::seekdir way,
std::ios_base::openmode mode = std::ios_base::in) override {
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
char* base = eback();
char* end = egptr();
if (!base || !end) return pos_type(off_type(-1));
std::ptrdiff_t current = gptr() - base;
std::ptrdiff_t newpos;
if (way == std::ios_base::beg) newpos = off;
else if (way == std::ios_base::cur) newpos = current + off;
else if (way == std::ios_base::end) newpos = (end - base) + off;
else return pos_type(off_type(-1));
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
setg(base, base + newpos, end);
return pos_type(off_type(newpos));
}
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
return seekoff(off_type(sp), std::ios_base::beg, mode);
}
protected:
int_type underflow() override {
if (gptr() < egptr()) {
return traits_type::to_int_type(*gptr());
}
return traits_type::eof();
}

View file

@ -55,6 +55,16 @@ public:
return fields;
}
void setFields(dv::value&& table, bool clear) {
if (clear) {
fields = std::move(table);
return;
}
for (auto&& [key, value] : table.asObject()) {
fields[std::move(key)] = std::move(value);
}
}
bool hasFields() const {
return fields != nullptr;
}

View file

@ -82,7 +82,6 @@ void LevelController::update(float delta, bool pause) {
continue;
}
player->rotationInterpolation.updateTimer(delta);
player->updateEntity();
glm::vec3 position = player->getPosition();
player->chunks->configure(
glm::floor(position.x),
@ -96,9 +95,9 @@ void LevelController::update(float delta, bool pause) {
*player,
player.get() == clientPlayer
);
player->updateEntity();
}
if (!pause) {
// update all objects that needed
blocks->update(delta, settings.chunks.padding.get());
level->entities->update(delta);
for (const auto& [_, player] : *level->players) {

View file

@ -200,8 +200,13 @@ void CameraControl::update(
auto castRay = [](Player& player, Camera& camera, const glm::vec3& front)
-> glm::vec3 {
auto blockEnd = player.chunks->rayCastToObstacle(camera.position, front, 3.0f);
Entities::RaycastSettings raycast {};
raycast.ignoredUid = player.getEntity();
raycast.solidEntitiesOnly = true;
auto entityRay = player.getLevel().entities->rayCast(
camera.position, front, 3.0f, player.getEntity(), true
camera.position, front, 3.0f, raycast
);
if (entityRay.has_value() &&
entityRay->distance < glm::distance(camera.position, blockEnd)) {
@ -350,7 +355,13 @@ voxel* PlayerController::updateSelection(float maxDistance) {
glm::ivec3 iend;
glm::ivec3 norm;
voxel* vox = chunks.rayCast(
camera->position, camera->front, maxDistance, end, norm, iend
camera->position,
camera->front,
maxDistance,
end,
norm,
iend,
blocks_agent::RaycastSettings {}
);
if (vox) {
maxDistance = glm::distance(camera->position, end);
@ -358,8 +369,12 @@ voxel* PlayerController::updateSelection(float maxDistance) {
auto prevEntity = selection.entity;
selection.entity = ENTITY_NONE;
selection.actualPosition = iend;
Entities::RaycastSettings raycast {};
raycast.ignoredUid = player.getEntity();
if (auto result = level.entities->rayCast(
camera->position, camera->front, maxDistance, player.getEntity()
camera->position, camera->front, maxDistance, raycast
)) {
selection.entity = result->entity;
selection.hitPosition =

View file

@ -1,104 +0,0 @@
#include "logic/scripting/descriptors_manager.hpp"
#include "debug/Logger.hpp"
static debug::Logger logger("descriptors-manager");
namespace scripting {
std::vector<std::optional<StreamDescriptor>> descriptors_manager::descriptors;
std::istream* descriptors_manager::get_input(int descriptor) {
if (!is_readable(descriptor))
return nullptr;
return descriptors[descriptor]->in.get();
}
std::ostream* descriptors_manager::get_output(int descriptor) {
if (!is_writeable(descriptor))
return nullptr;
return descriptors[descriptor]->out.get();
}
void descriptors_manager::flush(int descriptor) {
if (is_writeable(descriptor)) {
descriptors[descriptor]->out->flush();
}
}
bool descriptors_manager::has_descriptor(int descriptor) {
return is_readable(descriptor) || is_writeable(descriptor);
}
bool descriptors_manager::is_readable(int descriptor) {
return descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())
&& descriptors[descriptor].has_value()
&& descriptors[descriptor]->in != nullptr;
}
bool descriptors_manager::is_writeable(int descriptor) {
return descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())
&& descriptors[descriptor].has_value()
&& descriptors[descriptor]->out != nullptr;
}
void descriptors_manager::close(int descriptor) {
if (descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())) {
if (descriptors[descriptor].has_value()) {
auto& desc = descriptors[descriptor].value();
if (desc.out)
desc.out->flush();
desc.in.reset();
desc.out.reset();
}
descriptors[descriptor].reset();
descriptors[descriptor] = std::nullopt;
}
}
int descriptors_manager::open_descriptor(const io::path& path, bool write, bool read) {
std::unique_ptr<std::istream> in;
std::unique_ptr<std::ostream> out;
try {
if (read)
in = io::read(path);
if (write)
out = io::write(path);
} catch (const std::exception& e) {
logger.error() << "failed to open descriptor for " << path.string()
<< ": " << e.what();
return -1;
}
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
if (!descriptors[i].has_value()) {
descriptors[i] = StreamDescriptor{ std::move(in), std::move(out) };
return i;
}
}
descriptors.emplace_back(StreamDescriptor{ std::move(in), std::move(out) });
return static_cast<int>(descriptors.size() - 1);
}
void descriptors_manager::close_all_descriptors() {
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
if (descriptors[i].has_value()) {
close(i);
}
}
descriptors.clear();
}
}

View file

@ -1,39 +0,0 @@
#pragma once
#include <memory>
#include <vector>
#include <optional>
#include <string>
#include <istream>
#include <ostream>
#include "io/io.hpp"
namespace scripting {
struct StreamDescriptor {
std::unique_ptr<std::istream> in;
std::unique_ptr<std::ostream> out;
};
class descriptors_manager {
private:
static std::vector<std::optional<StreamDescriptor>> descriptors;
public:
static std::istream* get_input(int descriptor);
static std::ostream* get_output(int descriptor);
static void flush(int descriptor);
static bool has_descriptor(int descriptor);
static bool is_readable(int descriptor);
static bool is_writeable(int descriptor);
static void close(int descriptor);
static int open_descriptor(const io::path& path, bool write, bool read);
static void close_all_descriptors();
};
}

View file

@ -0,0 +1,160 @@
#include "logic/scripting/io_descriptors.hpp"
#include "debug/Logger.hpp"
#include "io/io.hpp"
#include <memory>
#include <string>
#include <vector>
#include <optional>
static debug::Logger logger("descriptors-manager");
using namespace scripting;
namespace {
struct StreamDescriptor {
// TODO: std::iostream?
std::unique_ptr<std::istream> in;
std::unique_ptr<std::ostream> out;
};
std::vector<std::optional<StreamDescriptor>> descriptors;
}
std::istream* io_descriptors::get_input(int id) {
if (!is_readable(id)) {
return nullptr;
}
return ::descriptors[id]->in.get();
}
std::ostream* io_descriptors::get_output(int id) {
if (!is_writeable(id)) {
return nullptr;
}
return ::descriptors[id]->out.get();
}
static StreamDescriptor& require_descriptor(int id) {
if (!io_descriptors::has_descriptor(id)) {
throw std::runtime_error(
"io-descriptor with id " + std::to_string(id) + " does not exists"
);
}
return *::descriptors[id];
}
std::istream& io_descriptors::require_input(int id) {
const auto& descriptor = require_descriptor(id);
if (descriptor.in) {
return *descriptor.in;
}
throw std::runtime_error("io-descriptor is not readable");
}
std::ostream& io_descriptors::require_output(int id) {
const auto& descriptor = require_descriptor(id);
if (descriptor.out) {
return *descriptor.out;
}
throw std::runtime_error("io-descriptor is not writeable");
}
void io_descriptors::flush(int id) {
if (is_writeable(id)) {
::descriptors[id]->out->flush();
}
}
int io_descriptors::available(int id) {
if (!is_readable(id))
return 0;
auto* stream = descriptors[id]->in.get();
auto current_pos = stream->tellg();
if (current_pos == -1) {
return 0;
}
stream->seekg(0, std::ios::end);
auto end_pos = stream->tellg();
stream->seekg(current_pos, std::ios::beg);
auto remaining = end_pos - current_pos;
if (remaining > std::numeric_limits<int>::max()) {
return std::numeric_limits<int>::max();
}
return static_cast<int>(remaining);
}
bool io_descriptors::has_descriptor(int id) {
return id >= 0 && id < static_cast<int>(::descriptors.size()) &&
::descriptors[id].has_value();
}
bool io_descriptors::is_readable(int id) {
return has_descriptor(id) && ::descriptors[id]->in != nullptr;
}
bool io_descriptors::is_writeable(int id) {
return id >= 0 && id < static_cast<int>(::descriptors.size())
&& ::descriptors[id].has_value()
&& ::descriptors[id]->out != nullptr;
}
void io_descriptors::close(int id) {
if (!has_descriptor(id)) {
return;
}
auto& desc = ::descriptors[id].value();
if (desc.out) {
desc.out->flush();
}
desc.in.reset();
desc.out.reset();
::descriptors[id] = std::nullopt;
}
int io_descriptors::open_descriptor(const io::path& path, bool write, bool read) {
std::unique_ptr<std::istream> in;
std::unique_ptr<std::ostream> out;
try {
if (read) {
in = io::read(path);
}
if (write) {
out = io::write(path);
}
} catch (const std::exception& e) {
logger.error() << "failed to open descriptor for " << path.string()
<< ": " << e.what();
return -1;
}
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
if (!descriptors[i].has_value()) {
descriptors[i] = StreamDescriptor{ std::move(in), std::move(out) };
return i;
}
}
::descriptors.emplace_back(StreamDescriptor{ std::move(in), std::move(out) });
return static_cast<int>(::descriptors.size() - 1);
}
void io_descriptors::close_all_descriptors() {
for (int i = 0; i < static_cast<int>(::descriptors.size()); ++i) {
if (::descriptors[i].has_value()) {
close(i);
}
}
::descriptors.clear();
}

View file

@ -0,0 +1,27 @@
#pragma once
#include <istream>
#include <ostream>
#include "io/fwd.hpp"
namespace scripting::io_descriptors {
std::istream* get_input(int id);
std::ostream* get_output(int id);
std::istream& require_input(int id);
std::ostream& require_output(int id);
void flush(int id);
int available(int id);
bool has_descriptor(int id);
bool is_readable(int id);
bool is_writeable(int id);
void close(int id);
int open_descriptor(const io::path& path, bool write, bool read);
void close_all_descriptors();
}

View file

@ -44,6 +44,7 @@ extern const luaL_Reg posteffectslib[]; // gfx.posteffects
extern const luaL_Reg quatlib[];
extern const luaL_Reg randomlib[];
extern const luaL_Reg compressionlib[];
extern const luaL_Reg testlib[];
extern const luaL_Reg text3dlib[]; // gfx.text3d
extern const luaL_Reg timelib[];
extern const luaL_Reg tomllib[];
@ -53,6 +54,7 @@ extern const luaL_Reg vec3lib[]; // vecn.cpp
extern const luaL_Reg vec4lib[]; // vecn.cpp
extern const luaL_Reg weatherlib[]; // gfx.weather
extern const luaL_Reg worldlib[];
extern const luaL_Reg xmllib[];
extern const luaL_Reg yamllib[];
// Components

View file

@ -543,13 +543,16 @@ static int l_destruct(lua::State* L) {
}
static int l_raycast(lua::State* L) {
auto& level = require_level();
const auto& level = require_level();
auto start = lua::tovec<3>(L, 1);
auto dir = lua::tovec<3>(L, 2);
auto maxDistance = lua::tonumber(L, 3);
bool includeNonSelectable = false;
blocks_agent::RaycastSettings raycast {};
std::set<blockid_t> filteredBlocks {};
raycast.filter = &filteredBlocks;
const int luaStackSize = lua::gettop(L);
if (luaStackSize >= 5) {
if (lua::istable(L, 5)) {
@ -568,21 +571,14 @@ static int l_raycast(lua::State* L) {
}
}
if (luaStackSize >= 6) {
includeNonSelectable = lua::toboolean(L, 6);
raycast.includeNonSelectable = lua::toboolean(L, 6);
}
glm::vec3 end;
glm::ivec3 normal;
glm::ivec3 iend;
if (auto voxel = blocks_agent::raycast(
*level.chunks,
start,
dir,
maxDistance,
end,
normal,
iend,
filteredBlocks,
includeNonSelectable
*level.chunks, start, dir, maxDistance, end, normal, iend, raycast
)) {
if (luaStackSize >= 4 && !lua::isnil(L, 4)) {
lua::pushvalue(L, 4);

View file

@ -17,6 +17,8 @@
#include "voxels/blocks_agent.hpp"
#include "window/Camera.hpp"
#include <set>
using namespace scripting;
static const EntityDef* require_entity_def(lua::State* L) {
@ -48,6 +50,13 @@ static int l_def_hitbox(lua::State* L) {
return 0;
}
static int l_def_solid(lua::State* L) {
if (auto def = require_entity_def(L)) {
return lua::pushboolean(L, def->solid);
}
return 0;
}
static int l_defs_count(lua::State* L) {
return lua::pushinteger(L, indices->entities.count());
}
@ -145,34 +154,59 @@ static int l_get_all_in_radius(lua::State* L) {
return 1;
}
static int l_raycast(lua::State* L) {
auto start = lua::tovec<3>(L, 1);
auto dir = lua::tovec<3>(L, 2);
auto maxDistance = lua::tonumber(L, 3);
auto ignoreEntityId = lua::tointeger(L, 4);
bool includeNonSelectable = false;
std::set<blockid_t> filteredBlocks {};
const int luaStackSize = lua::gettop(L);
if (luaStackSize >= 6) {
if (lua::istable(L, 6)) {
int addLen = lua::objlen(L, 6);
for (int i = 0; i < addLen; i++) {
lua::rawgeti(L, i + 1, 6);
auto blockName = std::string(lua::tostring(L, -1));
const Block* block = content->blocks.find(blockName);
if (block != nullptr) {
filteredBlocks.insert(block->rt.id);
}
lua::pop(L);
}
} else {
throw std::runtime_error("table expected for filter");
}
static int l_push_ray(
lua::State* L,
const std::optional<Entities::RaycastResult>& ray,
blockid_t block,
const glm::vec3& start,
const glm::vec3& dir,
const glm::vec3& end,
const glm::ivec3& iend,
const glm::ivec3& normal
) {
if (!ray.has_value() && block == BLOCK_VOID) {
return 0;
}
if (luaStackSize >= 7) {
includeNonSelectable = lua::toboolean(L, 6);
if (lua::gettop(L) >= 5 && lua::istable(L, 5)) {
lua::pushvalue(L, 5);
} else {
lua::createtable(L, 0, 6);
}
lua::pushvec3(L, ray ? (start + dir * ray->distance) : end);
lua::setfield(L, "endpoint");
lua::pushvec3(L, ray ? ray->normal : normal);
lua::setfield(L, "normal");
lua::pushnumber(L, glm::distance(start, end));
lua::setfield(L, "length");
lua::pushvec3(L, iend);
lua::setfield(L, "iendpoint");
lua::pushinteger(L, block);
lua::setfield(L, "block");
if (ray.has_value()) {
lua::pushinteger(L, ray->entity);
lua::setfield(L, "entity");
} else if (block != BLOCK_VOID) {
lua::pushinteger(L, block);
lua::setfield(L, "block");
}
return 1;
}
static int l_raycast(
lua::State* L,
const glm::vec3& start,
const glm::vec3& dir,
float maxDistance,
const blocks_agent::RaycastSettings& voxelRaycast,
const Entities::RaycastSettings& entityRaycast,
bool entities
) {
glm::vec3 end;
glm::ivec3 normal;
glm::ivec3 iend;
@ -187,61 +221,139 @@ static int l_raycast(lua::State* L) {
end,
normal,
iend,
filteredBlocks,
includeNonSelectable
voxelRaycast
)) {
maxDistance = glm::distance(start, end);
block = voxel->id;
}
if (auto ray =
level->entities->rayCast(start, dir, maxDistance, ignoreEntityId)) {
if (luaStackSize >= 5 && !lua::isnil(L, 5)) {
lua::pushvalue(L, 5);
auto ray =
entities
? level->entities->rayCast(start, dir, maxDistance, entityRaycast)
: std::nullopt;
return l_push_ray(L, ray, block, start, dir, end, iend, normal);
}
static void load_blocks_filter(lua::State* L, int idx, std::set<blockid_t>& dst) {
int count = lua::objlen(L, idx);
for (int i = 0; i < count; i++) {
lua::rawgeti(L, i + 1, idx);
if (lua::isnumber(L, -1)) {
dst.insert(lua::touinteger(L, -1));
} else {
lua::createtable(L, 0, 6);
auto blockName = std::string(lua::tostring(L, -1));
const Block* block = content->blocks.find(blockName);
if (block != nullptr) {
dst.insert(block->rt.id);
}
}
lua::pushvec3(L, start + dir * ray->distance);
lua::setfield(L, "endpoint");
lua::pushvec3(L, ray->normal);
lua::setfield(L, "normal");
lua::pushnumber(L, glm::distance(start, end));
lua::setfield(L, "length");
lua::pushvec3(L, iend);
lua::setfield(L, "iendpoint");
lua::pushinteger(L, block);
lua::setfield(L, "block");
lua::pushinteger(L, ray->entity);
lua::setfield(L, "entity");
return 1;
} else if (block != BLOCK_VOID) {
if (lua::gettop(L) >= 5 && !lua::isnil(L, 5)) {
lua::pushvalue(L, 5);
} else {
lua::createtable(L, 0, 5);
}
lua::pushvec3(L, end);
lua::setfield(L, "endpoint");
lua::pushvec3(L, normal);
lua::setfield(L, "normal");
lua::pushnumber(L, glm::distance(start, end));
lua::setfield(L, "length");
lua::pushvec3(L, iend);
lua::setfield(L, "iendpoint");
lua::pushinteger(L, block);
lua::setfield(L, "block");
return 1;
lua::pop(L);
}
return 0;
}
static void load_entities_filter(lua::State* L, int idx, std::set<entitydefid_t>& dst) {
int count = lua::objlen(L, idx);
for (int i = 0; i < count; i++) {
lua::rawgeti(L, i + 1, idx);
if (lua::isnumber(L, -1)) {
dst.insert(lua::touinteger(L, -1));
} else {
auto entityName = std::string(lua::tostring(L, -1));
const EntityDef* entity = content->entities.find(entityName);
if (entity != nullptr) {
dst.insert(entity->rt.id);
}
}
lua::pop(L);
}
}
static int l_world_raycast(lua::State* L) {
blocks_agent::RaycastSettings blocksRaycast {};
Entities::RaycastSettings entityRaycast {};
std::set<blockid_t> filteredBlocks {};
std::set<entitydefid_t> filteredEntities {};
glm::vec3 start, dir;
float maxDistance;
bool entities = true;
if (!lua::istable(L, 1)) {
throw std::runtime_error("invalid function overload");
}
lua::requirefield(L, "start", 1);
start = lua::tovec3(L, -1);
lua::requirefield(L, "dir", 1);
dir = lua::tovec3(L, -1);
lua::requirefield(L, "distance", 1);
maxDistance = lua::tonumber(L, -1);
lua::pop(L, 3);
if (lua::getfield(L, "entities", 1)) {
entities = lua::toboolean(L, -1);
lua::pop(L);
}
if (lua::getfield(L, "ignore_uid", 1)) {
entityRaycast.ignoredUid = lua::tointeger(L, -1);
lua::pop(L);
}
if (lua::getfield(L, "filter_entities")) {
load_entities_filter(L, -1, filteredEntities);
blocksRaycast.filter = &filteredEntities;
lua::pop(L);
}
if (lua::getfield(L, "entities_exclusion")) {
entityRaycast.entityFilterExcludeMode = lua::toboolean(L, -1);
lua::pop(L);
}
if (lua::getfield(L, "filter_blocks", 1)) {
load_blocks_filter(L, -1, filteredBlocks);
blocksRaycast.filter = &filteredBlocks;
lua::pop(L);
}
if (lua::getfield(L, "blocks_exclusion")) {
blocksRaycast.blocksFilterExcludeMode = lua::toboolean(L, -1);
lua::pop(L);
}
if (lua::getfield(L, "nonselect_blocks", 1)) {
blocksRaycast.includeNonSelectable = lua::toboolean(L, -1);
lua::pop(L);
}
return l_raycast(
L, start, dir, maxDistance, blocksRaycast, entityRaycast, entities
);
}
static int l_raycast(lua::State* L) {
blocks_agent::RaycastSettings blocksRaycast {};
Entities::RaycastSettings entityRaycast {};
std::set<blockid_t> filteredBlocks {};
const int luaStackSize = lua::gettop(L);
glm::vec3 start = lua::tovec<3>(L, 1);
glm::vec3 dir = lua::tovec<3>(L, 2);
float maxDistance = lua::tonumber(L, 3);
entityRaycast.ignoredUid = lua::tointeger(L, 4);
if (luaStackSize >= 6) {
if (lua::istable(L, 6)) {
load_blocks_filter(L, 6, filteredBlocks);
blocksRaycast.filter = &filteredBlocks;
} else {
throw std::runtime_error("table expected for filter");
}
}
if (luaStackSize >= 7) {
blocksRaycast.includeNonSelectable = lua::toboolean(L, 6);
}
return l_raycast(
L, start, dir, maxDistance, blocksRaycast, entityRaycast, true
);
}
static int l_reload_component(lua::State* L) {
@ -268,6 +380,7 @@ const luaL_Reg entitylib[] = {
{"def_index", lua::wrap<l_def_index>},
{"def_name", lua::wrap<l_def_name>},
{"def_hitbox", lua::wrap<l_def_hitbox>},
{"def_solid", lua::wrap<l_def_solid>},
{"get_def", lua::wrap<l_get_def>},
{"defs_count", lua::wrap<l_defs_count>},
{"spawn", lua::wrap<l_spawn>},
@ -278,6 +391,7 @@ const luaL_Reg entitylib[] = {
{"get_all_in_box", lua::wrap<l_get_all_in_box>},
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
{"raycast", lua::wrap<l_raycast>},
{"__world_raycast", lua::wrap<l_world_raycast>},
{"reload_component", lua::wrap<l_reload_component>},
{nullptr, nullptr}
};

View file

@ -10,7 +10,7 @@
#include "util/stringutil.hpp"
#include "api_lua.hpp"
#include "../lua_engine.hpp"
#include "logic/scripting/descriptors_manager.hpp"
#include "logic/scripting/io_descriptors.hpp"
namespace fs = std::filesystem;
using namespace scripting;
@ -263,13 +263,13 @@ static int l_open_descriptor(lua::State* L) {
std::vector<char> buffer;
if(wplusMode) {
int temp_descriptor = scripting::descriptors_manager::open_descriptor(path, false, true);
int temp_descriptor = io_descriptors::open_descriptor(path, false, true);
if (temp_descriptor == -1) {
throw std::runtime_error("failed to open descriptor for initial reading");
}
auto* in_stream = scripting::descriptors_manager::get_input(temp_descriptor);
auto* in_stream = io_descriptors::get_input(temp_descriptor);
in_stream->seekg(0, std::ios::end);
std::streamsize size = in_stream->tellg();
@ -278,17 +278,17 @@ static int l_open_descriptor(lua::State* L) {
buffer.resize(size);
in_stream->read(buffer.data(), size);
scripting::descriptors_manager::close(temp_descriptor);
io_descriptors::close(temp_descriptor);
}
int descriptor = scripting::descriptors_manager::open_descriptor(path, write, read);
int descriptor = io_descriptors::open_descriptor(path, write, read);
if(descriptor == -1) {
throw std::runtime_error("failed to open descriptor");
}
if(wplusMode) {
auto* out_stream = scripting::descriptors_manager::get_output(descriptor);
auto* out_stream = io_descriptors::get_output(descriptor);
out_stream->write(buffer.data(), buffer.size());
out_stream->flush();
}
@ -297,51 +297,31 @@ static int l_open_descriptor(lua::State* L) {
}
static int l_has_descriptor(lua::State* L) {
return lua::pushboolean(L, scripting::descriptors_manager::has_descriptor(lua::tointeger(L, 1)));
return lua::pushboolean(L, io_descriptors::has_descriptor(lua::tointeger(L, 1)));
}
static int l_read_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
if (!scripting::descriptors_manager::is_readable(descriptor)) {
throw std::runtime_error("descriptor is not readable");
}
int maxlen = lua::tointeger(L, 2);
auto* stream = scripting::descriptors_manager::get_input(descriptor);
auto& stream = io_descriptors::require_input(descriptor);
if (stream.eof()) {
stream.clear();
}
util::Buffer<char> buffer(maxlen);
stream->read(buffer.data(), maxlen);
std::streamsize read_len = stream->gcount();
stream.read(buffer.data(), maxlen);
std::streamsize read_len = stream.gcount();
return lua::create_bytearray(L, buffer.data(), read_len);
}
static int l_write_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
if (!scripting::descriptors_manager::is_writeable(descriptor)) {
throw std::runtime_error("descriptor is not writeable");
}
auto data = lua::bytearray_as_string(L, 2);
auto* stream = scripting::descriptors_manager::get_output(descriptor);
stream->write(data.data(), static_cast<std::streamsize>(data.size()));
if (!stream->good()) {
auto& stream = io_descriptors::require_output(descriptor);
stream.write(data.data(), static_cast<std::streamsize>(data.size()));
if (!stream.good()) {
throw std::runtime_error("failed to write to stream");
}
return 0;
@ -350,12 +330,13 @@ static int l_write_descriptor(lua::State* L) {
static int l_seek_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
std::string mode = lua::require_string(L, 2);
auto mode = lua::require_string(L, 2);
std::ios_base::seekdir dir;
auto position = lua::tointeger(L, 3);
switch (mode[0]) {
case 'b':
@ -371,45 +352,81 @@ static int l_seek_descriptor(lua::State* L) {
throw std::runtime_error("invalid seek mode");
}
auto* stream = scripting::descriptors_manager::get_output(descriptor);
if (io_descriptors::is_writeable(descriptor)) {
auto& stream = io_descriptors::require_output(descriptor);
stream.seekp(position, dir);
if (!stream.good()) {
throw std::runtime_error("failed to seek stream");
}
}
if (io_descriptors::is_readable(descriptor)) {
auto& stream = io_descriptors::require_input(descriptor);
stream.seekg(position, dir);
if (!stream.good()) {
throw std::runtime_error("failed to seek stream");
}
}
return 0;
}
stream->seekp(lua::tointeger(L, 3), dir);
static int l_tell_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!stream->good()) {
throw std::runtime_error("failed to seek stream");
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
return 0;
if (io_descriptors::is_writeable(descriptor)) {
auto& stream = io_descriptors::require_output(descriptor);
return lua::pushinteger(L, stream.tellp());
} else {
auto& stream = io_descriptors::require_input(descriptor);
return lua::pushinteger(L, stream.tellg());
}
}
static int l_flush_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
if (!scripting::descriptors_manager::is_writeable(descriptor)) {
if (!io_descriptors::is_writeable(descriptor)) {
throw std::runtime_error("descriptor is not writeable");
}
scripting::descriptors_manager::flush(descriptor);
io_descriptors::flush(descriptor);
return 0;
}
static int l_available_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
if (!io_descriptors::is_readable(descriptor)) {
throw std::runtime_error("descriptor is not readable");
}
return lua::pushinteger(L, io_descriptors::available(descriptor));
}
static int l_close_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
scripting::descriptors_manager::close(descriptor);
io_descriptors::close(descriptor);
return 0;
}
static int l_close_all_descriptors(lua::State* L) {
scripting::descriptors_manager::close_all_descriptors();
io_descriptors::close_all_descriptors();
return 0;
}
@ -442,7 +459,9 @@ const luaL_Reg filelib[] = {
{"__read_descriptor", lua::wrap<l_read_descriptor>},
{"__write_descriptor", lua::wrap<l_write_descriptor>},
{"__seek_descriptor", lua::wrap<l_seek_descriptor>},
{"__tell_descriptor", lua::wrap<l_tell_descriptor>},
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
{"__available_descriptor", lua::wrap<l_available_descriptor>},
{"__close_descriptor", lua::wrap<l_close_descriptor>},
{"__close_all_descriptors", lua::wrap<l_close_all_descriptors>},
{nullptr, nullptr}

View file

@ -420,15 +420,19 @@ static int p_get_data(UINode* node, lua::State* L) {
return 0;
}
static const std::string& request_node_id(const DocumentNode& docnode) {
std::string id = docnode.node->getId();
static const std::string& request_node_id(UiDocument& document, UINode& node) {
const std::string& id = node.getId();
if (id.empty()) {
id = "#" + std::to_string(
reinterpret_cast<std::ptrdiff_t>(docnode.node.get()));
node.setId( "#" + std::to_string(
reinterpret_cast<std::ptrdiff_t>(&node)));
document.pushIndices(node.shared_from_this());
return node.getId();
}
docnode.node->setId(std::move(id));
docnode.document->pushIndices(docnode.node);
return docnode.node->getId();
return id;
}
static const std::string& request_node_id(const DocumentNode& docnode) {
return request_node_id(*docnode.document, *docnode.node);
}
/// @brief Push UI-document node object to stack
@ -443,15 +447,14 @@ static int push_document_node(lua::State* L, const std::string& id) {
static int p_get_parent(UINode* node, lua::State* L) {
auto parent = node->getParent();
if (!parent) {
if (parent == nullptr) {
return 0;
}
auto docname = lua::require_string(L, 1);
auto element = lua::require_string(L, 2);
auto docnode = get_document_node_impl(L, docname, element);
const auto& id = request_node_id(docnode);
const auto& id = request_node_id(*docnode.document, *parent);
return push_document_node(L, id);
}
@ -1144,7 +1147,7 @@ static int l_gui_load_document(lua::State* L) {
auto document = documentPtr.get();
engine->requireAssets().store(std::move(documentPtr), alias);
scripting::on_ui_open(document, {args});
scripting::on_ui_open(*document, {args});
return 0;
}

View file

@ -237,6 +237,14 @@ static int l_get_all_data(lua::State* L, ItemStack& stack) {
return lua::pushvalue(L, stack.getFields());
}
static int l_set_all_data(lua::State* L, ItemStack& stack) {
if (!lua::istable(L, 3)) {
throw std::runtime_error("table expected as argument 1");
}
stack.setFields(lua::tovalue(L, 3), lua::toboolean(L, 4));
return 0;
}
static int l_has_data(lua::State* L, ItemStack& stack) {
auto key = lua::tostring(L, 3);
if (key == nullptr) {
@ -267,6 +275,7 @@ const luaL_Reg inventorylib[] = {
{"get_data", lua::wrap<wrap_slot<l_get_data>>},
{"set_data", lua::wrap<wrap_slot<l_set_data>>},
{"get_all_data", lua::wrap<wrap_slot<l_get_all_data>>},
{"set_all_data", lua::wrap<wrap_slot<l_set_all_data>>},
{"has_data", lua::wrap<wrap_slot<l_has_data>>},
{"create", lua::wrap<l_create>},
{"remove", lua::wrap<l_remove>},

View file

@ -1,4 +1,5 @@
#include "coders/json.hpp"
#include "coders/commons.hpp"
#include "api_lua.hpp"
static int l_json_stringify(lua::State* L) {
@ -12,8 +13,12 @@ static int l_json_stringify(lua::State* L) {
static int l_json_parse(lua::State* L) {
auto string = lua::require_string(L, 1);
auto element = json::parse("[string]", string);
return lua::pushvalue(L, element);
try {
auto element = json::parse("[string]", string);
return lua::pushvalue(L, element);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
const luaL_Reg jsonlib[] = {

View file

@ -0,0 +1,45 @@
#include "api_lua.hpp"
#include "engine/Engine.hpp"
#include "window/input.hpp"
#include <iostream>
using namespace scripting;
static int l_set_button_pressed(lua::State* L) {
int button = lua::touinteger(L, 1);
int x = lua::tointeger(L, 2);
int y = lua::tointeger(L, 3);
bool pressed = lua::toboolean(L, 4);
auto& input = engine->getInput();
input.simulateCursorPos(x, y);
input.simulateClick(button, pressed);
return 0;
}
static int l_set_key_pressed(lua::State* L) {
auto key = lua::require_string(L, 1);
bool pressed = lua::toboolean(L, 2);
auto& input = engine->getInput();
input.simulateKey(input_util::keycode_from(key), pressed);
return 0;
}
static int l_enter_text(lua::State* L) {
auto string = lua::require_wstring(L, 1);
auto& input = engine->getInput();
for (auto chr : string) {
input.simulateCodepoint(chr);
}
return 0;
}
const luaL_Reg testlib[] = {
{"set_button_pressed", lua::wrap<l_set_button_pressed>},
{"set_key_pressed", lua::wrap<l_set_key_pressed>},
{"enter_text", lua::wrap<l_enter_text>},
{nullptr, nullptr}
};

View file

@ -1,4 +1,5 @@
#include "coders/toml.hpp"
#include "coders/commons.hpp"
#include "api_lua.hpp"
using namespace scripting;
@ -16,8 +17,12 @@ static int l_toml_stringify(lua::State* L) {
static int l_toml_parse(lua::State* L) {
auto string = lua::require_string(L, 1);
auto element = toml::parse("[string]", string);
return lua::pushvalue(L, element);
try {
auto element = toml::parse("[string]", string);
return lua::pushvalue(L, element);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
const luaL_Reg tomllib[] = {

View file

@ -129,6 +129,9 @@ static int l_get_generator(lua::State* L) {
}
static int l_get_chunk_data(lua::State* L) {
if (level == nullptr) {
throw std::runtime_error("world is not open");
}
int x = static_cast<int>(lua::tointeger(L, 1));
int z = static_cast<int>(lua::tointeger(L, 2));
const auto& chunk = level->chunks->getChunk(x, z);
@ -175,7 +178,7 @@ static void integrate_chunk_client(Chunk& chunk) {
static int l_set_chunk_data(lua::State* L) {
if (level == nullptr) {
throw std::runtime_error("no open world");
throw std::runtime_error("world is not open");
}
int x = static_cast<int>(lua::tointeger(L, 1));
@ -201,7 +204,7 @@ static int l_set_chunk_data(lua::State* L) {
static int l_save_chunk_data(lua::State* L) {
if (level == nullptr) {
throw std::runtime_error("no open world");
throw std::runtime_error("world is not open");
}
int x = static_cast<int>(lua::tointeger(L, 1));

View file

@ -0,0 +1,114 @@
#include "coders/xml.hpp"
#include "coders/commons.hpp"
#include "api_lua.hpp"
static const char* TAG_ATTR = "#";
static int push_xml(lua::State* L, const xml::xmlelement& elem) {
if (elem.isText()) {
return lua::pushlstring(L, elem.getInnerText());
}
const auto& attrs = elem.getAttributes();
const auto& elems = elem.getElements();
const auto& tag = elem.getTag();
lua::createtable(L, elem.size(), attrs.size() + 1);
lua::pushlstring(L, tag);
lua::setfield(L, TAG_ATTR);
for (int i = 0; i < elems.size(); i++) {
push_xml(L, *elems[i]);
lua::rawseti(L, i + 1);
}
for (const auto& [key, value] : attrs) {
lua::pushlstring(L, value.getText());
lua::setfield(L, key);
}
return 1;
}
static std::unique_ptr<xml::xmlelement> toxml(lua::State* L) {
lua::getfield(L, TAG_ATTR);
auto tag = lua::require_lstring(L, -1);
lua::pop(L);
auto elem = std::make_unique<xml::xmlelement>(std::string(tag));
int length = lua::objlen(L, -1);
for (int i = 0; i < length; i++) {
lua::rawgeti(L, i + 1);
elem->add(toxml(L));
lua::pop(L);
}
lua::pushnil(L);
while (lua::next(L, -2)) {
lua::pushvalue(L, -2);
do {
if (!lua::isstring(L, -1)) {
break;
}
auto key = lua::require_lstring(L, -1);
if (key == "@") {
break;
}
auto value = lua::require_lstring(L, -2);
elem->set(std::string(key), std::string(value));
} while (false);
lua::pop(L, 2);
}
return elem;
}
static int l_tostring(lua::State* L) {
bool nice = lua::isboolean(L, 2) ? lua::toboolean(L, 2) : true;
lua::pushvalue(L, 1);
auto xml = toxml(L);
lua::pop(L);
return lua::pushstring(L, xml::stringify(*xml, nice));
}
static int l_parse(lua::State* L) {
auto string = lua::require_string(L, 1);
try {
auto document = xml::parse("[string]", string);
return push_xml(L, *document->getRoot());
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
static int l_parse_vcd(lua::State* L) {
auto string = lua::require_string(L, 1);
auto rootTag = lua::tostring(L, 2);
std::unique_ptr<xml::Document> document;
try {
document = xml::parse_vcm(
"[string]", string, rootTag ? rootTag : ""
);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
const auto& root = *document->getRoot();
if (rootTag != nullptr) {
return push_xml(L, root);
}
int rootElements = root.size();
if (rootElements != 1) {
throw std::runtime_error(
"one root element expected, got " + std::to_string(rootElements)
);
}
return push_xml(L, *root.getElements()[0]);
}
const luaL_Reg xmllib[] = {
{"tostring", lua::wrap<l_tostring>},
{"parse", lua::wrap<l_parse>},
{"parse_vcd", lua::wrap<l_parse_vcd>},
{nullptr, nullptr}
};

View file

@ -1,4 +1,5 @@
#include "coders/yaml.hpp"
#include "coders/commons.hpp"
#include "api_lua.hpp"
static int l_stringify(lua::State* L) {
@ -9,8 +10,12 @@ static int l_stringify(lua::State* L) {
static int l_parse(lua::State* L) {
auto string = lua::require_string(L, 1);
auto element = yaml::parse("[string]", string);
return lua::pushvalue(L, element);
try {
auto element = yaml::parse("[string]", string);
return lua::pushvalue(L, element);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
const luaL_Reg yamllib[] = {

View file

@ -19,6 +19,7 @@ namespace {
debug::Logger logger("lua-state");
lua::State* main_thread = nullptr;
bool headless_mode = false;
bool test_mode = false;
const std::unordered_map<std::string, std::string>* project_args;
}
@ -66,15 +67,16 @@ static void create_libs(State* L, StateType stateType) {
openlib(L, "vec2", vec2lib);
openlib(L, "vec3", vec3lib);
openlib(L, "vec4", vec4lib);
openlib(L, "xml", xmllib);
openlib(L, "yaml", yamllib);
openlib(L, "__vc_app", applib);
lua::getglobal(L, "__vc_app");
lua::setregistry(L, "app");
getglobal(L, "__vc_app");
setregistry(L, "app");
if (stateType == StateType::SCRIPT) {
lua::getregistry(L, "app");
lua::setglobal(L, "app");
getregistry(L, "app");
setglobal(L, "app");
}
if (stateType == StateType::BASE || stateType == StateType::SCRIPT) {
openlib(L, "assets", assetslib);
@ -99,6 +101,10 @@ static void create_libs(State* L, StateType stateType) {
openlib(L, "__transform", transformlib);
}
if (::test_mode) {
openlib(L, "test", testlib);
}
addfunc(L, "print", lua::wrap<l_print>);
addfunc(L, "crc32", lua::wrap<l_crc32>);
}
@ -191,6 +197,7 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) {
logger.info() << LUAJIT_VERSION;
headless_mode = params.headless;
test_mode = params.testMode;
project_args = &params.projectArgs;
main_thread = create_state(
paths, params.headless ? StateType::SCRIPT : StateType::BASE

View file

@ -81,16 +81,17 @@ union RGBA {
uint8_t arr[4];
uint32_t rgba;
};
static_assert(sizeof(RGBA) == 4);
static RGBA* get_at(const ImageData& data, uint index) {
if (index >= data.getWidth() * data.getHeight()) {
return nullptr;
}
static RGBA* get_at(const ImageData& data, size_t index) {
return reinterpret_cast<RGBA*>(data.getData() + index * sizeof(RGBA));
}
static RGBA* get_at(const ImageData& data, uint x, uint y) {
return get_at(data, y * data.getWidth() + x);
if (x >= data.getWidth() || y >= data.getHeight()) {
return nullptr;
}
return get_at(data, static_cast<size_t>(y) * data.getWidth() + x);
}
static RGBA* get_at(State* L, uint x, uint y) {

View file

@ -591,36 +591,46 @@ bool scripting::on_item_break_block(
);
}
void scripting::on_ui_open(
UiDocument* layout, std::vector<dv::value> args
) {
auto argsptr =
std::make_shared<std::vector<dv::value>>(std::move(args));
std::string name = layout->getId() + ".open";
lua::emit_event(lua::get_main_state(), name, [=](auto L) {
for (const auto& value : *argsptr) {
lua::pushvalue(L, value);
static void call_layout_event(const UiDocument& layout, const std::vector<dv::value>& args, const std::string& eventName) {
auto L = lua::get_main_state();
lua::pushenv(L, *layout.getEnvironment());
if (lua::getfield(L, eventName)) {
for (const auto& arg : args) {
lua::pushvalue(L, arg);
}
return argsptr->size();
});
lua::call_nothrow(L, args.size(), 0);
}
lua::pop(L);
}
void scripting::on_ui_open(
const UiDocument& layout, std::vector<dv::value> args
) {
if (layout.getScript().onopen) {
call_layout_event(layout, args, "on_open");
}
}
void scripting::on_ui_progress(
UiDocument* layout, int workDone, int workTotal
const UiDocument& layout, int workDone, int workTotal
) {
std::string name = layout->getId() + ".progress";
lua::emit_event(lua::get_main_state(), name, [=](auto L) {
lua::pushinteger(L, workDone);
lua::pushinteger(L, workTotal);
return 2;
});
if (layout.getScript().onprogress){
call_layout_event(layout, {workDone, workTotal}, "on_progress");
}
}
void scripting::on_ui_close(UiDocument* layout, Inventory* inventory) {
std::string name = layout->getId() + ".close";
lua::emit_event(lua::get_main_state(), name, [inventory](auto L) {
return lua::pushinteger(L, inventory ? inventory->getId() : 0);
});
void scripting::on_ui_close(const UiDocument& layout, Inventory* inventory) {
if (layout.getScript().onclose) {
call_layout_event(
layout, {inventory ? inventory->getId() : 0}, "on_close"
);
}
}
void scripting::on_ui_destroy(const UiDocument& layout) {
if (layout.getScript().ondestroy) {
call_layout_event(layout, {}, "on_destroy");
}
}
void scripting::on_scripts_loading() {
@ -734,9 +744,10 @@ void scripting::load_entity_component(
const io::path& file,
const std::string& fileName
) {
logger.info() << "script (component) " << file.string();
auto L = lua::get_main_state();
std::string src = io::read_string(file);
logger.info() << "script (component) " << file.string();
lua::loadbuffer(L, *env, src, fileName);
lua::store_in_registry(L, lua::CHUNKS_TABLE, name);
}
@ -805,11 +816,14 @@ void scripting::load_layout_script(
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "layout", file, fileName));
script.onopen = register_event(env, "on_open", prefix + ".open");
script.onprogress =
register_event(env, "on_progress", prefix + ".progress");
script.onclose = register_event(env, "on_close", prefix + ".close");
auto L = lua::get_main_state();
lua::pop(L, load_script(env, "layout", file, fileName));
lua::pushenv(L, env);
script.onopen = lua::hasfield(L, "on_open");
script.onprogress = lua::hasfield(L, "on_progress");
script.onclose = lua::hasfield(L, "on_close");
script.ondestroy = lua::hasfield(L, "on_destroy");
lua::pop(L);
}
void scripting::close() {

View file

@ -142,6 +142,7 @@ namespace scripting {
void on_entity_grounded(const Entity& entity, float force);
void on_entity_fall(const Entity& entity);
void on_entity_save(const Entity& entity);
void on_entity_player_set(const Entity& entity, int64_t pid);
void on_entities_update(int tps, int parts, int part);
void on_entities_physics_update(float delta);
void on_entities_render(float delta);
@ -152,13 +153,16 @@ namespace scripting {
void on_attacked(const Entity& entity, Player* player, entityid_t attacker);
void on_entity_used(const Entity& entity, Player* player);
/// @brief Called on UI view show
void on_ui_open(UiDocument* layout, std::vector<dv::value> args);
/// @brief Called on UI document show
void on_ui_open(const UiDocument& layout, std::vector<dv::value> args);
void on_ui_progress(UiDocument* layout, int workDone, int totalWork);
void on_ui_progress(const UiDocument& layout, int workDone, int totalWork);
/// @brief Called on UI view close
void on_ui_close(UiDocument* layout, Inventory* inventory);
/// @brief Called on UI document close
void on_ui_close(const UiDocument& layout, Inventory* inventory);
/// @brief Called on UI document destroy
void on_ui_destroy(const UiDocument& layout);
/// @brief Called on Content loading
void on_scripts_loading();

View file

@ -71,6 +71,7 @@ static void init_component_events(
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
funcsset.on_attacked = lua::hasfield(L, "on_attacked");
funcsset.on_used = lua::hasfield(L, "on_used");
funcsset.on_player_set = lua::hasfield(L, "on_player_set");
lua::pop(L);
}
@ -147,7 +148,12 @@ void scripting::on_entity_spawn(
lua::call(L, 1);
}
for (auto& component : components) {
create_component(L, -1, *component, args, saved);
try {
create_component(L, -1, *component, args, saved);
} catch (const std::runtime_error& err) {
logger.error() << "could not to initialize user component "
<< component->name << ": " << err.what();
}
}
for (auto& [packid, pack] : content->getPacks()) {
@ -165,6 +171,9 @@ static void process_entity_callback(
const std::string& name,
std::function<int(lua::State*)> args
) {
if (env == nullptr) {
return;
}
auto L = lua::get_main_state();
lua::pushenv(L, *env);
if (lua::hasfield(L, "__disabled")) {
@ -238,6 +247,15 @@ void scripting::on_entity_save(const Entity& entity) {
);
}
void scripting::on_entity_player_set(const Entity& entity, int64_t pid) {
process_entity_callback(
entity,
"on_player_set",
&EntityFuncsSet::on_player_set,
[pid](auto L) { return lua::pushinteger(L, pid); }
);
}
void scripting::on_sensor_enter(
const Entity& entity, size_t index, entityid_t oid
) {

View file

@ -166,8 +166,7 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
glm::vec3 start,
glm::vec3 dir,
float maxDistance,
entityid_t ignore,
bool solidOnly
const RaycastSettings& settings
) {
Ray ray(start, dir);
auto view = registry->view<EntityId, Transform, Rigidbody>();
@ -176,10 +175,18 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
glm::ivec3 foundNormal;
for (auto [entity, eid, transform, body] : view.each()) {
if (eid.uid == ignore || !body.enabled || (solidOnly && !eid.def.solid)) {
if (eid.uid == settings.ignoredUid || !body.enabled ||
(settings.solidEntitiesOnly && !eid.def.solid)) {
continue;
}
auto& hitbox = body.hitbox;
if (settings.entitiesFilter) {
bool matches = settings.entitiesFilter->find(eid.def.rt.id) !=
settings.entitiesFilter->end();
if (matches == settings.entityFilterExcludeMode) {
continue;
}
}
const auto& hitbox = body.hitbox;
glm::ivec3 normal;
double distance;
if (ray.intersectAABB(

View file

@ -5,6 +5,7 @@
#include <optional>
#include <vector>
#include "constants.hpp"
#include "physics/Hitbox.hpp"
#include "Transform.hpp"
#include "Rigidbody.hpp"
@ -46,12 +47,6 @@ class Entities final {
);
void preparePhysics(float delta);
public:
struct RaycastResult {
entityid_t entity;
glm::ivec3 normal;
float distance;
};
Entities(Level& level);
~Entities();
@ -82,20 +77,33 @@ public:
std::optional<Entity> get(entityid_t id);
struct RaycastSettings {
/// @param ignore Ignored entity uid
entityid_t ignoredUid;
/// @param solidEntitiesOnly If true, only entities with solid hitboxes
/// will be checked
bool solidEntitiesOnly = false;
bool entityFilterExcludeMode = false;
const std::set<entitydefid_t>* entitiesFilter = nullptr;
};
struct RaycastResult {
entityid_t entity;
glm::ivec3 normal;
float distance;
};
/// @brief Entities raycast. No blocks check included, use combined with
/// Chunks.rayCast
/// @param start Ray start
/// @param dir Ray direction normalized vector
/// @param maxDistance Max ray length
/// @param ignore Ignored entity ID
/// @param solidOnly If true, only entities with solid hitboxes will be checked
/// @return An optional structure containing entity, normal and distance
std::optional<RaycastResult> rayCast(
glm::vec3 start,
glm::vec3 dir,
float maxDistance,
entityid_t ignore = -1,
bool solidOnly = false
const RaycastSettings& settings
);
void loadEntities(dv::value map);

View file

@ -71,6 +71,9 @@ dv::value Entity::serialize() const {
if (!scripts.components.empty()) {
auto& compsMap = root.object("comps");
for (auto& comp : scripts.components) {
if (comp->env == nullptr) {
continue;
}
auto data =
scripting::get_component_value(comp->env, SAVED_DATA_VARNAME);
compsMap[comp->name] = data;
@ -113,6 +116,11 @@ int64_t Entity::getPlayer() const {
}
void Entity::setPlayer(int64_t id) {
registry.get<EntityId>(entity).player = id;
auto& eid = registry.get<EntityId>(entity);
if (eid.player == id) {
return;
}
eid.player = id;
scripting::on_entity_player_set(*this, id);
}

View file

@ -81,7 +81,7 @@ void Player::updateEntity() {
}
}
Hitbox* Player::getHitbox() {
Hitbox* Player::getHitbox() const {
if (auto entity = level.entities->get(eid)) {
return &entity->getRigidbody().hitbox;
}

View file

@ -135,7 +135,7 @@ public:
bool isCurrentCameraBuiltin() const;
Hitbox* getHitbox();
Hitbox* getHitbox() const;
void setSpawnPoint(glm::vec3 point);
glm::vec3 getSpawnPoint() const;

View file

@ -17,6 +17,7 @@ struct EntityFuncsSet {
bool on_aim_off;
bool on_attacked;
bool on_used;
bool on_player_set;
};
struct UserComponent {

View file

@ -88,12 +88,22 @@ void Skeleton::deserialize(const dv::value& root) {
void Skeleton::setConfig(std::shared_ptr<const SkeletonConfig> rigConfig) {
config = std::move(rigConfig);
const auto& bones = config->getBones();
pose.matrices.resize(
config->getBones().size(), glm::mat4(1.0f)
bones.size(), glm::mat4(1.0f)
);
calculated.matrices.resize(
config->getBones().size(), glm::mat4(1.0f)
bones.size(), glm::mat4(1.0f)
);
modelOverrides.resize(bones.size());
flags.resize(bones.size());
for (size_t i = 0; i < bones.size(); i++) {
flags[i].visible = true;
}
}
static void get_all_nodes(std::vector<Bone*>& nodes, Bone* node) {

View file

@ -13,6 +13,7 @@ dv::value NotePreset::serialize() const {
{"render_distance", renderDistance},
{"xray_opacity", xrayOpacity},
{"perspective", perspective},
{"font", font},
});
}
@ -27,4 +28,5 @@ void NotePreset::deserialize(const dv::value& src) {
src.at("render_distance").get(renderDistance);
src.at("xray_opacity").get(xrayOpacity);
src.at("perspective").get(perspective);
src.at("font").get(font);
}

View file

@ -28,6 +28,7 @@ struct NotePreset : public Serializable {
float renderDistance = 32.0f;
float xrayOpacity = 0.0f;
float perspective = 1.0f;
std::string font {};
dv::value serialize() const override;
void deserialize(const dv::value& src) override;

View file

@ -11,8 +11,10 @@
namespace util {
template<typename T>
inline T read_int_le(const uint8_t* src, size_t offset=0) {
return dataio::le2h(*(reinterpret_cast<const T*>(src) + offset));
inline T read_int_le(const uint8_t* src) {
T tmp;
std::memcpy(&tmp, src, sizeof(T));
return dataio::le2h(tmp);
}
/// @brief Simple heap implementation for memory-optimal sparse array of
@ -87,8 +89,7 @@ namespace util {
std::memset(found, 0, entrySize);
return found;
}
this->free(found);
return allocate(index, size);
free(found);
}
for (size_t i = 0; i < entriesCount; i++) {
auto data = buffer.data() + offset;
@ -110,9 +111,12 @@ namespace util {
entriesCount++;
auto data = buffer.data() + offset;
*reinterpret_cast<Tindex*>(data) = dataio::h2le(index);
Tindex indexTmp = dataio::h2le(index);
Tsize sizeTmp = dataio::h2le(size);
std::memcpy(data, &indexTmp, sizeof(Tindex));
data += sizeof(Tindex);
*reinterpret_cast<Tsize*>(data) = dataio::h2le(size);
std::memcpy(data, &sizeTmp, sizeof(Tsize));
return data + sizeof(Tsize);
}
@ -122,7 +126,7 @@ namespace util {
if (ptr == nullptr) {
return 0;
}
return read_int_le<Tsize>(ptr, -1);
return read_int_le<Tsize>(ptr - sizeof(Tsize));
}
/// @return number of entries
@ -147,7 +151,8 @@ namespace util {
ubyte* dst = out.data();
const ubyte* src = buffer.data();
*reinterpret_cast<Tindex*>(dst) = dataio::h2le(entriesCount);
Tindex countTmp = dataio::h2le(entriesCount);
std::memcpy(dst, &countTmp, sizeof(Tindex));
dst += sizeof(Tindex);
std::memcpy(dst, src, buffer.size());
@ -172,7 +177,7 @@ namespace util {
) : buffer(buffer), index(index), offset(offset) {}
Tsize size() const {
return read_int_le<Tsize>(buffer.data() + offset, -1);
return read_int_le<Tsize>(buffer.data() + offset - sizeof(Tsize));
}
bool operator!=(const const_iterator& o) const {

View file

@ -215,11 +215,10 @@ voxel* Chunks::rayCast(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> filter,
bool includeNonSelectable
const blocks_agent::RaycastSettings& settings
) const {
return blocks_agent::raycast(
*this, start, dir, maxDist, end, norm, iend, std::move(filter), includeNonSelectable
*this, start, dir, maxDist, end, norm, iend, settings
);
}

View file

@ -24,6 +24,10 @@ class VoxelsVolume;
template <int w, int h, int d> class StaticVoxelsVolume;
namespace blocks_agent {
struct RaycastSettings;
}
/// Player-centred chunks matrix
class Chunks {
LevelEvents* events;
@ -112,8 +116,7 @@ public:
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> filter = {},
bool includeNonSelectable = false
const blocks_agent::RaycastSettings& settings
) const;
glm::vec3 rayCastToObstacle(

View file

@ -223,8 +223,7 @@ static inline voxel* raycast_blocks(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> filter,
bool includeNonSelectable
const RaycastSettings& settings
) {
const auto& blocks = chunks.getContentIndices().blocks;
float px = start.x;
@ -240,9 +239,8 @@ static inline voxel* raycast_blocks(
int iy = std::floor(py);
int iz = std::floor(pz);
int stepx = (dx > 0.0f) ? 1 : -1;
int stepy = (dy > 0.0f) ? 1 : -1;
int stepz = (dz > 0.0f) ? 1 : -1;
glm::ivec3 step {
(dx > 0.0f) ? 1 : -1, (dy > 0.0f) ? 1 : -1, (dz > 0.0f) ? 1 : -1};
constexpr float infinity = std::numeric_limits<float>::infinity();
constexpr float epsilon = 1e-6f; // 0.000001
@ -250,31 +248,31 @@ static inline voxel* raycast_blocks(
float tyDelta = (std::fabs(dy) < epsilon) ? infinity : std::fabs(1.0f / dy);
float tzDelta = (std::fabs(dz) < epsilon) ? infinity : std::fabs(1.0f / dz);
float xdist = (stepx > 0) ? (ix + 1 - px) : (px - ix);
float ydist = (stepy > 0) ? (iy + 1 - py) : (py - iy);
float zdist = (stepz > 0) ? (iz + 1 - pz) : (pz - iz);
float xdist = (step.x > 0) ? (ix + 1 - px) : (px - ix);
float ydist = (step.y > 0) ? (iy + 1 - py) : (py - iy);
float zdist = (step.z > 0) ? (iz + 1 - pz) : (pz - iz);
float txMax = (txDelta < infinity) ? txDelta * xdist : infinity;
float tyMax = (tyDelta < infinity) ? tyDelta * ydist : infinity;
float tzMax = (tzDelta < infinity) ? tzDelta * zdist : infinity;
int steppedIndex = -1;
bool includeNonSelectable = settings.includeNonSelectable;
while (t <= maxDist) {
voxel* voxel = get(chunks, ix, iy, iz);
if (voxel == nullptr) {
return nullptr;
}
auto filter = settings.filter;
const auto& def = blocks.require(voxel->id);
if (voxel->id != BLOCK_AIR && (def.selectable || includeNonSelectable) &&
(filter.empty() || filter.find(def.rt.id) == filter.end())) {
end.x = px + t * dx;
end.y = py + t * dy;
end.z = pz + t * dz;
iend.x = ix;
iend.y = iy;
iend.z = iz;
if (voxel->id != BLOCK_AIR &&
(def.selectable || includeNonSelectable) &&
((filter == nullptr || filter->find(def.rt.id) == filter->end()) ==
settings.blocksFilterExcludeMode)) {
end = start + t * dir;
iend = {ix, iy, iz};
if (!def.rt.solid) {
const std::vector<AABB>& hitboxes =
@ -309,51 +307,43 @@ static inline voxel* raycast_blocks(
if (hit) return voxel;
} else {
iend.x = ix;
iend.y = iy;
iend.z = iz;
norm.x = norm.y = norm.z = 0;
if (steppedIndex == 0) norm.x = -stepx;
if (steppedIndex == 1) norm.y = -stepy;
if (steppedIndex == 2) norm.z = -stepz;
iend = {ix, iy, iz};
norm = {0, 0, 0};
if (steppedIndex != -1) {
norm[steppedIndex] = -step[steppedIndex];
}
return voxel;
}
}
if (txMax < tyMax) {
if (txMax < tzMax) {
ix += stepx;
ix += step.x;
t = txMax;
txMax += txDelta;
steppedIndex = 0;
} else {
iz += stepz;
iz += step.z;
t = tzMax;
tzMax += tzDelta;
steppedIndex = 2;
}
} else {
if (tyMax < tzMax) {
iy += stepy;
iy += step.y;
t = tyMax;
tyMax += tyDelta;
steppedIndex = 1;
} else {
iz += stepz;
iz += step.z;
t = tzMax;
tzMax += tzDelta;
steppedIndex = 2;
}
}
}
iend.x = ix;
iend.y = iy;
iend.z = iz;
end.x = px + t * dx;
end.y = py + t * dy;
end.z = pz + t * dz;
norm.x = norm.y = norm.z = 0;
iend = {ix, iy, iz};
end = start + t * dir;
norm = {0, 0, 0};
return nullptr;
}
@ -365,10 +355,9 @@ voxel* blocks_agent::raycast(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> filter,
bool includeNonSelectable
const RaycastSettings& settings
) {
return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable);
return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, settings);
}
voxel* blocks_agent::raycast(
@ -379,10 +368,9 @@ voxel* blocks_agent::raycast(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> filter,
bool includeNonSelectable
const RaycastSettings& settings
) {
return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable);
return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, settings);
}
// reduce nesting on next modification

View file

@ -412,6 +412,12 @@ inline void set_rotation(
}
}
struct RaycastSettings {
const std::set<blockid_t>* filter = nullptr;
bool blocksFilterExcludeMode = true;
bool includeNonSelectable = false;
};
/// @brief Cast ray to a selectable block with filter based on id.
/// @param chunks chunks matrix
/// @param start ray start position
@ -431,8 +437,7 @@ voxel* raycast(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> filter,
bool includeNonSelectable
const RaycastSettings& settings
);
/// @brief Cast ray to a selectable block with filter based on id.
@ -454,8 +459,7 @@ voxel* raycast(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> filter,
bool includeNonSelectable
const RaycastSettings& settings
);
void get_voxels(

View file

@ -33,6 +33,7 @@ static void init_gl_extensions_list() {
}
}
[[maybe_unused]]
static bool is_gl_extension_supported(const char *extension) {
if (!extension || !*extension) {
return false;
@ -40,6 +41,7 @@ static bool is_gl_extension_supported(const char *extension) {
return supported_gl_extensions.find(extension) != supported_gl_extensions.end();
}
#ifndef __APPLE__
static const char* gl_error_name(int error) {
switch (error) {
case GL_DEBUG_TYPE_ERROR: return "ERROR";
@ -80,6 +82,7 @@ static void GLAPIENTRY gl_message_callback(
logger.warning() << "GL:" << gl_error_name(type) << ":"
<< gl_severity_name(severity) << ": " << message;
}
#endif
static bool initialize_gl(int width, int height) {
glewExperimental = GL_TRUE;
@ -165,17 +168,127 @@ inline constexpr short MOUSE_KEYS_OFFSET = 1024;
static GLFWcursor* standard_cursors[static_cast<int>(CursorShape::LAST) + 1] = {};
class GLFWInput : public Input {
class BaseInput : public Input {
public:
std::vector<uint> codepoints;
int scroll = 0;
void onKeyCallback(int key, bool pressed) {
bool prevPressed = keys[key];
keys[key] = pressed;
frames[key] = currentFrame;
if (pressed && !prevPressed) {
const auto& callbacks = keyCallbacks.find(static_cast<Keycode>(key));
if (callbacks != keyCallbacks.end()) {
callbacks->second.notify();
}
}
if (pressed && key < MOUSE_KEYS_OFFSET) {
pressedKeys.push_back(static_cast<Keycode>(key));
}
}
void onMouseCallback(int button, bool pressed) {
int key = button + MOUSE_KEYS_OFFSET;
onKeyCallback(key, pressed);
}
bool isCursorLocked() const override {
return cursorLocked;
}
void setCursorPosition(double xpos, double ypos) {
if (cursorDrag) {
delta.x += xpos - cursor.x;
delta.y += ypos - cursor.y;
} else {
cursorDrag = true;
}
cursor.x = xpos;
cursor.y = ypos;
}
Bindings& getBindings() override {
return bindings;
}
const Bindings& getBindings() const override {
return bindings;
}
ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override {
return keyCallbacks[key].add(std::move(callback));
}
const std::vector<Keycode>& getPressedKeys() const override {
return pressedKeys;
}
const std::vector<uint>& getCodepoints() const override {
return codepoints;
}
CursorState getCursor() const override {
return {isCursorLocked(), cursor, delta};
}
int getScroll() override {
return scroll;
}
bool pressed(Keycode key) const override {
int keycode = static_cast<int>(key);
if (keycode < 0 || keycode >= KEYS_BUFFER_SIZE) {
return false;
}
return keys[keycode];
}
bool jpressed(Keycode keycode) const override {
return pressed(keycode) &&
frames[static_cast<int>(keycode)] == currentFrame;
}
bool clicked(Mousecode code) const override {
return pressed(
static_cast<Keycode>(MOUSE_KEYS_OFFSET + static_cast<int>(code))
);
}
bool jclicked(Mousecode code) const override {
return clicked(code) &&
frames[static_cast<int>(code) + MOUSE_KEYS_OFFSET] ==
currentFrame;
}
void simulateKey(Keycode key, bool pressed) override {
onKeyCallback(static_cast<int>(key), pressed);
}
void simulateClick(int button, bool pressed) override {
onMouseCallback(static_cast<int>(button), pressed);
}
void simulateCursorPos(double xpos, double ypos) override {
setCursorPosition(xpos, ypos);
}
void simulateCodepoint(uint codepoint) override {
codepoints.push_back(codepoint);
}
protected:
uint currentFrame = 0;
uint frames[KEYS_BUFFER_SIZE] {};
std::vector<uint> codepoints;
std::vector<Keycode> pressedKeys;
Bindings bindings;
bool keys[KEYS_BUFFER_SIZE] {};
std::unordered_map<Keycode, util::HandlersList<>> keyCallbacks;
bool cursorLocked = false;
bool cursorDrag = false;
glm::vec2 delta {};
glm::vec2 cursor {};
};
class GLFWInput : public BaseInput {
public:
GLFWInput(GLFWwindow* window)
: window(window) {
}
@ -225,26 +338,6 @@ public:
}
}
void onKeyCallback(int key, bool pressed) {
bool prevPressed = keys[key];
keys[key] = pressed;
frames[key] = currentFrame;
if (pressed && !prevPressed) {
const auto& callbacks = keyCallbacks.find(static_cast<Keycode>(key));
if (callbacks != keyCallbacks.end()) {
callbacks->second.notify();
}
}
if (pressed && key < MOUSE_KEYS_OFFSET) {
pressedKeys.push_back(static_cast<Keycode>(key));
}
}
void onMouseCallback(int button, bool pressed) {
int key = button + MOUSE_KEYS_OFFSET;
onKeyCallback(key, pressed);
}
const char* getClipboardText() const override {
return glfwGetClipboardString(window);
}
@ -253,41 +346,6 @@ public:
glfwSetClipboardString(window, text);
}
int getScroll() override {
return scroll;
}
bool pressed(Keycode key) const override {
int keycode = static_cast<int>(key);
if (keycode < 0 || keycode >= KEYS_BUFFER_SIZE) {
return false;
}
return keys[keycode];
}
bool jpressed(Keycode keycode) const override {
return pressed(keycode) &&
frames[static_cast<int>(keycode)] == currentFrame;
}
bool clicked(Mousecode code) const override {
return pressed(
static_cast<Keycode>(MOUSE_KEYS_OFFSET + static_cast<int>(code))
);
}
bool jclicked(Mousecode code) const override {
return clicked(code) &&
frames[static_cast<int>(code) + MOUSE_KEYS_OFFSET] ==
currentFrame;
}
CursorState getCursor() const override {
return {isCursorLocked(), cursor, delta};
}
bool isCursorLocked() const override {
return cursorLocked;
}
void toggleCursor() override {
cursorDrag = false;
if (cursorLocked) {
@ -298,43 +356,8 @@ public:
}
cursorLocked = !cursorLocked;
}
void setCursorPosition(double xpos, double ypos) {
if (cursorDrag) {
delta.x += xpos - cursor.x;
delta.y += ypos - cursor.y;
} else {
cursorDrag = true;
}
cursor.x = xpos;
cursor.y = ypos;
}
Bindings& getBindings() override {
return bindings;
}
const Bindings& getBindings() const override {
return bindings;
}
ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override {
return keyCallbacks[key].add(std::move(callback));
}
const std::vector<Keycode>& getPressedKeys() const override {
return pressedKeys;
}
const std::vector<uint>& getCodepoints() const override {
return codepoints;
}
private:
GLFWwindow* window;
bool cursorLocked = false;
bool cursorDrag = false;
glm::vec2 delta {};
glm::vec2 cursor {};
};
static_assert(!std::is_abstract<GLFWInput>());

View file

@ -274,6 +274,11 @@ public:
virtual bool clicked(Mousecode mousecode) const = 0;
virtual bool jclicked(Mousecode mousecode) const = 0;
virtual void simulateKey(Keycode key, bool pressed) = 0;
virtual void simulateClick(int button, bool pressed) = 0;
virtual void simulateCursorPos(double xpos, double ypos) = 0;
virtual void simulateCodepoint(uint codepoint) = 0;
virtual CursorState getCursor() const = 0;
virtual bool isCursorLocked() const = 0;