Merge branch 'MihailRis:main' into main

This commit is contained in:
Astazium 2026-08-04 17:15:26 +03:00 • committed by GitHub
commit 7536ac2736
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 2932 additions and 1028 deletions

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

@ -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

@ -16,6 +16,9 @@ inline constexpr bool ENGINE_DEBUG_BUILD = true;
inline const std::string ENGINE_VERSION_STRING = "0.32";
inline constexpr uint MAX_SUBPROCESSES = 1;
inline constexpr uint MAX_SUBPROCESS_DEPTH = 2;
/// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3;

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

@ -26,17 +26,21 @@ std::unique_ptr<Content> ContentBuilder::build() {
def.rt.tags.insert(tags.add(tag));
}
auto isSolid = [](const Variant& variant) {
return variant.model.type == BlockModelType::BLOCK ||
variant.culling == CullingMode::OPTIONAL;
};
if (def.variants) {
for (auto& variant : def.variants->variants) {
variant.rt.solid =
variant.model.type == BlockModelType::BLOCK ||
def.explictlySolid;
variant.rt.solid = isSolid(variant) || def.explictlySolid;
}
def.defaults = def.variants->variants.at(0);
} else {
def.defaults.rt.solid =
def.defaults.model.type == BlockModelType::BLOCK ||
def.explictlySolid;
def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid;
}
if (def.material.empty()) {
defaults.at("block-material").get(def.material);
}
const float EPSILON = 0.01f;

View file

@ -506,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();
@ -516,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,
@ -524,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

@ -89,6 +89,7 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
root.at("skeleton-name").get(def.skeletonName);
root.at("material").get(def.material);
root.at("blocking").get(def.blocking);
root.at("selectable").get(def.selectable);
root.at("solid").get(def.solid);
root.at("mass").get(def.mass);
root.at("elasticity").get(def.elasticity);

View file

@ -9,6 +9,10 @@
#include <stdexcept>
#include <unordered_map>
#ifdef VC_ENABLE_REFLECTION
#include "util/EnumMetadata.hpp"
#endif
namespace util {
template<class T> class Buffer;
}
@ -70,6 +74,14 @@ namespace dv {
return ptr != nullptr;
}
inline value* operator->() noexcept {
return ptr;
}
inline const value* operator->() const noexcept {
return ptr;
}
inline value& operator*() noexcept {
return *ptr;
}
@ -78,6 +90,11 @@ namespace dv {
return *ptr;
}
#ifdef VC_ENABLE_REFLECTION
template <typename T>
bool get(T& dst, const util::EnumMetadata<T>& mt) const;
#endif
bool get(std::string& dst) const;
bool get(bool& dst) const;
bool get(char& dst) const;
@ -552,6 +569,17 @@ namespace dv {
}
return false;
}
#ifdef VC_ENABLE_REFLECTION
template <typename T>
inline bool optionalvalue::get(T& dst, const util::EnumMetadata<T>& mt) const {
if (ptr) {
return mt.getItem(ptr->asString(), dst);
}
return false;
}
#endif
inline bool optionalvalue::get(std::string& dst) const {
if (ptr) {
dst = ptr->asString();

View file

@ -0,0 +1,59 @@
#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) {
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,29 @@
#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);
void tick();
void loadProjectClientScript();
void terminate(std::string_view reason);
void onScreenChange(const std::string& name, bool show);
bool isFinished() const;
private:
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,22 +1,19 @@
#pragma once
#include "interfaces/Serializable.hpp"
#include "io/path.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";
static inline std::string RECORD_AUDIO = "record-audio";
static inline std::string WRITE_TO_USER = "write-to-user";
static inline std::string SUB_INSTANCES = "sub-instances";
std::set<std::string> permissions;
@ -26,16 +23,12 @@ struct Permissions {
struct Project : Serializable {
std::string name;
std::string title;
io::path path;
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

@ -12,7 +12,9 @@ struct CoreParameters {
std::filesystem::path userFolder = ".";
std::filesystem::path scriptFile;
std::filesystem::path projectFolder;
std::filesystem::path logFile;
std::string debugServerString;
int tps = 20;
int subProcessDepth = 0;
std::unordered_map<std::string, std::string> projectArgs;
};

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);
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) {
@ -382,13 +380,14 @@ void Engine::loadAssets() {
void Engine::loadProject() {
io::path projectFile = "project:project.toml";
project = std::make_unique<Project>();
project->path = "project:";
project->deserialize(io::read_object(projectFile));
logger.info() << "loaded project " << util::quote(project->name);
}
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 +396,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();
}
}
@ -414,6 +413,7 @@ void Engine::onWorldClosed() {
}
void Engine::quit() {
logger.info() << "quitSignal set to true";
quitSignal = true;
if (!isHeadless()) {
window->setShouldClose(true);

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() && !engine.isQuitSignal()) {
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

@ -30,6 +30,7 @@ void ContentGfxCache::refreshVariant(
uint8_t variantIndex,
const Atlas& atlas
) {
std::unordered_map<std::string, std::string> optionalTextures;
bool denseRender = settings.denseRender.get();
for (uint side = 0; side < 6; side++) {
std::string tex = variant.textureFaces[side];
@ -43,6 +44,7 @@ void ContentGfxCache::refreshVariant(
texOpaque = tex;
} else if (variant.culling == CullingMode::OPTIONAL && !denseRender) {
tex = texOpaque;
optionalTextures[variant.textureFaces[side]] = texOpaque;
}
size_t index = getRegionIndex(def.rt.id, variantIndex, side, false);
sideregions[index] = atlas.get(tex);
@ -56,7 +58,12 @@ void ContentGfxCache::refreshVariant(
if (pos == std::string::npos) {
continue;
}
if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) {
std::string texture = mesh.texture.substr(pos+1);
const auto& found = optionalTextures.find(texture);
if (found != optionalTextures.end()) {
texture = found->second;
}
if (auto region = atlas.getIf(texture)) {
for (auto& vertex : mesh.vertices) {
vertex.uv = region->apply(vertex.uv);
}

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

@ -4,7 +4,7 @@
#include <GL/glew.h>
Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
: Texture(0, width, height)
: Texture(0U, width, height, imageFormat)
{
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
@ -14,7 +14,6 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
uint format = gl::to_glenum(imageFormat);
for (uint face = 0; face < 6; face++) {
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
@ -37,3 +36,23 @@ void Cubemap::bind() const {
void Cubemap::unbind() const {
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}
void Cubemap::reload(const ubyte* data, uint width, uint height) {
this->width = width;
this->height = height;
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
for (uint face = 0; face < 6; face++) {
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
0,
format,
width,
height,
0,
format,
GL_UNSIGNED_BYTE,
data
);
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}

View file

@ -9,4 +9,5 @@ public:
virtual void bind() const override;
virtual void unbind() const override;
virtual void reload(const ubyte* data, uint w, uint h) override;
};

View file

@ -74,6 +74,18 @@ DrawContext::~DrawContext() {
if (lineWidth != parent->lineWidth) {
glLineWidth(parent->lineWidth);
}
for (int i = 0; i < textures.size(); i++) {
auto texture = parent->textures[i];
if (textures[i] != texture) {
glActiveTexture(GL_TEXTURE0 + i);
if (texture == nullptr) {
glBindTexture(GL_TEXTURE_2D, 0);
} else {
texture->bind();
}
}
}
}
const glm::uvec2& DrawContext::getViewport() const {
@ -154,3 +166,13 @@ void DrawContext::setLineWidth(float width) {
lineWidth = width;
glLineWidth(width);
}
void DrawContext::useTexture(int target, const Bindable* texture) {
glActiveTexture(GL_TEXTURE0 + target);
if (texture == nullptr) {
glBindTexture(GL_TEXTURE_2D, 0);
} else {
texture->bind();
}
textures.at(target) = texture;
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
@ -23,6 +24,7 @@ class DrawContext {
BlendMode blendMode = BlendMode::normal;
int scissorsCount = 0;
float lineWidth = 1.0f;
std::array<const Bindable*, advanced_pipeline::TARGETS_COUNT> textures {};
public:
DrawContext(
const DrawContext* parent,
@ -44,4 +46,5 @@ public:
void setBlendMode(BlendMode mode);
void setScissors(const glm::vec4& area);
void setLineWidth(float width);
void useTexture(int target, const Bindable* texture);
};

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

@ -1,14 +1,14 @@
#include "Framebuffer.hpp"
#include <GL/glew.h>
#include "Texture.hpp"
#include "debug/Logger.hpp"
static debug::Logger logger("gl-framebuffer");
Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
: fbo(fbo), depth(depth), texture(std::move(texture))
{
: fbo(fbo), depth(depth), texture(std::move(texture)) {
if (this->texture) {
width = this->texture->getWidth();
height = this->texture->getHeight();
@ -18,35 +18,55 @@ Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
}
}
static std::unique_ptr<Texture> create_texture(int width, int height, int format) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);
static std::unique_ptr<Texture> create_texture(
int width, int height, bool alpha
) {
GLenum glformat = alpha ? GL_RGBA : GL_RGB;
GLuint textureid;
glGenTextures(1, &textureid);
glBindTexture(GL_TEXTURE_2D, textureid);
glTexImage2D(
GL_TEXTURE_2D,
0,
glformat,
width,
height,
0,
glformat,
GL_UNSIGNED_BYTE,
nullptr
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
return std::make_unique<Texture>(tex, width, height);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid, 0
);
return std::make_unique<Texture>(
textureid,
width,
height,
alpha ? ImageFormat::RGBA8888 : ImageFormat::RGB888
);
}
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
: width(width), height(height)
{
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
: width(width), height(height) {
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
format = alpha ? GL_RGBA : GL_RGB;
// Setup color attachment (texture)
texture = create_texture(width, height, format);
texture = create_texture(width, height, alpha);
// Setup depth attachment
glGenRenderbuffers(1, &depth);
glBindRenderbuffer(GL_RENDERBUFFER, depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth
);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
logger.error() << "framebuffer is not complete!";
@ -60,11 +80,15 @@ Framebuffer::~Framebuffer() {
glDeleteRenderbuffers(1, &depth);
}
void Framebuffer::bind() {
void Framebuffer::setTexture(std::unique_ptr<Texture> texture) {
this->texture = std::move(texture);
}
void Framebuffer::bind() const {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
}
void Framebuffer::unbind() {
void Framebuffer::unbind() const {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@ -81,7 +105,7 @@ void Framebuffer::resize(uint width, uint height) {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
texture = create_texture(width, height, format);
texture->resize(width, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@ -102,5 +126,5 @@ uint Framebuffer::getHeight() const {
}
uint Framebuffer::getFBO() const {
return fbo;
return fbo;
}

View file

@ -12,18 +12,19 @@ class Framebuffer : public Bindable {
uint depth;
uint width;
uint height;
uint format;
std::shared_ptr<Texture> texture;
public:
Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture);
Framebuffer(uint width, uint height, bool alpha=false);
~Framebuffer();
void setTexture(std::unique_ptr<Texture> texture);
/// @brief Use framebuffer
void bind() override;
void bind() const override;
/// @brief Stop using framebuffer
void unbind() override;
void unbind() const override;
/// @brief Update framebuffer texture size
/// @param width new width

View file

@ -108,7 +108,7 @@ GBuffer::~GBuffer() {
glDeleteFramebuffers(1, &ssaoFbo);
}
void GBuffer::bind() {
void GBuffer::bind() const {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glClear(GL_COLOR_BUFFER_BIT);
}
@ -117,7 +117,7 @@ void GBuffer::bindSSAO() const {
glBindFramebuffer(GL_FRAMEBUFFER, ssaoFbo);
}
void GBuffer::unbind() {
void GBuffer::unbind() const {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

View file

@ -9,9 +9,9 @@ public:
GBuffer(uint width, uint height);
~GBuffer() override;
void bind() override;
void bind() const override;
void bindSSAO() const;
void unbind() override;
void unbind() const override;
void bindBuffers() const;
void bindSSAOBuffer() const;

View file

@ -7,8 +7,8 @@
uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it
Texture::Texture(uint id, uint width, uint height)
: id(id), width(width), height(height) {
Texture::Texture(uint id, uint width, uint height, ImageFormat imageFormat)
: id(id), width(width), height(height), format(gl::to_glenum(imageFormat)) {
}
Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFormat)
@ -17,7 +17,7 @@ Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFo
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLenum format = gl::to_glenum(imageFormat);
format = gl::to_glenum(imageFormat);
glTexImage2D(
GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data)
@ -41,23 +41,27 @@ void Texture::unbind() const {
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::resize(uint width, uint height) {
reload(nullptr, width, height);
}
void Texture::reload(const ImageData& image) {
width = image.getWidth();
height = image.getHeight();
reload(image.getData(), width, height);
}
void Texture::reload(const ubyte* data, uint width, uint height) {
this->width = width;
this->height = height;
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) {
glBindTexture(GL_TEXTURE_2D, id);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData());
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, format, GL_UNSIGNED_BYTE, image.getData());
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}

View file

@ -1,29 +1,32 @@
#pragma once
#include "typedefs.hpp"
#include "maths/UVRegion.hpp"
#include "ImageData.hpp"
#include <memory>
class Texture {
#include "ImageData.hpp"
#include "commons.hpp"
#include "maths/UVRegion.hpp"
#include "typedefs.hpp"
class Texture : public Bindable {
protected:
uint id;
uint width;
uint height;
uint format;
public:
Texture(uint id, uint width, uint height);
Texture(uint id, uint width, uint height, ImageFormat imageFormat);
Texture(const ubyte* data, uint width, uint height, ImageFormat format);
virtual ~Texture();
virtual void bind() const;
virtual void unbind() const;
void reload(const ubyte* data, uint w, uint h);
virtual void bind() const override;
virtual void unbind() const override;
virtual void reload(const ubyte* data, uint w, uint h);
void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h);
void setNearestFilter();
void reload(const ImageData& image);
virtual void resize(uint w, uint h);
void setMipMapping(bool flag, bool pixelated);

View file

@ -59,6 +59,8 @@ namespace advanced_pipeline {
inline constexpr int TARGET_SSAO = 5;
inline constexpr int TARGET_SHADOWS0 = 6;
inline constexpr int TARGET_SHADOWS1 = 7;
inline constexpr int TARGETS_COUNT = 8;
}
VC_ENUM_METADATA(CursorShape)
@ -85,6 +87,6 @@ class Bindable {
public:
virtual ~Bindable() = default;
virtual void bind() = 0;
virtual void unbind() = 0;
virtual void bind() const = 0;
virtual void unbind() const = 0;
};

View file

@ -538,7 +538,6 @@ void BlocksRenderer::render(
break;
}
case BlockModelType::CUSTOM: {
if (!denseRender)
blockCustomModel(
{x, y, z},
def,

View file

@ -34,4 +34,7 @@ void HandsRenderer::render(const Camera& camera) {
glm::vec3(),
glm::vec3(1.0f)
);
modelBatch.render();
modelBatch.setLightsOffset(glm::vec3());
}

View file

@ -1,91 +1,64 @@
#include "Skybox.hpp"
#include "assets/Assets.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/DrawContext.hpp"
#include "window/Window.hpp"
#include "window/Camera.hpp"
#include "maths/UVRegion.hpp"
#include <GL/glew.h>
#include <cmath>
#include <iostream>
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <limits>
#include "assets/Assets.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "maths/UVRegion.hpp"
#include "window/Camera.hpp"
#include "window/Window.hpp"
using namespace advanced_pipeline;
const int STARS_COUNT = 3000;
const int STARS_SEED = 632;
Skybox::Skybox(uint size, Shader& shader)
: size(size),
shader(shader),
batch3d(std::make_unique<Batch3D>(4096))
{
auto cubemap = std::make_unique<Cubemap>(size, size, ImageFormat::RGB888);
Skybox::Skybox(uint size, const Assets& assets)
: size(size),
assets(assets),
shader(assets.require<Shader>("skybox_gen")),
batch3d(std::make_unique<Batch3D>(4096)) {
uint fboid;
glGenFramebuffers(1, &fboid);
fbo = std::make_unique<Framebuffer>(fboid, 0, std::move(cubemap));
SkyboxVertex vertices[]{
SkyboxVertex vertices[] {
{{-1.0f, -1.0f}},
{{-1.0f, 1.0f}},
{{1.0f, 1.0f}},
{{-1.0f, -1.0f}},
{{1.0f, 1.0f}},
{{1.0f, -1.0f}}
};
{{1.0f, -1.0f}}};
mesh = std::make_unique<Mesh<SkyboxVertex>>(vertices, 6);
sprites.push_back(SkySprite {
"misc/moon",
glm::pi<float>() * 0.5f,
4.0f,
false,
glm::pi<float>() * 0.25f,
});
sprites.push_back(SkySprite {
"misc/moon_flare",
glm::pi<float>() * 0.5f,
0.5f,
false,
glm::pi<float>() * 0.25f,
});
sprites.push_back(SkySprite {
"misc/sun",
glm::pi<float>() * 1.5f,
4.0f,
true,
glm::pi<float>() * 0.25f,
});
setMode(mode);
}
Skybox::~Skybox() = default;
void Skybox::drawBackground(
const Camera& camera, const Assets& assets, int width, int height
) {
void Skybox::drawBackground(const Camera& camera, int width, int height) {
auto backShader = assets.get<Shader>("background");
backShader->use();
backShader->uniformMatrix("u_view", camera.getView(false));
backShader->uniform1f(
"u_zoom", camera.zoom * camera.getFov() / glm::half_pi<float>()
);
backShader->uniform1f("u_ar", float(width)/float(height));
backShader->uniform1f("u_ar", float(width) / float(height));
backShader->uniform1i("u_skybox", 1);
bind();
auto texture = fbo->getTexture();
texture->bind();
mesh->draw();
unbind();
texture->unbind();
}
void Skybox::drawStars(float angle, float opacity) {
@ -93,9 +66,7 @@ void Skybox::drawStars(float angle, float opacity) {
random.setSeed(STARS_SEED);
glm::mat4 rotation = glm::rotate(
glm::mat4(1.0f),
-angle + glm::pi<float>() * 0.5f,
glm::vec3(0, 0, -1)
glm::mat4(1.0f), -angle + glm::pi<float>() * 0.5f, glm::vec3(0, 0, -1)
);
rotation = glm::rotate(rotation, sunAltitude, glm::vec3(1, 0, 0));
@ -108,41 +79,21 @@ void Skybox::drawStars(float angle, float opacity) {
glm::vec3 pos = glm::vec4(rx, ry, rz, 1) * rotation;
float sopacity = random.randFloat();
if (pos.y < 0.0f)
continue;
if (pos.y < 0.0f) continue;
sopacity *= (0.2f + std::sqrt(std::cos(angle)) * 0.5f) - 0.05f;
glm::vec4 tint (1,1,1, sopacity * opacity);
glm::vec4 tint(1, 1, 1, sopacity * opacity);
batch3d->point(pos * depth, tint);
}
batch3d->flushPoints();
}
void Skybox::draw(
const DrawContext& pctx,
const Camera& camera,
const Assets& assets,
void Skybox::drawSkySprites(
float daytime,
float fog)
{
const glm::uvec2& viewport = pctx.getViewport();
glActiveTexture(GL_TEXTURE0);
drawBackground(camera, assets, viewport.x, viewport.y);
DrawContext ctx = pctx.sub();
ctx.setBlendMode(BlendMode::addition);
auto shader = assets.get<Shader>("ui3d");
shader->use();
shader->uniformMatrix("u_projview", camera.getProjView(false));
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin();
float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f - fog, 7.0f);
float angle,
float opacity,
const std::vector<SkySprite>& sprites
) {
float depthScale = 2e3;
for (auto& sprite : sprites) {
batch3d->texture(assets.get<Texture>(sprite.texture));
@ -159,39 +110,124 @@ void Skybox::draw(
glm::vec3 pos = glm::vec4(0, distance, 0, 1) * rotation;
glm::vec3 up = glm::vec4(depthScale, 0, 0, 1) * rotation;
glm::vec3 right = glm::vec4(0, 0, depthScale, 1) * rotation;
glm::vec4 tint (1,1,1, opacity);
glm::vec4 tint(1, 1, 1, opacity);
if (!sprite.emissive) {
tint *= 0.6f + std::cos(angle)*0.4;
tint *= 0.6f + std::cos(angle) * 0.4;
}
batch3d->sprite(pos, right,
up, 1, 1, UVRegion(), tint);
batch3d->sprite(pos, right, up, 1, 1, UVRegion(), tint);
}
batch3d->flush();
drawStars(angle, opacity);
}
void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality) {
void Skybox::draw(
const Environment& environment,
const DrawContext& pctx,
const Camera& camera,
float daytime,
float fog
) {
const glm::uvec2& viewport = pctx.getViewport();
drawBackground(camera, viewport.x, viewport.y);
float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f - fog, 7.0f);
DrawContext ctx = pctx.sub();
ctx.setBlendMode(BlendMode::addition);
if (environment.sky.sprites.empty() && !environment.sky.stars) {
return;
}
auto shader = assets.get<Shader>("ui3d");
shader->use();
shader->uniformMatrix("u_projview", camera.getProjView(false));
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin();
if (!environment.sky.sprites.empty()) {
drawSkySprites(daytime, angle, opacity, environment.sky.sprites);
}
if (environment.sky.stars) {
drawStars(angle, opacity);
}
}
void Skybox::setMode(SkyMode mode) {
if (this->mode == mode && fbo) {
return;
}
this->mode = mode;
uint size = mode == SkyMode::BOX ? this->size : 1U;
prevT = std::numeric_limits<float>().min();
uint fboid;
glGenFramebuffers(1, &fboid);
fbo = std::make_unique<Framebuffer>(
fboid,
false,
std::make_unique<Cubemap>(size, size, ImageFormat::RGB888)
);
}
void Skybox::refresh(
const Environment& environment,
const DrawContext& pctx,
float dayTime,
float mie,
const glm::vec3& tint,
const glm::vec3& hightlight,
uint quality
) {
setMode(environment.sky.mode);
if (mode == SkyMode::NONE) {
return;
} else if (mode == SkyMode::SOLID) {
DrawContext ctx = pctx.sub();
ctx.setDepthMask(false);
ctx.setDepthTest(false);
ctx.setFramebuffer(fbo.get());
ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
assert(cubemap != nullptr);
for (int face = 0; face < 6; face++) {
glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(),
0
);
glClearColor(tint.r, tint.g, tint.b, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
return;
}
frameid++;
float dayTime = t;
DrawContext ctx = pctx.sub();
ctx.setDepthMask(false);
ctx.setDepthTest(false);
ctx.setFramebuffer(fbo.get());
ctx.setViewport({size, size});
ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
assert(cubemap != nullptr);
ready = true;
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
cubemap->bind();
shader.use();
t *= glm::two_pi<float>();
float t = dayTime * glm::two_pi<float>();
lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f));
float sunAngle = glm::radians((t / glm::two_pi<float>() - 0.25f) * 360.0f);
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) * glm::radians(sunAltitude);
float sunAngle = glm::radians((dayTime - 0.25f) * 360.0f);
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) *
glm::radians(sunAltitude);
float y = sunAngle - glm::pi<float>() * 0.5f;
float z = glm::radians(0.0f);
rotation = glm::rotate(glm::mat4(1.0f), y, glm::vec3(0, 1, 0));
@ -199,6 +235,7 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
rotation = glm::rotate(rotation, z, glm::vec3(0, 0, 1));
lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1));
shader.use();
shader.uniform1i("u_quality", quality);
shader.uniform1f("u_mie", mie);
shader.uniform3f("u_tint", tint);
@ -207,23 +244,22 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
shader.uniform3f("u_lightDir", lightDir);
shader.uniform1f("u_dayTime", dayTime);
if (glm::abs(mie-prevMie) + glm::abs(t-prevT) + glm::abs(prevHighlight.r - hightlight.r) >= 0.01) {
if (glm::abs(mie - prevMie) + glm::abs(t - prevT) +
glm::abs(prevHighlight.r - hightlight.r) >=
0.01) {
for (uint face = 0; face < 6; face++) {
refreshFace(face, cubemap);
refreshFace(face, *cubemap);
}
} else {
uint face = frameid % 6;
refreshFace(face, cubemap);
refreshFace(face, *cubemap);
}
prevMie = mie;
prevT = t;
prevHighlight = hightlight;
cubemap->unbind();
glActiveTexture(GL_TEXTURE0);
}
void Skybox::refreshFace(uint face, Cubemap* cubemap) {
void Skybox::refreshFace(uint face, Cubemap& cubemap) {
const glm::vec3 xaxs[] = {
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, 1.0f},
@ -256,7 +292,7 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(),
cubemap.getId(),
0
);
shader.uniform3f("u_xaxis", xaxs[face]);
@ -265,14 +301,6 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
mesh->draw();
}
void Skybox::bind() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
fbo->getTexture()->bind();
glActiveTexture(GL_TEXTURE0);
}
void Skybox::unbind() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
fbo->getTexture()->unbind();
glActiveTexture(GL_TEXTURE0);
const Cubemap* Skybox::getCubemap() const {
return dynamic_cast<const Cubemap*>(fbo->getTexture());
}

View file

@ -1,14 +1,17 @@
#pragma once
#include <glm/glm.hpp>
#include <memory>
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include "typedefs.hpp"
#include "maths/fastmaths.hpp"
#include "graphics/core/MeshData.hpp"
template<typename VertexStructure> class Mesh;
#include "graphics/core/MeshData.hpp"
#include "maths/fastmaths.hpp"
#include "typedefs.hpp"
#include "world/Environment.hpp"
template <typename VertexStructure>
class Mesh;
class Shader;
class Assets;
class Camera;
@ -21,29 +24,20 @@ struct SkyboxVertex {
glm::vec2 position;
static constexpr VertexAttribute ATTRIBUTES[] {
{VertexAttribute::Type::FLOAT, false, 2},
{{}, 0}};
};
struct SkySprite {
std::string texture;
float phase;
float distance;
bool emissive;
float altitude;
{VertexAttribute::Type::FLOAT, false, 2}, {{}, 0}};
};
class Skybox {
std::unique_ptr<Framebuffer> fbo;
SkyMode mode = SkyMode::SOLID;
uint size;
std::unique_ptr<Framebuffer> fbo;
const Assets& assets;
Shader& shader;
bool ready = false;
FastRandom random;
glm::vec3 lightDir;
std::unique_ptr<Mesh<SkyboxVertex>> mesh;
std::unique_ptr<Batch3D> batch3d;
std::vector<SkySprite> sprites;
int frameid = 0;
float prevMie = -1.0f;
@ -53,30 +47,41 @@ class Skybox {
glm::mat4 rotation;
void drawStars(float angle, float opacity);
void drawBackground(
const Camera& camera, const Assets& assets, int width, int height
void drawBackground(const Camera& camera, int width, int height);
void drawSkySprites(
float daytime,
float angle,
float opacity,
const std::vector<SkySprite>& sprites
);
void refreshFace(uint face, Cubemap* cubemap);
void refreshFace(uint face, Cubemap& cubemap);
public:
Skybox(uint size, Shader& shader);
Skybox(uint size, const Assets& assets);
~Skybox();
void setMode(SkyMode mode);
void draw(
const DrawContext& pctx,
const Camera& camera,
const Assets& assets,
const Environment& environment,
const DrawContext& pctx,
const Camera& camera,
float daytime,
float fog
);
void refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality);
void bind() const;
void unbind() const;
bool isReady() const {
return ready;
}
void refresh(
const Environment& environment,
const DrawContext& pctx,
float t,
float mie,
const glm::vec3& tint,
const glm::vec3& hightlight,
uint quality
);
const glm::vec3 getLightDir() const {
const Cubemap* getCubemap() const;
const glm::vec3& getLightDir() const {
return lightDir;
}
};

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

@ -1,12 +1,39 @@
#include "WorldRenderer.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "CloudsRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "Emitter.hpp"
#include "HandsRenderer.hpp"
#include "ModelBatch.hpp"
#include "NamedSkeletons.hpp"
#include "ParticlesRenderer.hpp"
#include "PrecipitationRenderer.hpp"
#include "Skybox.hpp"
#include "TextNote.hpp"
#include "TextsRenderer.hpp"
#include "assets/Assets.hpp"
#include "assets/assets_util.hpp"
#include "coders/GLSLExtension.hpp"
#include "content/Content.hpp"
#include "engine/Engine.hpp"
#include "coders/GLSLExtension.hpp"
#include "frontend/LevelFrontend.hpp"
#include "frontend/ContentGfxCache.hpp"
#include "frontend/LevelFrontend.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/GBuffer.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/PostProcessing.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Shadows.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/Texture.hpp"
#include "items/Inventory.hpp"
#include "items/ItemDef.hpp"
#include "items/ItemStack.hpp"
@ -25,32 +52,6 @@
#include "world/Level.hpp"
#include "world/LevelEvents.hpp"
#include "world/World.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/PostProcessing.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Shadows.hpp"
#include "graphics/core/GBuffer.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ParticlesRenderer.hpp"
#include "PrecipitationRenderer.hpp"
#include "HandsRenderer.hpp"
#include "NamedSkeletons.hpp"
#include "TextsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "ModelBatch.hpp"
#include "Skybox.hpp"
#include "Emitter.hpp"
#include "TextNote.hpp"
#include "CloudsRenderer.hpp"
#include <assert.h>
#include <algorithm>
@ -58,8 +59,6 @@
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
using namespace advanced_pipeline;
inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
@ -105,8 +104,7 @@ WorldRenderer::WorldRenderer(
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
);
skybox = std::make_unique<Skybox>(
settings.graphics.skyboxResolution.get(),
assets.require<Shader>("skybox_gen")
settings.graphics.skyboxResolution.get(), assets
);
const auto& content = level.content;
@ -160,7 +158,7 @@ void WorldRenderer::setupWorldShader(
shader.uniform1i("u_debugNormals", false);
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
shader.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform1i("u_skybox", TARGET_SKYBOX);
shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX);
auto indices = level.content.getIndices();
// Light emission when an emissive item is chosen
@ -179,6 +177,11 @@ void WorldRenderer::setupWorldShader(
}
}
float WorldRenderer::calcFogFactor() const {
const auto& settings = engine.getSettings();
return 15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
}
void WorldRenderer::renderOpaque(
const DrawContext& ctx,
const Camera& camera,
@ -187,14 +190,12 @@ void WorldRenderer::renderOpaque(
) {
texts->render(ctx, camera, settings, hudVisible, false);
bool culling = engine.getSettings().graphics.frustumCulling.get();
float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
float fogFactor = calcFogFactor();
auto& entityShader = assets.require<Shader>("entity");
setupWorldShader(entityShader, camera, settings, fogFactor);
skybox->bind();
bool culling = engine.getSettings().graphics.frustumCulling.get();
if (culling) {
frustumCulling->update(camera.getProjView());
}
@ -213,25 +214,26 @@ void WorldRenderer::renderOpaque(
auto& shader = assets.require<Shader>("main");
auto& cloudsShader = assets.require<Shader>("clouds");
auto& linesShader = assets.require<Shader>("lines");
setupWorldShader(shader, camera, settings, fogFactor);
chunksRenderer->drawChunks(camera, shader);
blockWraps->draw(ctx);
int cloudsQuality = settings.graphics.cloudsQuality.get();
if (cloudsQuality > 0) {
setupWorldShader(cloudsShader, camera, settings, fogFactor);
cloudsRenderer->draw(
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
);
if (level.environment.sky.clouds) {
int cloudsQuality = settings.graphics.cloudsQuality.get();
if (cloudsQuality > 0) {
setupWorldShader(cloudsShader, camera, settings, fogFactor);
cloudsRenderer->draw(
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
);
}
}
if (hudVisible) {
auto& linesShader = assets.require<Shader>("lines");
renderLines(camera, linesShader, ctx);
}
skybox->unbind();
}
void WorldRenderer::renderBlockSelection() {
@ -287,21 +289,19 @@ void WorldRenderer::refreshSettings(Shader** shaders) {
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
CompileTimeShaderSettings currentSettings {
gbufferPipeline,
shadowsQuality != 0,
graphics.ssao.get() && gbufferPipeline
};
if (
prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
graphics.ssao.get() && gbufferPipeline};
if (prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao
) {
prevCTShaderSettings.ssao != currentSettings.ssao) {
std::vector<std::string> defines;
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER");
if (currentSettings.advancedRender)
defines.emplace_back("ADVANCED_RENDER");
for (size_t i = 0; shaders[i]; i++) {
shaders[i]->recompile(defines);
@ -333,7 +333,8 @@ void WorldRenderer::renderFrame(
auto& entityShader = assets.require<Shader>("entity");
auto& cloudsShader = assets.require<Shader>("clouds");
auto& translucentShader = assets.require<Shader>("translucent");
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
auto& deferredShader =
assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings();
@ -343,19 +344,19 @@ void WorldRenderer::renderFrame(
&cloudsShader,
&translucentShader,
&deferredShader,
nullptr
};
nullptr};
refreshSettings(affectedShaders);
const auto& worldInfo = world->getInfo();
float clouds = weather.clouds();
clouds = glm::max(worldInfo.fog, clouds);
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
float random = rand() / static_cast<float>(RAND_MAX);
skybox->refresh(
level.environment,
pctx,
worldInfo.daytime,
mie,
@ -374,20 +375,19 @@ void WorldRenderer::renderFrame(
{
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderOpaque(ctx, camera, settings, hudVisible);
}
texts->render(pctx, camera, settings, hudVisible, true);
}
skybox->bind();
float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
float fogFactor = calcFogFactor();
if (gbufferPipeline) {
deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor);
@ -396,6 +396,7 @@ void WorldRenderer::renderFrame(
{
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
if (gbufferPipeline) {
postProcessing.bindDepthBuffer();
@ -404,7 +405,9 @@ void WorldRenderer::renderFrame(
}
// Background sky plane
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds);
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, clouds
);
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
@ -416,10 +419,12 @@ void WorldRenderer::renderFrame(
linesShader.uniformMatrix("u_projview", projView);
lineBatch->flush();
skybox->bind();
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
auto sctx = ctx.sub();
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
@ -430,44 +435,41 @@ void WorldRenderer::renderFrame(
entityShader.use();
setupWorldShader(entityShader, camera, settings, fogFactor);
std::array<const WeatherPreset*, 2> weatherInstances {&weather.a, &weather.b};
std::array<const WeatherPreset*, 2> weatherInstances {
&weather.a, &weather.b};
for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f("u_opacity", weather->fall.opaque ? t * t : t);
entityShader.uniform1f(
"u_opacity", weather->fall.opaque ? t * t : t
);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather);
}
}
skybox->unbind();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
postProcessing.render(pctx, assets, timer, camera);
if (player.currentCamera == player.fpCamera) {
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
display::clearDepth();
// prepare modified HUD camera
Camera hudcam = camera;
hudcam.far = 10.0f;
hudcam.setFov(0.9f);
hudcam.position = {};
hands->render(camera);
display::clearDepth();
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
skybox->bind();
modelBatch->render();
modelBatch->setLightsOffset(glm::vec3());
skybox->unbind();
hands->render(camera);
}
renderBlockOverlay(pctx);
@ -487,13 +489,12 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
if (def.overlayTexture.empty()) {
return;
}
auto textureRegion = util::get_texture_region(
assets, def.overlayTexture, "blocks:notfound"
);
auto textureRegion =
util::get_texture_region(assets, def.overlayTexture, "blocks:notfound");
DrawContext ctx = wctx.sub();
ctx.setDepthTest(false);
ctx.setCullFace(false);
auto& shader = assets.require<Shader>("ui3d");
shader.use();
batch3d->begin();

View file

@ -99,6 +99,8 @@ class WorldRenderer {
);
void refreshSettings(Shader** shaders);
float calcFogFactor() const;
public:
std::unique_ptr<ParticlesRenderer> particles;
std::unique_ptr<TextsRenderer> texts;

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

@ -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

@ -33,6 +33,8 @@ namespace io {
}
}
path(std::string_view str) : path(std::string(str)) {}
path(const char* str) : path(std::string(str)) {}
bool operator==(const std::string& other) const {

View file

@ -25,7 +25,7 @@ const uint MIN_SURROUNDING = 9;
ChunksController::ChunksController(Level& level)
: level(level),
generator(std::make_unique<WorldGenerator>(
level.content.generators.require(level.getWorld()->getGenerator()),
level.content.generators.require(level.environment.generator),
level.content,
level.getWorld()->getSeed()
)) {}

View file

@ -179,7 +179,7 @@ void EngineController::onMissingContent(
if (engine.isHeadless()) {
throw std::runtime_error(
"missing content: " +
json::stringify(create_missing_content_report(report), true, " ")
util::quote(json::stringify(create_missing_content_report(report), true, " "))
);
} else {
engine.setScreen(std::make_shared<MenuScreen>(engine));
@ -268,7 +268,7 @@ inline uint64_t str2seed(const std::string& seedstr) {
void EngineController::createWorld(
const std::string& name,
const std::string& seedstr,
const std::string& generatorID
const std::string& environment
) {
uint64_t seed = str2seed(seedstr);
@ -279,17 +279,31 @@ void EngineController::createWorld(
paths.setCurrentWorldFolder(folder);
engine.getContentControl().loadContent();
});
const auto& generators = engine.getContentControl().get()->generators;
// generator-based environments must die in 1.0
bool genBasedEnv = false;
if (auto genDef = generators.find(environment)) {
genBasedEnv = true;
}
auto& contentControl = engine.getContentControl();
auto level = World::create(
name,
generatorID,
genBasedEnv ? "" : environment,
genBasedEnv ? environment : "",
folder,
seed,
engine.getSettings(),
*contentControl.get(),
contentControl.getContentPacks()
);
// generator-based environment initialization
if (genBasedEnv) {
level->environment.generator = environment;
}
if (!engine.isHeadless()) {
level->players->create(localPlayer);
}

View file

@ -149,7 +149,7 @@ void LevelController::saveWorld() {
world->wfile->createDirectories();
scripting::on_world_save();
level->onSave();
level->getWorld()->write(level.get());
level->getWorld()->write(*level);
}
void LevelController::onWorldQuit() {

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[];

View file

@ -20,8 +20,14 @@
#include "window/Window.hpp"
#include "world/Level.hpp"
#include <array>
using namespace scripting;
namespace {
static std::array<std::unique_ptr<Process>, MAX_SUBPROCESSES> processes;
}
/// @brief Check if content is loaded
static int l_is_content_loaded(lua::State* L) {
return lua::pushboolean(L, content != nullptr);
@ -337,6 +343,10 @@ static int l_start_debug_instance(lua::State* L) {
if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) {
throw std::runtime_error("project has no debugging permission");
}
const auto& params = engine->getCoreParameters();
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
throw std::runtime_error("max subprocess depth exceeded");
}
int port = lua::tointeger(L, 1);
if (port == 0) {
@ -357,6 +367,8 @@ static int l_start_debug_instance(lua::State* L) {
"--res", paths.getResourcesFolder().u8string(),
"--dir", paths.getUserFilesFolder().u8string(),
"--dbg-server", "tcp:" + std::to_string(port),
"--sub-depth", std::to_string(engine->getCoreParameters()
.subProcessDepth + 1),
};
if (!projectPath.empty()) {
args.emplace_back("--project");
@ -365,11 +377,76 @@ static int l_start_debug_instance(lua::State* L) {
platform::new_engine_instance(
std::move(args),
outputPath.empty() ? "" : io::resolve(std::string(outputPath))
outputPath.empty() ? "" : io::resolve(std::string(outputPath)),
false
);
return lua::pushinteger(L, port);
}
static int l_start_background_instance(lua::State* L) {
if (!engine->getProject().permissions.has(Permissions::SUB_INSTANCES)) {
throw std::runtime_error("project has no sub-instances permission");
}
const auto& params = engine->getCoreParameters();
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
throw std::runtime_error("max subprocess depth exceeded");
}
auto scriptPath = lua::require_lstring(L, 1);
io::path outputPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : "";
const auto& paths = engine->getPaths();
std::vector<std::string> args {
"--headless",
"--res", paths.getResourcesFolder().u8string(),
"--dir", paths.getUserFilesFolder().u8string(),
"--script", io::resolve(scriptPath).u8string(),
"--sub-depth", std::to_string(engine->getCoreParameters()
.subProcessDepth + 1),
"--log", io::resolve(outputPath).u8string(),
};
args.emplace_back("--project");
args.emplace_back(io::resolve(engine->getProject().path).u8string());
int handle = -1;
for (int i = 0; i < ::processes.size(); i++) {
if (!::processes[i] || !::processes[i]->isActive()) {
handle = i;
break;
}
}
if (handle == -1) {
throw std::runtime_error("sub-processes limit exceeded");
}
::processes[handle] =
platform::new_engine_instance(std::move(args), "", true);
return lua::pushinteger(L, handle);
}
static int l_is_instance_alive(lua::State* L) {
int handle = lua::tointeger(L, 1);
if (handle < 0 || handle >= ::processes.size()) {
throw std::runtime_error("invalid process handle");
}
auto& process = ::processes[handle];
return lua::pushboolean(L, process && process->isActive());
}
static int l_terminate_instance(lua::State* L) {
int handle = lua::tointeger(L, 1);
if (handle < 0 || handle >= ::processes.size()) {
throw std::runtime_error("invalid process handle");
}
auto& process = ::processes[handle];
if (process == nullptr) {
return lua::pushboolean(L, false);
}
bool active = process->isActive();
process.reset();
return lua::pushboolean(L, active);
}
const luaL_Reg applib[] = {
/// content
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
@ -402,5 +479,8 @@ const luaL_Reg applib[] = {
{"get_version", lua::wrap<l_get_version>},
{"create_memory_device", lua::wrap<l_create_memory_device>},
{"start_debug_instance", lua::wrap<l_start_debug_instance>},
{"start_background_instance", lua::wrap<l_start_background_instance>},
{"is_instance_alive", lua::wrap<l_is_instance_alive>},
{"terminate_instance", lua::wrap<l_terminate_instance>},
{nullptr, nullptr}
};

View file

@ -314,6 +314,10 @@ static int l_world_raycast(lua::State* L) {
entityRaycast.entityFilterExcludeMode = lua::toboolean(L, -1);
lua::pop(L);
}
if (lua::getfield(L, "nonselect_entities", 1)) {
entityRaycast.includeNonSelectable = lua::toboolean(L, -1);
lua::pop(L);
}
if (lua::getfield(L, "filter_blocks", 1)) {
load_blocks_filter(L, -1, filteredBlocks);
blocksRaycast.filter = &filteredBlocks;

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

@ -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

@ -125,10 +125,13 @@ static int l_is_night(lua::State* L) {
}
static int l_get_generator(lua::State* L) {
return lua::pushstring(L, require_world_info().generator);
return lua::pushstring(L, require_level().environment.generator);
}
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

@ -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;
}
@ -70,12 +71,12 @@ static void create_libs(State* L, StateType stateType) {
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);
@ -100,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>);
}
@ -192,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

@ -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

@ -153,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

@ -148,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()) {
@ -166,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")) {

View file

@ -7,9 +7,11 @@
#include <iostream>
#include <stdexcept>
using namespace std::literals;
static debug::Logger logger("main");
static void sigterm_handler(int signum) {
logger.info() << (signum == SIGTERM ? "SIGTERM" : "SIGINT") << " received";
Engine::getInstance().quit();
}
@ -25,13 +27,24 @@ int main(int argc, char** argv) {
if (!parse_cmdline(argc, argv, coreParameters)) {
return EXIT_SUCCESS;
}
logger.debug() << "sub-process depth: "
<< coreParameters.subProcessDepth;
} catch (const std::runtime_error& err) {
std::cerr << err.what() << std::endl;
return EXIT_FAILURE;
}
std::signal(SIGTERM, sigterm_handler);
debug::Logger::init(coreParameters.userFolder.string() + "/latest.log");
#ifdef NDEBUG
std::signal(SIGINT, sigterm_handler);
#endif
std::filesystem::path logFile = coreParameters.logFile;
if (logFile.empty()) {
logFile = coreParameters.userFolder.string() + "/latest"s +
(coreParameters.subProcessDepth > 0
? ".sub" + std::to_string(coreParameters.subProcessDepth)
: "") + ".log"s;
}
debug::Logger::init(logFile.u8string());
platform::configure_encoding();
auto& engine = Engine::getInstance();

View file

@ -176,7 +176,8 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
for (auto [entity, eid, transform, body] : view.each()) {
if (eid.uid == settings.ignoredUid || !body.enabled ||
(settings.solidEntitiesOnly && !eid.def.solid)) {
(settings.solidEntitiesOnly && !eid.def.solid) ||
(!eid.def.selectable && !settings.includeNonSelectable)) {
continue;
}
if (settings.entitiesFilter) {

View file

@ -85,6 +85,7 @@ public:
bool solidEntitiesOnly = false;
bool entityFilterExcludeMode = false;
const std::set<entitydefid_t>* entitiesFilter = nullptr;
bool includeNonSelectable = false;
};
struct RaycastResult {

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;

View file

@ -42,6 +42,9 @@ struct EntityDef {
/// @brief Solid entity material material
std::string material;
/// @brief Can the entity be interacted with cursor (on_attack, on_use)
bool selectable = true;
/// @brief Does entity prevent blocks setup
bool blocking = true;

View file

@ -127,7 +127,7 @@ void Player::teleport(glm::vec3 position) {
void Player::attemptToChooseSpawnpoint() {
// looks bad to be here tbh
const auto& generatorDef =
level.content.generators.require(level.getWorld()->getGenerator());
level.content.generators.require(level.environment.generator);
int minHeight = generatorDef.playerMinSpawnHeight;
int maxHeight = generatorDef.playerMaxSpawnHeight;

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

@ -13,22 +13,22 @@
namespace fs = std::filesystem;
class ArgC {
public:
std::string keyword;
std::function<bool()> execute;
std::string args;
std::string help;
ArgC(
const std::string& keyword,
std::function<bool()> execute,
const std::string& args,
const std::string& help
) {
this->keyword = keyword;
this->execute = execute;
this->args = args;
this->help = help;
}
public:
std::string keyword;
std::function<bool(CoreParameters&, util::ArgsReader&)> execute;
std::string args;
std::string help;
ArgC(
const std::string& keyword,
std::function<bool(CoreParameters&, util::ArgsReader&)> execute,
const std::string& args,
const std::string& help
) {
this->keyword = keyword;
this->execute = execute;
this->args = args;
this->help = help;
}
};
@ -36,54 +36,66 @@ static bool perform_keyword(
util::ArgsReader& reader, const std::string& keyword, CoreParameters& params
) {
static const std::vector<ArgC> argumentsCommandline = {
ArgC("--res", [&params, &reader]() -> bool {
ArgC("--res", [](auto& params, auto& reader) -> bool {
params.resFolder = reader.next();
return true;
}, "<path>", "set resources directory."),
ArgC("--dir", [&params, &reader]() -> bool {
ArgC("--dir", [](auto& params, auto& reader) -> bool {
params.userFolder = reader.next();
return true;
}, "<path>", "set userfiles directory."),
ArgC("--project", [&params, &reader]() -> bool {
ArgC("--project", [](auto& params, auto& reader) -> bool {
params.projectFolder = reader.next();
return true;
}, "<path>", "set project directory."),
ArgC("--test", [&params, &reader]() -> bool {
ArgC("--test", [](auto& params, auto& reader) -> bool {
params.testMode = true;
params.scriptFile = reader.next();
return true;
}, "<path>", "test script file."),
ArgC("--script", [&params, &reader]() -> bool {
ArgC("--script", [](auto& params, auto& reader) -> bool {
params.testMode = false;
params.scriptFile = reader.next();
return true;
}, "<path>", "main script file."),
ArgC("--headless", [&params]() -> bool {
ArgC("--headless", [](auto& params, auto& reader) -> bool {
params.headless = true;
return true;
}, "", "run in headless mode."),
ArgC("--stdin-cmd", [&params]() -> bool {
ArgC("--stdin-cmd", [](auto& params, auto& reader) -> bool {
params.stdinCommands = true;
return true;
}, "", "run commands from stdin."),
ArgC("--tps", [&params, &reader]() -> bool {
ArgC("--tps", [](auto& params, auto& reader) -> bool {
params.tps = reader.nextInt();
return true;
}, "<tps>", "headless mode tick-rate (default - 20)."),
ArgC("--version", []() -> bool {
ArgC("--version", [](auto&, auto&) -> bool {
std::cout << ENGINE_VERSION_STRING << std::endl;
return false;
}, "", "display the engine version."),
ArgC("--dbg-server", [&params, &reader]() -> bool {
ArgC("--dbg-server", [](auto& params, auto& reader) -> bool {
params.debugServerString = reader.next();
return true;
}, "<serv>", "open debugging server where <serv> is {transport}:{port}"),
ArgC("--help", []() -> bool {
ArgC("--sub-depth", [](auto& params, auto& reader) -> bool {
params.subProcessDepth = reader.nextInt();
return true;
}, "<depth>", "sub-process depth"),
ArgC("--log", [](auto& params, auto& reader) -> bool {
params.logFile = reader.next();
return true;
}, "<file>", "target log file"),
ArgC("--help", [](auto&, auto&) -> bool {
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
std::cout << "Command-line arguments:\n";
for (auto& a : argumentsCommandline) {
std::cout << std::setw(24) << std::left << (a.keyword + " " + a.args);
std::cout << "- " << a.help << std::endl;
for (auto& arg : argumentsCommandline) {
if (arg.help.empty()) {
continue;
}
std::cout << std::setw(24) << std::left
<< (arg.keyword + " " + arg.args);
std::cout << "- " << arg.help << std::endl;
}
std::cout << std::endl;
return false;
@ -91,7 +103,7 @@ static bool perform_keyword(
};
for (auto& a : argumentsCommandline) {
if (a.keyword == keyword) {
return a.execute();
return a.execute(params, reader);
}
}
throw std::runtime_error("unknown argument " + keyword);

View file

@ -19,7 +19,16 @@
#else
#include <sys/poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#endif
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/wait.h>
#endif
using namespace std::literals;
namespace platform::internal {
std::filesystem::path get_executable_path();
@ -215,8 +224,102 @@ std::filesystem::path platform::get_executable_path() {
#endif
}
void platform::new_engine_instance(
const std::vector<std::string>& args, std::filesystem::path outputFile
class SystemProcess final : public Process {
public:
#ifdef __linux__
SystemProcess(pid_t pid) : pid(pid) {
}
~SystemProcess() {
terminate();
}
#elif defined(_WIN32)
SystemProcess(const PROCESS_INFORMATION& pi)
: processHandle(pi.hProcess), pid(pi.dwProcessId) {
if (pi.hThread != nullptr) {
CloseHandle(pi.hThread);
}
}
~SystemProcess() {
terminate();
}
#endif
SystemProcess(const SystemProcess&) = delete;
SystemProcess(SystemProcess&&) noexcept = default;
SystemProcess& operator=(const SystemProcess&) = delete;
SystemProcess& operator=(SystemProcess&& other) noexcept {
if (this != &other) {
#ifdef __linux__
pid = other.pid;
#elif defined(_WIN32)
if (processHandle != nullptr) {
CloseHandle(processHandle);
}
processHandle = other.processHandle;
pid = other.pid;
other.processHandle = nullptr;
other.pid = 0;
#endif
}
return *this;
}
bool isActive() const override {
#ifdef __linux__
if (kill(pid, 0) == 0) {
return true;
}
return errno != ESRCH;
#elif defined(_WIN32)
if (processHandle == nullptr) {
return false;
}
DWORD exitCode;
if (GetExitCodeProcess(processHandle, &exitCode)) {
return exitCode == STILL_ACTIVE;
}
return false;
#endif
}
void update() override {}
void waitForEnd() override {
#ifdef __linux__
waitpid(pid, nullptr, 0);
#elif defined(_WIN32)
if (processHandle != nullptr) {
WaitForSingleObject(processHandle, INFINITE);
}
#endif
};
void terminate() final override {
#ifdef __linux__
logger.info() << "terminating " << pid;
kill(pid, SIGTERM);
#elif defined(_WIN32)
if (processHandle != nullptr) {
TerminateProcess(processHandle, 1);
WaitForSingleObject(processHandle, INFINITE);
}
#endif
}
private:
#ifdef __linux__
pid_t pid;
#elif defined(_WIN32)
HANDLE processHandle = nullptr;
DWORD pid = 0;
#endif
};
std::unique_ptr<Process> platform::new_engine_instance(
const std::vector<std::string>& args,
std::filesystem::path outputFile,
bool subProcess
) {
auto executable = get_executable_path();
@ -255,7 +358,7 @@ void platform::new_engine_instance(
if (!outputFile.empty()) {
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = CreateFileW(
toWString(outputFile.u8string()).data(),
toWString(outputFile.empty() ? outputFile.u8string() : "NUL"s).data(),
GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
nullptr,
@ -285,6 +388,38 @@ void platform::new_engine_instance(
&si,
&pi
);
if (subProcess) {
HANDLE job = CreateJobObjectW(nullptr, nullptr);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{};
info.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
try {
if (!SetInformationJobObject(
job,
JobObjectExtendedLimitInformation,
&info,
sizeof(info))) {
throw std::runtime_error(
"SetInformationJobObject failed with code: " +
std::to_string(GetLastError())
);
}
if (!AssignProcessToJobObject(job, pi.hProcess)) {
throw std::runtime_error(
"SetInformationJobObject failed with code: " +
std::to_string(GetLastError())
);
}
return std::make_unique<SystemProcess>(pi);
} catch (const std::exception& err) {
CloseHandle(job);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return nullptr;
}
}
if (success) {
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
@ -295,6 +430,49 @@ void platform::new_engine_instance(
);
}
#else
// TODO: implement
#ifndef __APPLE__
if (subProcess) {
pid_t pid = fork();
if (outputFile.empty()) {
outputFile = "/dev/null";
}
int fd = open(
outputFile.string().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644
);
if (pid == 0) {
prctl(PR_SET_PDEATHSIG, SIGTERM);
if (getppid() == 1) {
_exit(1);
}
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
std::vector<const char*> argv(args.size() + 2);
argv[0] = const_cast<char*>(executable.c_str());
for (int i = 0; i < args.size(); i++) {
argv[i + 1] = args[i].c_str();
}
argv[args.size() + 1] = nullptr;
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
execvp(executable.c_str(), const_cast<char**>(argv.data()));
_exit(127);
} else {
close(fd);
usleep(1000); // waiting for execvp call
return std::make_unique<SystemProcess>(pid);
}
}
#endif
std::stringstream ss;
ss << executable;
for (int i = 0; i < args.size(); i++) {
@ -315,6 +493,7 @@ void platform::new_engine_instance(
);
}
#endif
return nullptr;
}
bool platform::stdin_has_data() {

View file

@ -2,8 +2,11 @@
#include <string>
#include <vector>
#include <memory>
#include <filesystem>
#include "interfaces/Process.hpp"
namespace platform {
void configure_encoding();
/// @brief Get Environment locale in ISO format ll_CC
@ -18,8 +21,10 @@ namespace platform {
/// @brief Get current process running executable path
std::filesystem::path get_executable_path();
/// @brief Run a separate engine instance with specified arguments
void new_engine_instance(
const std::vector<std::string>& args, std::filesystem::path outputFile
std::unique_ptr<Process> new_engine_instance(
const std::vector<std::string>& args,
std::filesystem::path outputFile,
bool subProcess
);
/// @brief Open URL in web browser
bool open_url(const std::string& url);

View file

@ -37,8 +37,6 @@ inline constexpr size_t MAX_USER_BLOCK_FIELDS_SIZE = 240;
inline constexpr int BLOCK_MAX_VARIANTS = 16;
inline std::string DEFAULT_MATERIAL = "base:stone";
struct BlockFuncsSet {
bool init : 1;
bool update : 1;
@ -206,7 +204,7 @@ public:
dv::value properties = nullptr;
/// @brief id of used BlockMaterial, may specify non-existing material
std::string material = DEFAULT_MATERIAL;
std::string material;
/// @brief Light emission R, G, B, S (sky lights: sun, moon, radioactive
/// clouds)

View file

@ -168,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) {
}
@ -228,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);
}
@ -256,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) {
@ -301,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;

49
src/world/Environment.cpp Normal file
View file

@ -0,0 +1,49 @@
#define VC_ENABLE_REFLECTION
#include "Environment.hpp"
#include "data/dv.hpp"
#include "data/dv_util.hpp"
dv::value Environment::serialize() const {
auto skySpritesList = dv::list();
for (const auto& sprite : sky.sprites) {
skySpritesList.add(dv::object({
{"texture", sprite.texture},
{"phase", sprite.phase},
{"distance", sprite.distance},
{"emissive", sprite.emissive},
{"altitude", sprite.altitude},
}));
}
return dv::object({
{"sky", dv::object({
{"mode", SkyModeMeta.getNameString(sky.mode)},
{"stars", sky.stars},
{"clouds", sky.clouds},
{"sprites", std::move(skySpritesList)},
})},
{"generator", generator}
});
}
void Environment::deserialize(const dv::value& src) {
if (auto skyItem = src.at("sky")) {
skyItem->at("mode").get(sky.mode, SkyModeMeta);
skyItem->at("stars").get(sky.stars);
skyItem->at("clouds").get(sky.clouds);
if (auto skySpritesList = skyItem->at("sprites")) {
for (int i = 0; i < skySpritesList->size(); i++) {
SkySprite sprite;
const auto& item = (*skySpritesList.ptr)[i];
item.at("texture").get(sprite.texture);
item.at("phase").get(sprite.phase);
item.at("distance").get(sprite.distance);
item.at("emissive").get(sprite.emissive);
item.at("altitude").get(sprite.altitude);
sky.sprites.push_back(std::move(sprite));
}
}
}
src.at("generator").get(generator);
}

66
src/world/Environment.hpp Normal file
View file

@ -0,0 +1,66 @@
#pragma once
#include "interfaces/Serializable.hpp"
#include "util/EnumMetadata.hpp"
#include <vector>
#include <glm/gtc/constants.hpp>
enum class SkyMode {
NONE, SOLID, BOX
};
VC_ENUM_METADATA(SkyMode)
{"none", SkyMode::NONE},
{"solid", SkyMode::SOLID},
{"box", SkyMode::BOX},
VC_ENUM_END
struct SkySprite {
std::string texture;
float phase;
float distance;
bool emissive;
float altitude;
};
class Environment : public Serializable {
public:
struct {
SkyMode mode = SkyMode::BOX;
bool stars = true;
bool clouds = true;
// TODO: move defaults to config file
std::vector<SkySprite> sprites {
SkySprite {
"misc/moon",
glm::pi<float>() * 0.5f,
4.0f,
false,
glm::pi<float>() * 0.25f,
},
SkySprite {
"misc/moon_flare",
glm::pi<float>() * 0.5f,
0.5f,
false,
glm::pi<float>() * 0.25f,
},
SkySprite {
"misc/sun",
glm::pi<float>() * 1.5f,
4.0f,
true,
glm::pi<float>() * 0.25f,
},
};
} sky;
std::string generator;
Environment() = default;
dv::value serialize() const override;
void deserialize(const dv::value& src) override;
private:
};

View file

@ -31,6 +31,7 @@ Level::Level(
entities(std::make_unique<Entities>(*this)),
players(std::make_unique<Players>(*this)),
pathfinding(std::make_unique<voxels::Pathfinding>(*this)) {
const auto& worldInfo = world->getInfo();
auto& cameraIndices = content.getIndices(ResourceType::CAMERA);
for (size_t i = 0; i < cameraIndices.size(); i++) {

View file

@ -6,6 +6,7 @@
#include <unordered_map>
#include "typedefs.hpp"
#include "Environment.hpp"
class Content;
class World;
@ -36,6 +37,7 @@ public:
std::unique_ptr<Players> players;
std::unique_ptr<voxels::Pathfinding> pathfinding;
std::vector<std::shared_ptr<Camera>> cameras; // move somewhere?
Environment environment {};
Level(
std::unique_ptr<World> world,

View file

@ -65,12 +65,12 @@ void World::writeResources(const Content& content) {
io::write_json(wfile->getResourcesFile(), root);
}
void World::write(Level* level) {
level->chunks->saveAll();
info.nextEntityId = level->entities->peekNextID();
void World::write(Level& level) {
level.chunks->saveAll();
info.nextEntityId = level.entities->peekNextID();
wfile->write(this, &content);
auto playerFile = level->players->serialize();
auto playerFile = level.players->serialize();
io::write_json(wfile->getPlayerFile(), playerFile);
writeResources(content);
@ -78,7 +78,8 @@ void World::write(Level* level) {
std::unique_ptr<Level> World::create(
const std::string& name,
const std::string& generator,
const std::string& environment,
const std::string& generatorOverride,
const io::path& directory,
uint64_t seed,
EngineSettings& settings,
@ -87,7 +88,8 @@ std::unique_ptr<Level> World::create(
) {
WorldInfo info {};
info.name = name;
info.generator = generator;
info.environment = environment;
info.explicitGenerator = generatorOverride;
info.seed = seed;
auto world = std::make_unique<World>(
info,
@ -101,7 +103,7 @@ std::unique_ptr<Level> World::create(
logger.info() << "created world '" << name << "' ("
<< directory.string() << ")";
}
logger.info() << "world seed: " << seed << " generator: " << generator;
logger.info() << "world seed: " << seed << " environment: " << environment;
return std::make_unique<Level>(std::move(world), content, settings);
}
@ -122,16 +124,19 @@ std::unique_ptr<Level> World::load(
<< worldFilesPtr->getFolder().string() << ")";
logger.info() << "world version: " << info->major << "." << info->minor
<< " seed: " << info->seed
<< " generator: " << info->generator;
<< " environment: " << info->environment;
auto world = std::make_unique<World>(
auto worldPtr = std::make_unique<World>(
info.value(), std::move(worldFilesPtr), content, packs
);
auto world = worldPtr.get();
auto& wfile = world->wfile;
wfile->readResourcesData(content);
auto level = std::make_unique<Level>(std::move(world), content, settings);
auto level = std::make_unique<Level>(std::move(worldPtr), content, settings);
if (world->getEnvironment().empty()) {
level->environment.generator = world->getInfo().explicitGenerator;
}
io::path file = wfile->getPlayerFile();
if (!io::is_regular_file(file)) {
logger.warning() << "player.json does not exists";
@ -163,8 +168,8 @@ void World::setName(const std::string& name) {
this->info.name = name;
}
void World::setGenerator(const std::string& generator) {
this->info.generator = generator;
void World::setEnvironment(const std::string& environment) {
this->info.environment = environment;
}
bool World::hasPack(const std::string& id) const {
@ -186,8 +191,8 @@ uint64_t World::getSeed() const {
return info.seed;
}
std::string World::getGenerator() const {
return info.generator;
std::string World::getEnvironment() const {
return info.environment;
}
const std::vector<ContentPack>& World::getPacks() const {
@ -196,7 +201,8 @@ const std::vector<ContentPack>& World::getPacks() const {
void WorldInfo::deserialize(const dv::value& root) {
name = root["name"].asString();
generator = root["generator"].asString(generator);
root.at("environment").get(environment);
root.at("generator").get(explicitGenerator);
seed = root["seed"].asInteger(seed);
if (root.has("version")) {
@ -226,7 +232,11 @@ dv::value WorldInfo::serialize() const {
versionobj["minor"] = ENGINE_VERSION_MINOR;
root["name"] = name;
root["generator"] = generator;
if (environment.empty()) {
root["generator"] = explicitGenerator;
} else {
root["environment"] = environment;
}
root["seed"] = seed;
auto& timeobj = root.object("time");

View file

@ -23,7 +23,8 @@ public:
struct WorldInfo : public Serializable {
std::string name;
std::string generator;
std::string environment;
std::string explicitGenerator;
uint64_t seed;
int64_t nextInventoryId = 1;
int64_t nextPlayerId = 0;
@ -76,7 +77,7 @@ public:
void updateTimers(float delta);
/// @brief Write all unsaved level data to the world directory
void write(Level* level);
void write(Level& level);
/// @brief Check world indices and generate ContentReport if convert required
/// @param directory world directory
@ -98,7 +99,8 @@ public:
/// @return Level instance containing World instance
static std::unique_ptr<Level> create(
const std::string& name,
const std::string& generator,
const std::string& environment,
const std::string& generatorOverride,
const io::path& directory,
uint64_t seed,
EngineSettings& settings,
@ -123,7 +125,7 @@ public:
void setName(const std::string& name);
void setSeed(uint64_t seed);
void setGenerator(const std::string& generator);
void setEnvironment(const std::string& environment);
/// @brief Check if world has content-pack installed
/// @param id content-pack id
@ -137,7 +139,7 @@ public:
uint64_t getSeed() const;
/// @brief Get world generator id
std::string getGenerator() const;
std::string getEnvironment() const;
bool isNameless() const {
return info.name.empty();