Merge branch 'main' into dev

This commit is contained in:
MihailRis
2025-09-30 23:40:02 +03:00
39 changed files with 658 additions and 233 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ protected:
void goBack(size_t count = 1);
void reset();
int64_t parseSimpleInt(int base);
int64_t parseSimpleInt(int base, size_t maxLength = 0xFFFFFFFF);
dv::value parseNumber(int sign);
dv::value parseNumber();
StringT parseString(CharT chr, bool closeRequired = true);
+6 -3
View File
@@ -349,7 +349,10 @@ std::basic_string<CharT> BasicParser<CharT>::parseXmlName() {
}
template <typename CharT>
int64_t BasicParser<CharT>::parseSimpleInt(int base) {
int64_t BasicParser<CharT>::parseSimpleInt(int base, size_t maxLength) {
if (maxLength == 0) return 0;
size_t start = pos;
CharT c = peek();
int index = hexchar2int(c);
if (index == -1 || index >= base) {
@@ -357,7 +360,7 @@ int64_t BasicParser<CharT>::parseSimpleInt(int base) {
}
int64_t value = index;
pos++;
while (hasNext()) {
while (hasNext() && pos - start < maxLength) {
c = source[pos];
while (c == '_') {
c = source[++pos];
@@ -476,7 +479,7 @@ std::basic_string<CharT> BasicParser<CharT>::parseString(
continue;
}
if (c == 'u' || c == 'x') {
int codepoint = parseSimpleInt(16);
int codepoint = parseSimpleInt(16, c == 'u' ? 4 : 2);
ubyte bytes[4];
int size = util::encode_utf8(codepoint, bytes);
CharT chars[4];
+12 -2
View File
@@ -1,3 +1,4 @@
#define VC_ENABLE_REFLECTION
#include "ContentPack.hpp"
#include <algorithm>
@@ -146,7 +147,7 @@ ContentPack ContentPack::read(const io::path& folder) {
std::uint8_t op_size = 0;
// Two symbol operators
if (op == ">=" || op == "=>" || op == "<=" || op == "=<") {
if (op == ">=" || op == "<=") {
op_size = 2;
depVerOperator = op;
}
@@ -169,7 +170,16 @@ ContentPack ContentPack::read(const io::path& folder) {
}
}
pack.dependencies.push_back({level, depName, depVer, depVerOperator});
VersionOperator versionOperator;
if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) {
pack.dependencies.push_back(
{level, depName, depVer, versionOperator}
);
} else {
throw contentpack_error(
pack.id, folder, "invalid version operator"
);
}
}
}
+15 -6
View File
@@ -1,14 +1,15 @@
#pragma once
#include "typedefs.hpp"
#include "content_fwd.hpp"
#include "io/io.hpp"
#include "util/EnumMetadata.hpp"
#include <stdexcept>
#include <string>
#include <vector>
#include <optional>
#include "typedefs.hpp"
#include "content_fwd.hpp"
#include "io/io.hpp"
class EnginePaths;
class contentpack_error : public std::runtime_error {
@@ -25,11 +26,19 @@ public:
io::path getFolder() const;
};
enum class DependencyVersionOperator {
enum class VersionOperator {
EQUAL, GREATHER, LESS,
GREATHER_OR_EQUAL, LESS_OR_EQUAL
};
VC_ENUM_METADATA(VersionOperator)
{"=", VersionOperator::EQUAL},
{">", VersionOperator::GREATHER},
{"<", VersionOperator::LESS},
{">=", VersionOperator::GREATHER_OR_EQUAL},
{"<=", VersionOperator::LESS_OR_EQUAL},
VC_ENUM_END
enum class DependencyLevel {
REQUIRED, // dependency must be installed
OPTIONAL, // dependency will be installed if found
@@ -41,7 +50,7 @@ struct DependencyPack {
DependencyLevel level;
std::string id;
std::string version;
std::string op;
VersionOperator op;
};
struct ContentPackStats {
+1 -16
View File
@@ -26,26 +26,11 @@ Version::Version(const std::string& version) {
if (parts.size() > 2) patch = parts[2];
}
DependencyVersionOperator Version::string_to_operator(const std::string& op) {
if (op == "=")
return DependencyVersionOperator::EQUAL;
else if (op == ">")
return DependencyVersionOperator::GREATHER;
else if (op == "<")
return DependencyVersionOperator::LESS;
else if (op == ">=" || op == "=>")
return DependencyVersionOperator::GREATHER_OR_EQUAL;
else if (op == "<=" || op == "=<")
return DependencyVersionOperator::LESS_OR_EQUAL;
else
return DependencyVersionOperator::EQUAL;
}
bool isNumber(const std::string& s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::is_digit);
}
bool Version::matches_pattern(const std::string& version) {
bool Version::matchesPattern(const std::string& version) {
for (char c : version) {
if (!isdigit(c) && c != '.') {
return false;
+8 -11
View File
@@ -33,25 +33,22 @@ public:
return !(*this > other);
}
bool process_operator(const std::string& op, const Version& other) const {
auto dep_op = Version::string_to_operator(op);
switch (dep_op) {
case DependencyVersionOperator::EQUAL:
bool processOperator(VersionOperator op, const Version& other) const {
switch (op) {
case VersionOperator::EQUAL:
return *this == other;
case DependencyVersionOperator::GREATHER:
case VersionOperator::GREATHER:
return *this > other;
case DependencyVersionOperator::LESS:
case VersionOperator::LESS:
return *this < other;
case DependencyVersionOperator::LESS_OR_EQUAL:
case VersionOperator::LESS_OR_EQUAL:
return *this <= other;
case DependencyVersionOperator::GREATHER_OR_EQUAL:
case VersionOperator::GREATHER_OR_EQUAL:
return *this >= other;
default:
return false;
}
}
static DependencyVersionOperator string_to_operator(const std::string& op);
static bool matches_pattern(const std::string& version);
static bool matchesPattern(const std::string& version);
};
+8 -3
View File
@@ -1,3 +1,4 @@
#define VC_ENABLE_REFLECTION
#include "PacksManager.hpp"
#include <queue>
@@ -109,9 +110,9 @@ static bool resolve_dependencies(
auto dep_pack = found -> second;
if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version)
if (Version::matchesPattern(dep.version) && Version::matchesPattern(dep_pack.version)
&& Version(dep_pack.version)
.process_operator(dep.op, Version(dep.version))
.processOperator(dep.op, Version(dep.version))
) {
// dependency pack version meets the required one
continue;
@@ -120,7 +121,11 @@ static bool resolve_dependencies(
continue;
} else {
throw contentpack_error(
dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'"
dep.id,
io::path(),
"does not meet required version '" +
VersionOperatorMeta.getNameString(dep.op) + dep.version +
"' of '" + pack->id + "'"
);
}
+5 -1
View File
@@ -96,12 +96,16 @@ void Shadows::setup(Shader& shader, const Weather& weather) {
if (shadows) {
const auto& worldInfo = level.getWorld()->getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
float shadowsOpacity = 1.0f - cloudsIntensity;
shadowsOpacity *= glm::sqrt(glm::abs(
glm::mod((worldInfo.daytime + 0.5f) * 2.0f, 1.0f) * 2.0f - 1.0f
));
shader.uniform1i("u_screen", 0);
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
shader.uniform3f("u_sunDir", shadowCamera.front);
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable
shader.uniform1f("u_shadowsOpacity", shadowsOpacity); // TODO: make it configurable
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
+1 -1
View File
@@ -22,7 +22,7 @@ namespace markdown {
Result<wchar_t> process(std::wstring_view source, bool eraseMarkdown);
template <typename CharT>
inline std::basic_string<CharT> escape(std::string_view source) {
inline std::basic_string<CharT> escape(std::basic_string_view<CharT> source) {
std::basic_stringstream<CharT> ss;
int pos = 0;
while (pos < source.size()) {
@@ -54,7 +54,12 @@ static int l_get_gravity_scale(lua::State* L) {
static int l_set_gravity_scale(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
entity->getRigidbody().hitbox.gravityScale = lua::tovec3(L, 2).y;
auto& hitbox = entity->getRigidbody().hitbox;
if (lua::istable(L, 2)) {
hitbox.gravityScale = lua::tovec3(L, 2).y;
} else {
hitbox.gravityScale = lua::tonumber(L, 2);
}
}
return 0;
}
+16 -6
View File
@@ -1,8 +1,4 @@
#include <algorithm>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <set>
#define VC_ENABLE_REFLECTION
#include "assets/AssetsLoader.hpp"
#include "content/Content.hpp"
@@ -19,6 +15,12 @@
#include "world/World.hpp"
#include "api_lua.hpp"
#include <algorithm>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <set>
using namespace scripting;
static int l_pack_get_folder(lua::State* L) {
@@ -114,8 +116,16 @@ static int l_pack_get_info(
default:
throw std::runtime_error("");
}
auto opString = VersionOperatorMeta.getNameString(dpack.op);
lua::pushfstring(L, "%s%s@%s%s", prefix.c_str(), dpack.id.c_str(), dpack.op.c_str(), dpack.version.c_str());
lua::pushfstring(
L,
"%s%s@%s%s",
prefix.c_str(),
dpack.id.c_str(),
(dpack.op == VersionOperator::EQUAL ? "" : opString).c_str(),
dpack.version.c_str()
);
lua::rawseti(L, i + 1);
}
lua::setfield(L, "dependencies");
@@ -3,6 +3,7 @@
#include <string>
#include <vector>
#include <array>
#include <random>
#include "lua_commons.hpp"
@@ -114,4 +115,20 @@ namespace lua {
std::shared_ptr<ImageData> mData;
};
static_assert(!std::is_abstract<LuaCanvas>());
class LuaRandom : public Userdata {
public:
std::mt19937 rng;
explicit LuaRandom(uint64_t seed) : rng(seed) {}
virtual ~LuaRandom() override = default;
const std::string& getTypeName() const override {
return TYPENAME;
}
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "__vc_Random";
};
static_assert(!std::is_abstract<LuaRandom>());
}
+8
View File
@@ -169,5 +169,13 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) {
auto file = "res:scripts/stdmin.lua";
auto src = io::read_string(file);
lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua"));
newusertype<LuaRandom>(L);
if (getglobal(L, "random")) {
if (getglobal(L, "__vc_Random")) {
setfield(L, "Random");
}
pop(L);
}
return L;
}
+9
View File
@@ -275,6 +275,15 @@ namespace lua {
}
return nullptr;
}
template <class T>
inline T& require_userdata(lua::State* L, int idx) {
if (void* rawptr = lua_touserdata(L, idx)) {
return *static_cast<T*>(rawptr);
}
throw std::runtime_error("invalid 'self' value");
}
template <class T, typename... Args>
inline int newuserdata(lua::State* L, Args&&... args) {
const auto& found = usertypeNames.find(typeid(T));
@@ -0,0 +1,56 @@
#include "../lua_custom_types.hpp"
#include "../lua_util.hpp"
#include <chrono>
using namespace lua;
using namespace std::chrono;
static int l_random(lua::State* L) {
std::uniform_int_distribution<> dist(0, std::numeric_limits<int>::max());
auto& rng = require_userdata<LuaRandom>(L, 1).rng;
size_t n = touinteger(L, 2);
createtable(L, n, 0);
for (size_t i = 0; i < n; i++) {
pushnumber(L, dist(rng) / (double)std::numeric_limits<int>::max());
rawseti(L, i + 1);
}
return 1;
}
static int l_seed(lua::State* L) {
require_userdata<LuaRandom>(L, 1).rng = std::mt19937(lua::touinteger(L, 2));
return 0;
}
static int l_meta_meta_call(lua::State* L) {
integer_t seed;
if (lua::isnoneornil(L, 1)) {
seed = system_clock::now().time_since_epoch().count();
} else {
seed = tointeger(L, 1);
}
return newuserdata<LuaRandom>(L, seed);
}
int LuaRandom::createMetatable(lua::State* L) {
createtable(L, 0, 3);
requireglobal(L, "__vc_create_random_methods");
createtable(L, 0, 0);
pushcfunction(L, wrap<l_random>);
setfield(L, "random");
pushcfunction(L, wrap<l_seed>);
setfield(L, "seed");
call(L, 1, 1);
setfield(L, "__index");
createtable(L, 0, 1);
pushcfunction(L, wrap<l_meta_meta_call>);
setfield(L, "__call");
setmetatable(L);
return 1;
}
+31 -16
View File
@@ -620,6 +620,26 @@ public:
}
};
static sockaddr_in resolve_address_dgram(const std::string& address, int port) {
sockaddr_in serverAddr{};
addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* addrinfo = nullptr;
if (int res = getaddrinfo(
address.c_str(), nullptr, &hints, &addrinfo
)) {
throw std::runtime_error(gai_strerror(res));
}
std::memcpy(&serverAddr, addrinfo->ai_addr, sizeof(sockaddr_in));
serverAddr.sin_port = htons(port);
freeaddrinfo(addrinfo);
return serverAddr;
}
class SocketUdpConnection : public UdpConnection {
u64id_t id;
SOCKET descriptor;
@@ -652,13 +672,7 @@ public:
throw std::runtime_error("could not create udp socket");
}
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
if (inet_pton(AF_INET, address.c_str(), &serverAddr.sin_addr) <= 0) {
closesocket(descriptor);
throw std::runtime_error("invalid udp address: " + address);
}
serverAddr.sin_port = htons(port);
sockaddr_in serverAddr = resolve_address_dgram(address, port);
if (::connect(descriptor, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
auto err = handle_socket_error("udp connect failed");
@@ -683,6 +697,7 @@ public:
while (open) {
int size = recv(descriptor, buffer.data(), buffer.size(), 0);
if (size <= 0) {
logger.error() <<id <<"udp connection " << id << handle_socket_error(" recv error").what();
if (!open) break;
closesocket(descriptor);
state = ConnectionState::CLOSED;
@@ -697,11 +712,12 @@ public:
}
int send(const char* buffer, size_t length) override {
int len = sendto(descriptor, buffer, length, 0,
(sockaddr*)&addr, sizeof(addr));
int len = ::send(descriptor, buffer, length, 0);
if (len < 0) {
auto err = handle_socket_error(" send failed");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << "udp connection " << id << err.what();
} else totalUpload += len;
return len;
@@ -710,6 +726,7 @@ public:
void close(bool discardAll=false) override {
if (!open) return;
open = false;
logger.info() << "closing udp connection "<< id;
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
@@ -789,13 +806,11 @@ public:
}
void sendTo(const std::string& addr, int port, const char* buffer, size_t length) override {
sockaddr_in client{};
client.sin_family = AF_INET;
inet_pton(AF_INET, addr.c_str(), &client.sin_addr);
client.sin_port = htons(port);
sendto(descriptor, buffer, length, 0,
reinterpret_cast<sockaddr*>(&client), sizeof(client));
sockaddr_in client = resolve_address_dgram(addr, port);
if (sendto(descriptor, buffer, length, 0,
reinterpret_cast<sockaddr*>(&client), sizeof(client)) < 0) {
logger.error() << handle_socket_error("sendto").what();
}
}
void close() override {
+62 -35
View File
@@ -1,6 +1,9 @@
#include "command_line.hpp"
#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include "io/engine_paths.hpp"
#include "util/ArgsReader.hpp"
@@ -8,45 +11,69 @@
namespace fs = std::filesystem;
class ArgC {
public:
std::string keyword;
std::function<bool()> execute;
std::string help;
ArgC(const std::string& keyword, std::function<bool()> execute, const std::string& help) {
this->keyword = keyword;
this->execute = execute;
this->help = help;
}
};
static bool perform_keyword(
util::ArgsReader& reader, const std::string& keyword, CoreParameters& params
) {
if (keyword == "--res") {
params.resFolder = reader.next();
} else if (keyword == "--dir") {
params.userFolder = reader.next();
} else if (keyword == "--project") {
params.projectFolder = reader.next();
} else if (keyword == "--help" || keyword == "-h") {
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
std::cout << "command-line arguments:\n";
std::cout << " --help - display this help\n";
std::cout << " --version - display engine version\n";
std::cout << " --res <path> - set resources directory\n";
std::cout << " --dir <path> - set userfiles directory\n";
std::cout << " --project <path> - set project directory\n";
std::cout << " --headless - run in headless mode\n";
std::cout << " --test <path> - test script file\n";
std::cout << " --script <path> - main script file\n";
std::cout << std::endl;
return false;
} else if (keyword == "--version") {
std::cout << ENGINE_VERSION_STRING << std::endl;
return false;
} else if (keyword == "--headless") {
params.headless = true;
} else if (keyword == "--test") {
auto token = reader.next();
params.testMode = true;
params.scriptFile = token;
} else if (keyword == "--script") {
auto token = reader.next();
params.testMode = false;
params.scriptFile = token;
} else {
throw std::runtime_error("unknown argument " + keyword);
static const std::vector<ArgC> argumentsCommandline = {
ArgC("--res", [&params, &reader]() -> bool {
params.resFolder = reader.next();
return true;
}, "<path> - set resources directory."),
ArgC("--dir", [&params, &reader]() -> bool {
params.userFolder = reader.next();
return true;
}, "<path> - set userfiles directory."),
ArgC("--project", [&params, &reader]() -> bool {
params.projectFolder = reader.next();
return true;
}, "<path> - set project directory."),
ArgC("--test", [&params, &reader]() -> bool {
params.testMode = true;
params.scriptFile = reader.next();
return true;
}, "<path> - test script file."),
ArgC("--script", [&params, &reader]() -> bool {
params.testMode = false;
params.scriptFile = reader.next();
return true;
}, "<path> - main script file."),
ArgC("--headless", [&params]() -> bool {
params.headless = true;
return true;
}, "- run in headless mode."),
ArgC("--version", []() -> bool {
std::cout << ENGINE_VERSION_STRING << std::endl;
return false;
}, "- display the engine version."),
ArgC("--help", []() -> bool {
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
std::cout << "Command-line arguments:\n";
for (auto& a : argumentsCommandline) {
std::cout << a.keyword << " " << a.help << std::endl;
}
std::cout << std::endl;
return false;
}, "- display this help.")
};
for (auto& a : argumentsCommandline) {
if (a.keyword == keyword) {
return a.execute();
}
}
return true;
throw std::runtime_error("unknown argument " + keyword);
}
bool parse_cmdline(int argc, char** argv, CoreParameters& params) {
+1 -1
View File
@@ -40,7 +40,7 @@ std::string util::escape(std::string_view s, bool escapeUnicode) {
uint cpsize;
int codepoint = decode_utf8(cpsize, s.data() + pos);
if (escapeUnicode) {
ss << "\\u" << std::hex << codepoint;
ss << "\\u" << std::setw(4) << std::setfill('0') << std::hex << codepoint;
} else {
ss << std::string(s.data() + pos, cpsize);
}
+15 -1
View File
@@ -19,6 +19,7 @@
static debug::Logger logger("window");
static std::unordered_set<std::string> supported_gl_extensions;
static void window_size_callback(GLFWwindow* window, int width, int height);
static void init_gl_extensions_list() {
GLint numExtensions = 0;
@@ -418,7 +419,7 @@ public:
if (fullscreen) {
glfwGetWindowPos(window, &posX, &posY);
glfwSetWindowMonitor(
window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE
window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate
);
} else {
glfwSetWindowMonitor(
@@ -430,6 +431,7 @@ public:
settings->height.get(),
GLFW_DONT_CARE
);
window_size_callback(window, settings->width.get(), settings->height.get());
}
double xPos, yPos;
@@ -596,6 +598,17 @@ static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) {
handler->input.setCursorPosition(xpos, ypos);
}
static void iconify_callback(GLFWwindow* window, int iconified) {
auto handler = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (handler->isFullscreen() && iconified == 0) {
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(
window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate
);
}
}
static void create_standard_cursors() {
for (int i = 0; i <= static_cast<int>(CursorShape::LAST); i++) {
int cursor = GLFW_ARROW_CURSOR + i;
@@ -615,6 +628,7 @@ static void setup_callbacks(GLFWwindow* window) {
glfwSetWindowSizeCallback(window, window_size_callback);
glfwSetCharCallback(window, character_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetWindowIconifyCallback(window, iconify_callback);
}
std::tuple<