@@ -71,8 +71,8 @@ static auto process_program(const ResPaths& paths, const std::string& filename)
|
||||
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
|
||||
auto vertex = preprocessor.process(vertexFile, vertexSource);
|
||||
auto fragment = preprocessor.process(fragmentFile, fragmentSource);
|
||||
auto vertex = preprocessor.process(vertexFile, vertexSource, false, {});
|
||||
auto fragment = preprocessor.process(fragmentFile, fragmentSource, false, {});
|
||||
return std::make_pair(vertex, fragment);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ assetload::postfunc assetload::posteffect(
|
||||
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
preprocessor.addHeader(
|
||||
"__effect__", preprocessor.process(effectFile, effectSource, true)
|
||||
"__effect__", preprocessor.process(effectFile, effectSource, true, {})
|
||||
);
|
||||
|
||||
auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect");
|
||||
|
||||
@@ -22,6 +22,10 @@ void GLSLExtension::setPaths(const ResPaths* paths) {
|
||||
this->paths = paths;
|
||||
}
|
||||
|
||||
void GLSLExtension::setTraceOutput(bool enabled) {
|
||||
this->traceOutput = enabled;
|
||||
}
|
||||
|
||||
void GLSLExtension::loadHeader(const std::string& name) {
|
||||
if (paths == nullptr) {
|
||||
return;
|
||||
@@ -29,7 +33,7 @@ void GLSLExtension::loadHeader(const std::string& name) {
|
||||
io::path file = paths->find("shaders/lib/" + name + ".glsl");
|
||||
std::string source = io::read_string(file);
|
||||
addHeader(name, {});
|
||||
addHeader(name, process(file, source, true));
|
||||
addHeader(name, process(file, source, true, {}));
|
||||
}
|
||||
|
||||
void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) {
|
||||
@@ -123,13 +127,22 @@ static Value default_value_for(Type type) {
|
||||
|
||||
class GLSLParser : public BasicParser<char> {
|
||||
public:
|
||||
GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header)
|
||||
GLSLParser(
|
||||
GLSLExtension& glsl,
|
||||
std::string_view file,
|
||||
std::string_view source,
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
)
|
||||
: BasicParser(file, source), glsl(glsl) {
|
||||
if (!header) {
|
||||
ss << "#version " << GLSLExtension::VERSION << '\n';
|
||||
}
|
||||
for (auto& entry : glsl.getDefines()) {
|
||||
ss << "#define " << entry.first << " " << entry.second << '\n';
|
||||
for (auto& entry : defines) {
|
||||
ss << "#define " << entry << '\n';
|
||||
}
|
||||
for (auto& entry : defines) {
|
||||
ss << "#define " << entry << '\n';
|
||||
}
|
||||
}
|
||||
uint linenum = 1;
|
||||
source_line(ss, linenum);
|
||||
@@ -289,10 +302,34 @@ private:
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
static void trace_output(
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
const GLSLExtension::ProcessingResult& result
|
||||
) {
|
||||
std::stringstream ss;
|
||||
ss << "export:trace/" << file.name();
|
||||
io::path outfile = ss.str();
|
||||
try {
|
||||
io::create_directories(outfile.parent());
|
||||
io::write_string(outfile, result.code);
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.error() << "error on saving GLSLExtension::preprocess output ("
|
||||
<< outfile.string() << "): " << err.what();
|
||||
}
|
||||
}
|
||||
|
||||
GLSLExtension::ProcessingResult GLSLExtension::process(
|
||||
const io::path& file, const std::string& source, bool header
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
) {
|
||||
std::string filename = file.string();
|
||||
GLSLParser parser(*this, filename, source, header);
|
||||
return parser.process();
|
||||
GLSLParser parser(*this, filename, source, header, defines);
|
||||
auto result = parser.process();
|
||||
if (traceOutput) {
|
||||
trace_output(file, source, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "data/setting.hpp"
|
||||
#include "graphics/core/PostEffect.hpp"
|
||||
|
||||
class ResPaths;
|
||||
@@ -19,6 +20,7 @@ public:
|
||||
};
|
||||
|
||||
void setPaths(const ResPaths* paths);
|
||||
void setTraceOutput(bool enabled);
|
||||
|
||||
void define(const std::string& name, std::string value);
|
||||
void undefine(const std::string& name);
|
||||
@@ -37,7 +39,8 @@ public:
|
||||
ProcessingResult process(
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
bool header = false
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
);
|
||||
|
||||
static inline std::string VERSION = "330 core";
|
||||
@@ -46,4 +49,5 @@ private:
|
||||
std::unordered_map<std::string, std::string> defines;
|
||||
|
||||
const ResPaths* paths = nullptr;
|
||||
bool traceOutput = false;
|
||||
};
|
||||
|
||||
@@ -174,7 +174,6 @@ std::unique_ptr<model::Model> vcm::parse(
|
||||
"'model' tag expected as root, got '" + root.getTag() + "'"
|
||||
);
|
||||
}
|
||||
std::cout << xml::stringify(*doc) << std::endl;
|
||||
return load_model(root);
|
||||
} catch (const parsing_error& err) {
|
||||
throw std::runtime_error(err.errorLog());
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
inline constexpr int ENGINE_VERSION_MAJOR = 0;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 28;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 29;
|
||||
|
||||
#ifdef NDEBUG
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = false;
|
||||
@@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = true;
|
||||
#endif // NDEBUG
|
||||
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.28";
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.29";
|
||||
|
||||
/// @brief world regions format version
|
||||
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
||||
|
||||
+11
-1
@@ -35,13 +35,15 @@ Content::Content(
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
|
||||
ResourceIndicesSet resourceIndices,
|
||||
dv::value defaults
|
||||
dv::value defaults,
|
||||
std::unordered_map<std::string, int> tags
|
||||
)
|
||||
: indices(std::move(indices)),
|
||||
packs(std::move(packs)),
|
||||
blockMaterials(std::move(blockMaterials)),
|
||||
skeletons(std::move(skeletons)),
|
||||
defaults(std::move(defaults)),
|
||||
tags(std::move(tags)),
|
||||
blocks(std::move(blocks)),
|
||||
items(std::move(items)),
|
||||
entities(std::move(entities)),
|
||||
@@ -63,6 +65,14 @@ const rigging::SkeletonConfig* Content::getSkeleton(const std::string& id
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
const rigging::SkeletonConfig& Content::requireSkeleton(const std::string& id) const {
|
||||
auto skeleton = getSkeleton(id);
|
||||
if (skeleton == nullptr) {
|
||||
throw std::runtime_error("skeleton '" + id + "' not loaded");
|
||||
}
|
||||
return *skeleton;
|
||||
}
|
||||
|
||||
const BlockMaterial* Content::findBlockMaterial(const std::string& id) const {
|
||||
auto found = blockMaterials.find(id);
|
||||
if (found == blockMaterials.end()) {
|
||||
|
||||
+12
-1
@@ -176,6 +176,7 @@ class Content {
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials;
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
|
||||
dv::value defaults = nullptr;
|
||||
std::unordered_map<std::string, int> tags;
|
||||
public:
|
||||
ContentUnitDefs<Block> blocks;
|
||||
ContentUnitDefs<ItemDef> items;
|
||||
@@ -195,7 +196,8 @@ public:
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
|
||||
ResourceIndicesSet resourceIndices,
|
||||
dv::value defaults
|
||||
dv::value defaults,
|
||||
std::unordered_map<std::string, int> tags
|
||||
);
|
||||
~Content();
|
||||
|
||||
@@ -211,7 +213,16 @@ public:
|
||||
return defaults;
|
||||
}
|
||||
|
||||
int getTagIndex(const std::string& tag) const {
|
||||
const auto& found = tags.find(tag);
|
||||
if (found == tags.end()) {
|
||||
return -1;
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
const rigging::SkeletonConfig* getSkeleton(const std::string& id) const;
|
||||
const rigging::SkeletonConfig& requireSkeleton(const std::string& id) const;
|
||||
const BlockMaterial* findBlockMaterial(const std::string& id) const;
|
||||
const ContentPackRuntime* getPackRuntime(const std::string& id) const;
|
||||
ContentPackRuntime* getPackRuntime(const std::string& id);
|
||||
|
||||
@@ -28,6 +28,9 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
// Generating runtime info
|
||||
def.rt.id = blockDefsIndices.size();
|
||||
def.rt.emissive = *reinterpret_cast<uint32_t*>(def.emission);
|
||||
for (const auto& tag : def.tags) {
|
||||
def.rt.tags.insert(tags.add(tag));
|
||||
}
|
||||
|
||||
if (def.variants) {
|
||||
for (auto& variant : def.variants->variants) {
|
||||
@@ -58,7 +61,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
}
|
||||
|
||||
blockDefsIndices.push_back(&def);
|
||||
groups->insert(def.defaults.drawGroup); // FIXME
|
||||
groups->insert(def.defaults.drawGroup); // FIXME: variants
|
||||
}
|
||||
|
||||
std::vector<ItemDef*> itemDefsIndices;
|
||||
@@ -93,7 +96,8 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
std::move(blockMaterials),
|
||||
std::move(skeletons),
|
||||
std::move(resourceIndices),
|
||||
std::move(defaults)
|
||||
std::move(defaults),
|
||||
std::move(tags.map)
|
||||
);
|
||||
|
||||
// Now, it's time to resolve foreign keys
|
||||
|
||||
@@ -62,6 +62,27 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct TagsIndices {
|
||||
int nextIndex = 1;
|
||||
std::unordered_map<std::string, int> map;
|
||||
|
||||
int add(const std::string& tag) {
|
||||
const auto& found = map.find(tag);
|
||||
if (found != map.end()) {
|
||||
return found->second;
|
||||
}
|
||||
return map[tag] = nextIndex++;
|
||||
}
|
||||
|
||||
int indexOf(const std::string& tag) {
|
||||
const auto& found = map.find(tag);
|
||||
if (found == map.end()) {
|
||||
return -1;
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
};
|
||||
|
||||
class ContentBuilder {
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials;
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
|
||||
@@ -74,6 +95,7 @@ public:
|
||||
ContentUnitBuilder<GeneratorDef> generators {allNames, ContentType::GENERATOR};
|
||||
ResourceIndicesSet resourceIndices {};
|
||||
dv::value defaults = nullptr;
|
||||
TagsIndices tags {};
|
||||
|
||||
~ContentBuilder();
|
||||
|
||||
|
||||
@@ -288,6 +288,7 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
item.icon = def.name;
|
||||
item.placingBlock = def.name;
|
||||
item.tags = def.tags;
|
||||
|
||||
for (uint j = 0; j < 4; j++) {
|
||||
item.emission[j] = def.emission[j];
|
||||
@@ -412,6 +413,25 @@ void ContentLoader::load() {
|
||||
if (io::exists(contentFile)) {
|
||||
loadContent(io::read_json(contentFile));
|
||||
}
|
||||
|
||||
// Load attached tags
|
||||
io::path tagsFile = folder / "tags.toml";
|
||||
if (io::exists(tagsFile)) {
|
||||
auto tagsMap = io::read_object(tagsFile);
|
||||
for (const auto& [key, list] : tagsMap.asObject()) {
|
||||
for (const auto& id : list) {
|
||||
const auto& stringId = id.asString();
|
||||
if (auto block = builder.blocks.get(stringId)) {
|
||||
block->tags.push_back(key);
|
||||
if (auto item = builder.items.get(stringId + BLOCK_ITEM_SUFFIX)) {
|
||||
item->tags.push_back(key);
|
||||
}
|
||||
} else if (auto item = builder.items.get(stringId)) {
|
||||
item->tags.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
||||
@@ -118,21 +118,58 @@ ContentPack ContentPack::read(const io::path& folder) {
|
||||
const auto& dependencies = *found;
|
||||
for (const auto& elem : dependencies) {
|
||||
std::string depName = elem.asString();
|
||||
auto level = DependencyLevel::required;
|
||||
auto level = DependencyLevel::REQUIRED;
|
||||
switch (depName.at(0)) {
|
||||
case '!':
|
||||
depName = depName.substr(1);
|
||||
break;
|
||||
case '?':
|
||||
depName = depName.substr(1);
|
||||
level = DependencyLevel::optional;
|
||||
level = DependencyLevel::OPTIONAL;
|
||||
break;
|
||||
case '~':
|
||||
depName = depName.substr(1);
|
||||
level = DependencyLevel::weak;
|
||||
level = DependencyLevel::WEAK;
|
||||
break;
|
||||
}
|
||||
pack.dependencies.push_back({level, depName});
|
||||
|
||||
std::string depVer = "*";
|
||||
std::string depVerOperator = "=";
|
||||
|
||||
size_t versionPos = depName.rfind("@");
|
||||
if (versionPos != std::string::npos) {
|
||||
depVer = depName.substr(versionPos + 1);
|
||||
depName = depName.substr(0, versionPos);
|
||||
|
||||
if (depVer.size() >= 2) {
|
||||
std::string op = depVer.substr(0, 2);
|
||||
std::uint8_t op_size = 0;
|
||||
|
||||
// Two symbol operators
|
||||
if (op == ">=" || op == "=>" || op == "<=" || op == "=<") {
|
||||
op_size = 2;
|
||||
depVerOperator = op;
|
||||
}
|
||||
|
||||
// One symbol operators
|
||||
else {
|
||||
op = depVer.substr(0, 1);
|
||||
|
||||
if (op == ">" || op == "<") {
|
||||
op_size = 1;
|
||||
depVerOperator = op;
|
||||
}
|
||||
}
|
||||
|
||||
depVer = depVer.substr(op_size);
|
||||
} else {
|
||||
if (depVer == ">" || depVer == "<"){
|
||||
depVer = "*";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pack.dependencies.push_back({level, depName, depVer, depVerOperator});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,21 +20,28 @@ public:
|
||||
io::path folder,
|
||||
const std::string& message
|
||||
);
|
||||
|
||||
|
||||
std::string getPackId() const;
|
||||
io::path getFolder() const;
|
||||
};
|
||||
|
||||
enum class DependencyVersionOperator {
|
||||
EQUAL, GREATHER, LESS,
|
||||
GREATHER_OR_EQUAL, LESS_OR_EQUAL
|
||||
};
|
||||
|
||||
enum class DependencyLevel {
|
||||
required, // dependency must be installed
|
||||
optional, // dependency will be installed if found
|
||||
weak, // only affects packs order
|
||||
REQUIRED, // dependency must be installed
|
||||
OPTIONAL, // dependency will be installed if found
|
||||
WEAK, // only affects packs order
|
||||
};
|
||||
|
||||
/// @brief Content-pack that should be installed earlier the dependent
|
||||
struct DependencyPack {
|
||||
DependencyLevel level;
|
||||
std::string id;
|
||||
std::string version;
|
||||
std::string op;
|
||||
};
|
||||
|
||||
struct ContentPackStats {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "ContentPackVersion.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "coders/commons.hpp"
|
||||
|
||||
Version::Version(const std::string& version) {
|
||||
major = 0;
|
||||
minor = 0;
|
||||
patch = 0;
|
||||
|
||||
std::vector<int> parts;
|
||||
|
||||
std::stringstream ss(version);
|
||||
std::string part;
|
||||
while (std::getline(ss, part, '.')) {
|
||||
if (!part.empty()) {
|
||||
parts.push_back(std::stoi(part));
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.size() > 0) major = parts[0];
|
||||
if (parts.size() > 1) minor = parts[1];
|
||||
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) {
|
||||
for (char c : version) {
|
||||
if (!isdigit(c) && c != '.') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream ss(version);
|
||||
|
||||
std::vector<std::string> parts;
|
||||
std::string part;
|
||||
while (std::getline(ss, part, '.')) {
|
||||
if (part.empty()) return false;
|
||||
if (!isNumber(part)) return false;
|
||||
|
||||
parts.push_back(part);
|
||||
}
|
||||
|
||||
return parts.size() == 2 || parts.size() == 3;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#include <string>
|
||||
|
||||
#include "content/ContentPack.hpp"
|
||||
|
||||
class Version {
|
||||
public:
|
||||
int major;
|
||||
int minor;
|
||||
int patch;
|
||||
|
||||
Version(const std::string& version);
|
||||
|
||||
bool operator==(const Version& other) const {
|
||||
return major == other.major && minor == other.minor &&
|
||||
patch == other.patch;
|
||||
}
|
||||
|
||||
bool operator<(const Version& other) const {
|
||||
if (major != other.major) return major < other.major;
|
||||
if (minor != other.minor) return minor < other.minor;
|
||||
return patch < other.patch;
|
||||
}
|
||||
|
||||
bool operator>(const Version& other) const {
|
||||
return other < *this;
|
||||
}
|
||||
|
||||
bool operator>=(const Version& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
bool operator<=(const Version& other) const {
|
||||
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:
|
||||
return *this == other;
|
||||
case DependencyVersionOperator::GREATHER:
|
||||
return *this > other;
|
||||
case DependencyVersionOperator::LESS:
|
||||
return *this < other;
|
||||
case DependencyVersionOperator::LESS_OR_EQUAL:
|
||||
return *this <= other;
|
||||
case DependencyVersionOperator::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);
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
|
||||
#include "ContentPackVersion.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
|
||||
PacksManager::PacksManager() = default;
|
||||
@@ -90,7 +91,7 @@ static bool resolve_dependencies(
|
||||
}
|
||||
auto found = packs.find(dep.id);
|
||||
bool exists = found != packs.end();
|
||||
if (!exists && dep.level == DependencyLevel::required) {
|
||||
if (!exists && dep.level == DependencyLevel::REQUIRED) {
|
||||
throw contentpack_error(
|
||||
dep.id, io::path(), "dependency of '" + pack->id + "'"
|
||||
);
|
||||
@@ -99,15 +100,32 @@ static bool resolve_dependencies(
|
||||
// ignored for optional or weak dependencies
|
||||
continue;
|
||||
}
|
||||
if (resolveWeaks && dep.level == DependencyLevel::weak) {
|
||||
if (resolveWeaks && dep.level == DependencyLevel::WEAK) {
|
||||
// dependency pack is found but not added yet
|
||||
// resolveWeaks is used on second iteration, so it's will not be
|
||||
// added
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dep_pack = found -> second;
|
||||
|
||||
if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version)
|
||||
&& Version(dep_pack.version)
|
||||
.process_operator(dep.op, Version(dep.version))
|
||||
) {
|
||||
// dependency pack version meets the required one
|
||||
continue;
|
||||
} else if (dep.version == "*" || dep.version == dep_pack.version){
|
||||
// fallback: dependency pack version also meets required one
|
||||
continue;
|
||||
} else {
|
||||
throw contentpack_error(
|
||||
dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'"
|
||||
);
|
||||
}
|
||||
|
||||
if (!util::contains(allNames, dep.id) &&
|
||||
dep.level != DependencyLevel::weak) {
|
||||
dep.level != DependencyLevel::WEAK) {
|
||||
allNames.push_back(dep.id);
|
||||
queue.push(&found->second);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#define VC_ENABLE_REFLECTION
|
||||
#include "ContentUnitLoader.hpp"
|
||||
#include "ContentLoadingCommons.hpp"
|
||||
|
||||
#include "../ContentBuilder.hpp"
|
||||
#include "coders/json.hpp"
|
||||
@@ -87,20 +88,8 @@ template<> void ContentUnitLoader<Block>::loadUnit(
|
||||
Block& def, const std::string& name, const io::path& file
|
||||
) {
|
||||
auto root = io::read_json(file);
|
||||
if (def.properties == nullptr) {
|
||||
def.properties = dv::object();
|
||||
def.properties["name"] = name;
|
||||
}
|
||||
for (auto& [key, value] : root.asObject()) {
|
||||
auto pos = key.rfind('@');
|
||||
if (pos == std::string::npos) {
|
||||
def.properties[key] = value;
|
||||
continue;
|
||||
}
|
||||
auto field = key.substr(0, pos);
|
||||
auto suffix = key.substr(pos + 1);
|
||||
process_method(def.properties, suffix, field, value);
|
||||
}
|
||||
process_properties(def, name, root);
|
||||
process_tags(def, root);
|
||||
|
||||
if (root.has("parent")) {
|
||||
const auto& parentName = root["parent"].asString();
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "data/dv.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
template <typename T>
|
||||
inline void process_properties(T& def, const std::string& name, const dv::value& root) {
|
||||
if (def.properties == nullptr) {
|
||||
def.properties = dv::object();
|
||||
def.properties["name"] = name;
|
||||
}
|
||||
for (auto& [key, value] : root.asObject()) {
|
||||
auto pos = key.rfind('@');
|
||||
if (pos == std::string::npos) {
|
||||
def.properties[key] = value;
|
||||
continue;
|
||||
}
|
||||
auto field = key.substr(0, pos);
|
||||
auto suffix = key.substr(pos + 1);
|
||||
process_method(def.properties, suffix, field, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void process_tags(T& def, const dv::value& root) {
|
||||
if (!root.has("tags")) {
|
||||
return;
|
||||
}
|
||||
const auto& tags = root["tags"];
|
||||
for (const auto& tagValue : tags) {
|
||||
if (!tagValue.isString()) {
|
||||
continue;
|
||||
}
|
||||
def.tags.push_back(tagValue.asString());
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,18 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
|
||||
|
||||
if (auto found = root.at("components")) {
|
||||
for (const auto& elem : *found) {
|
||||
def.components.emplace_back(elem.asString());
|
||||
std::string name;
|
||||
dv::value params;
|
||||
if (elem.isObject()) {
|
||||
name = elem["name"].asString();
|
||||
if (elem.has("args")) {
|
||||
params = elem["args"];
|
||||
}
|
||||
} else {
|
||||
name = elem.asString();
|
||||
}
|
||||
def.components.push_back(ComponentInstance {
|
||||
std::move(name), std::move(params)});
|
||||
}
|
||||
}
|
||||
if (auto found = root.at("hitbox")) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#define VC_ENABLE_REFLECTION
|
||||
#include "ContentUnitLoader.hpp"
|
||||
#include "ContentLoadingCommons.hpp"
|
||||
|
||||
#include "../ContentBuilder.hpp"
|
||||
#include "coders/json.hpp"
|
||||
@@ -12,11 +13,13 @@
|
||||
|
||||
static debug::Logger logger("item-content-loader");
|
||||
|
||||
|
||||
template<> void ContentUnitLoader<ItemDef>::loadUnit(
|
||||
ItemDef& def, const std::string& name, const io::path& file
|
||||
) {
|
||||
auto root = io::read_json(file);
|
||||
def.properties = root;
|
||||
process_properties(def, name, root);
|
||||
process_tags(def, root);
|
||||
|
||||
if (root.has("parent")) {
|
||||
const auto& parentName = root["parent"].asString();
|
||||
@@ -29,6 +32,7 @@ template<> void ContentUnitLoader<ItemDef>::loadUnit(
|
||||
parentDef->cloneTo(def);
|
||||
}
|
||||
root.at("caption").get(def.caption);
|
||||
root.at("description").get(def.description);
|
||||
|
||||
std::string iconTypeStr = "";
|
||||
root.at("icon-type").get(iconTypeStr);
|
||||
|
||||
@@ -46,12 +46,12 @@ namespace dv {
|
||||
if (!map.has(key)) {
|
||||
return;
|
||||
}
|
||||
auto& list = map[key];
|
||||
const auto& srcList = map[key];
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if constexpr (std::is_floating_point<T>()) {
|
||||
vec[i] = list[i].asNumber();
|
||||
vec[i] = srcList[i].asNumber();
|
||||
} else {
|
||||
vec[i] = list[i].asInteger();
|
||||
vec[i] = srcList[i].asInteger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "Project.hpp"
|
||||
|
||||
#include "data/dv_util.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
Project::~Project() = default;
|
||||
|
||||
dv::value Project::serialize() const {
|
||||
return dv::object({
|
||||
|
||||
@@ -2,13 +2,21 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
namespace scripting {
|
||||
class IClientProjectScript;
|
||||
}
|
||||
|
||||
struct Project : Serializable {
|
||||
std::string name;
|
||||
std::string title;
|
||||
std::vector<std::string> basePacks;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
|
||||
~Project();
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
+95
-56
@@ -60,6 +60,17 @@ static std::unique_ptr<ImageData> load_icon() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::unique_ptr<scripting::IClientProjectScript> load_client_project_script() {
|
||||
io::path scriptFile = "project:project_client.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project script";
|
||||
return scripting::load_client_project_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project script does not exists";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Engine::Engine() = default;
|
||||
Engine::~Engine() = default;
|
||||
|
||||
@@ -72,6 +83,74 @@ Engine& Engine::getInstance() {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
void Engine::onContentLoad() {
|
||||
editor->loadTools();
|
||||
langs::setup(langs::get_current(), paths.resPaths.collectRoots());
|
||||
|
||||
if (isHeadless()) {
|
||||
return;
|
||||
}
|
||||
for (auto& pack : content->getAllContentPacks()) {
|
||||
auto configFolder = pack.folder / "config";
|
||||
auto bindsFile = configFolder / "bindings.toml";
|
||||
if (io::is_regular_file(bindsFile)) {
|
||||
input->getBindings().read(
|
||||
toml::parse(
|
||||
bindsFile.string(), io::read_string(bindsFile)
|
||||
),
|
||||
BindType::BIND
|
||||
);
|
||||
}
|
||||
}
|
||||
loadAssets();
|
||||
}
|
||||
|
||||
void Engine::initializeClient() {
|
||||
std::string title = project->title;
|
||||
if (title.empty()) {
|
||||
title = "VoxelCore v" +
|
||||
std::to_string(ENGINE_VERSION_MAJOR) + "." +
|
||||
std::to_string(ENGINE_VERSION_MINOR);
|
||||
}
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
title += " [debug]";
|
||||
}
|
||||
auto [window, input] = Window::initialize(&settings.display, title);
|
||||
if (!window || !input){
|
||||
throw initialize_error("could not initialize window");
|
||||
}
|
||||
window->setFramerate(settings.display.framerate.get());
|
||||
|
||||
time.set(window->time());
|
||||
if (auto icon = load_icon()) {
|
||||
icon->flipY();
|
||||
window->setIcon(icon.get());
|
||||
}
|
||||
this->window = std::move(window);
|
||||
this->input = std::move(input);
|
||||
|
||||
loadControls();
|
||||
|
||||
gui = std::make_unique<gui::GUI>(*this);
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
menus::create_version_label(*gui);
|
||||
}
|
||||
keepAlive(settings.display.fullscreen.observe(
|
||||
[this](bool value) {
|
||||
if (value != this->window->isFullscreen()) {
|
||||
this->window->toggleFullscreen();
|
||||
}
|
||||
},
|
||||
true
|
||||
));
|
||||
keepAlive(settings.debug.doTraceShaders.observe(
|
||||
[](bool value) {
|
||||
Shader::preprocessor->setTraceOutput(value);
|
||||
},
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
void Engine::initialize(CoreParameters coreParameters) {
|
||||
params = std::move(coreParameters);
|
||||
settingsHandler = std::make_unique<SettingsHandler>(settings);
|
||||
@@ -100,78 +179,28 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
|
||||
controller = std::make_unique<EngineController>(*this);
|
||||
if (!params.headless) {
|
||||
std::string title = project->title;
|
||||
if (title.empty()) {
|
||||
title = "VoxelCore v" +
|
||||
std::to_string(ENGINE_VERSION_MAJOR) + "." +
|
||||
std::to_string(ENGINE_VERSION_MINOR);
|
||||
}
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
title += " [debug]";
|
||||
}
|
||||
auto [window, input] = Window::initialize(&settings.display, title);
|
||||
if (!window || !input){
|
||||
throw initialize_error("could not initialize window");
|
||||
}
|
||||
window->setFramerate(settings.display.framerate.get());
|
||||
|
||||
time.set(window->time());
|
||||
if (auto icon = load_icon()) {
|
||||
icon->flipY();
|
||||
window->setIcon(icon.get());
|
||||
}
|
||||
this->window = std::move(window);
|
||||
this->input = std::move(input);
|
||||
|
||||
loadControls();
|
||||
|
||||
gui = std::make_unique<gui::GUI>(*this);
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
menus::create_version_label(*gui);
|
||||
}
|
||||
keepAlive(settings.display.fullscreen.observe(
|
||||
[this](bool value) {
|
||||
if (value != this->window->isFullscreen()) {
|
||||
this->window->toggleFullscreen();
|
||||
}
|
||||
},
|
||||
true
|
||||
));
|
||||
initializeClient();
|
||||
}
|
||||
audio::initialize(!params.headless, settings.audio);
|
||||
|
||||
bool langNotSet = settings.ui.language.get() == "auto";
|
||||
if (langNotSet) {
|
||||
if (settings.ui.language.get() == "auto") {
|
||||
settings.ui.language.set(
|
||||
langs::locale_by_envlocale(platform::detect_locale())
|
||||
);
|
||||
}
|
||||
content = std::make_unique<ContentControl>(*project, paths, *input, [this]() {
|
||||
editor->loadTools();
|
||||
langs::setup(langs::get_current(), paths.resPaths.collectRoots());
|
||||
if (!isHeadless()) {
|
||||
for (auto& pack : content->getAllContentPacks()) {
|
||||
auto configFolder = pack.folder / "config";
|
||||
auto bindsFile = configFolder / "bindings.toml";
|
||||
if (io::is_regular_file(bindsFile)) {
|
||||
input->getBindings().read(
|
||||
toml::parse(
|
||||
bindsFile.string(), io::read_string(bindsFile)
|
||||
),
|
||||
BindType::BIND
|
||||
);
|
||||
}
|
||||
}
|
||||
loadAssets();
|
||||
}
|
||||
onContentLoad();
|
||||
});
|
||||
scripting::initialize(this);
|
||||
|
||||
if (!isHeadless()) {
|
||||
gui->setPageLoader(scripting::create_page_loader());
|
||||
}
|
||||
keepAlive(settings.ui.language.observe([this](auto lang) {
|
||||
langs::setup(lang, paths.resPaths.collectRoots());
|
||||
}, true));
|
||||
|
||||
project->clientScript = load_client_project_script();
|
||||
}
|
||||
|
||||
void Engine::loadSettings() {
|
||||
@@ -286,6 +315,7 @@ void Engine::close() {
|
||||
audio::close();
|
||||
network.reset();
|
||||
clearKeepedObjects();
|
||||
project.reset();
|
||||
scripting::close();
|
||||
logger.info() << "scripting finished";
|
||||
if (!params.headless) {
|
||||
@@ -345,10 +375,19 @@ void Engine::loadProject() {
|
||||
}
|
||||
|
||||
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||
if (project->clientScript && this->screen) {
|
||||
project->clientScript->onScreenChange(this->screen->getName(), false);
|
||||
}
|
||||
// reset audio channels (stop all sources)
|
||||
audio::reset_channel(audio::get_channel_index("regular"));
|
||||
audio::reset_channel(audio::get_channel_index("ambient"));
|
||||
this->screen = std::move(screen);
|
||||
if (this->screen) {
|
||||
this->screen->onOpen();
|
||||
}
|
||||
if (project->clientScript && this->screen) {
|
||||
project->clientScript->onScreenChange(this->screen->getName(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::onWorldOpen(std::unique_ptr<Level> level, int64_t localPlayer) {
|
||||
|
||||
@@ -82,6 +82,9 @@ class Engine : public util::ObjectsKeeper {
|
||||
void updateHotkeys();
|
||||
void loadAssets();
|
||||
void loadProject();
|
||||
|
||||
void initializeClient();
|
||||
void onContentLoad();
|
||||
public:
|
||||
Engine();
|
||||
~Engine();
|
||||
@@ -174,4 +177,8 @@ public:
|
||||
devtools::Editor& getEditor() {
|
||||
return *editor;
|
||||
}
|
||||
|
||||
const Project& getProject() {
|
||||
return *project;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "frontend/screens/LevelScreen.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");
|
||||
|
||||
@@ -36,6 +39,7 @@ void Mainloop::run() {
|
||||
while (!window.isShouldClose()){
|
||||
time.update(window.time());
|
||||
engine.updateFrontend();
|
||||
|
||||
if (!window.isIconified()) {
|
||||
engine.renderFrame();
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ UiDocument::UiDocument(
|
||||
const std::shared_ptr<gui::UINode>& root,
|
||||
scriptenv env
|
||||
) : id(std::move(id)), script(script), root(root), env(std::move(env)) {
|
||||
gui::UINode::getIndices(root, map);
|
||||
rebuildIndices();
|
||||
}
|
||||
|
||||
void UiDocument::rebuildIndices() {
|
||||
map.clear();
|
||||
gui::UINode::getIndices(root, map);
|
||||
map["root"] = root;
|
||||
}
|
||||
|
||||
const UINodesMap& UiDocument::getMap() const {
|
||||
|
||||
@@ -12,12 +12,14 @@
|
||||
#include "graphics/render/WorldRenderer.hpp"
|
||||
#include "graphics/render/ParticlesRenderer.hpp"
|
||||
#include "graphics/render/ChunksRenderer.hpp"
|
||||
#include "graphics/render/DebugLinesRenderer.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
@@ -44,6 +46,7 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
|
||||
// TODO: move to xml
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
std::shared_ptr<UINode> create_debug_panel(
|
||||
Engine& engine,
|
||||
Level& level,
|
||||
@@ -260,6 +263,18 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
});
|
||||
panel->add(checkbox);
|
||||
}
|
||||
{
|
||||
auto checkbox = std::make_shared<FullCheckBox>(
|
||||
gui, L"Show Paths", glm::vec2(400, 24)
|
||||
);
|
||||
checkbox->setSupplier([=]() {
|
||||
return DebugLinesRenderer::showPaths;
|
||||
});
|
||||
checkbox->setConsumer([=](bool checked) {
|
||||
DebugLinesRenderer::showPaths = checked;
|
||||
});
|
||||
panel->add(checkbox);
|
||||
}
|
||||
{
|
||||
auto checkbox = std::make_shared<FullCheckBox>(
|
||||
gui, L"Show Generator Minimap", glm::vec2(400, 24)
|
||||
|
||||
@@ -207,6 +207,9 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
|
||||
}
|
||||
|
||||
Hud::~Hud() {
|
||||
if (input.isCursorLocked()) {
|
||||
input.toggleCursor();
|
||||
}
|
||||
// removing all controlled ui
|
||||
for (auto& element : elements) {
|
||||
onRemove(element);
|
||||
@@ -324,7 +327,7 @@ void Hud::updateWorldGenDebug() {
|
||||
|
||||
void Hud::update(bool visible) {
|
||||
const auto& chunks = *player.chunks;
|
||||
bool is_menu_open = menu.hasOpenPage();
|
||||
bool isMenuOpen = menu.hasOpenPage();
|
||||
|
||||
debugPanel->setVisible(
|
||||
debug && visible && !(inventoryOpen && inventoryView == nullptr)
|
||||
@@ -333,13 +336,13 @@ void Hud::update(bool visible) {
|
||||
if (!visible && inventoryOpen) {
|
||||
closeInventory();
|
||||
}
|
||||
if (pause && !is_menu_open) {
|
||||
if (pause && !isMenuOpen) {
|
||||
setPause(false);
|
||||
}
|
||||
if (!gui.isFocusCaught()) {
|
||||
processInput(visible);
|
||||
}
|
||||
if ((is_menu_open || inventoryOpen) == input.getCursor().locked) {
|
||||
if ((isMenuOpen || inventoryOpen) == input.isCursorLocked()) {
|
||||
input.toggleCursor();
|
||||
}
|
||||
|
||||
@@ -360,8 +363,8 @@ void Hud::update(bool visible) {
|
||||
contentAccessPanel->setSize(glm::vec2(caSize.x, windowSize.y));
|
||||
contentAccess->setMinSize(glm::vec2(1, windowSize.y));
|
||||
hotbarView->setVisible(visible && !(secondUI && !inventoryView));
|
||||
darkOverlay->setVisible(is_menu_open);
|
||||
menu.setVisible(is_menu_open);
|
||||
darkOverlay->setVisible(isMenuOpen);
|
||||
menu.setVisible(isMenuOpen);
|
||||
|
||||
if (visible) {
|
||||
for (auto& element : elements) {
|
||||
|
||||
@@ -97,7 +97,6 @@ LevelScreen::LevelScreen(
|
||||
animator->addAnimations(assets.getAnimations());
|
||||
|
||||
loadDecorations();
|
||||
initializeContent();
|
||||
}
|
||||
|
||||
LevelScreen::~LevelScreen() {
|
||||
@@ -112,6 +111,10 @@ LevelScreen::~LevelScreen() {
|
||||
engine.getPaths().setCurrentWorldFolder("");
|
||||
}
|
||||
|
||||
void LevelScreen::onOpen() {
|
||||
initializeContent();
|
||||
}
|
||||
|
||||
void LevelScreen::initializeContent() {
|
||||
auto& content = controller->getLevel()->content;
|
||||
for (auto& entry : content.getPacks()) {
|
||||
@@ -173,7 +176,7 @@ void LevelScreen::saveWorldPreview() {
|
||||
static_cast<uint>(previewSize)}
|
||||
);
|
||||
|
||||
renderer->draw(ctx, camera, false, true, 0.0f, *postProcessing);
|
||||
renderer->renderFrame(ctx, camera, false, true, 0.0f, *postProcessing);
|
||||
auto image = postProcessing->toImage();
|
||||
image->flipY();
|
||||
imageio::write("world:preview.png", image.get());
|
||||
@@ -260,7 +263,7 @@ void LevelScreen::draw(float delta) {
|
||||
if (!hud->isPause()) {
|
||||
scripting::on_entities_render(engine.getTime().getDelta());
|
||||
}
|
||||
renderer->draw(
|
||||
renderer->renderFrame(
|
||||
ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing
|
||||
);
|
||||
|
||||
|
||||
@@ -53,8 +53,13 @@ public:
|
||||
);
|
||||
~LevelScreen();
|
||||
|
||||
void onOpen() override;
|
||||
void update(float delta) override;
|
||||
void draw(float delta) override;
|
||||
|
||||
void onEngineShutdown() override;
|
||||
|
||||
const char* getName() const override {
|
||||
return "level";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,12 +13,6 @@
|
||||
#include "engine/Engine.hpp"
|
||||
|
||||
MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
|
||||
engine.getContentControl().resetContent();
|
||||
|
||||
auto menu = engine.getGUI().getMenu();
|
||||
menu->reset();
|
||||
menu->setPage("main");
|
||||
|
||||
uicamera =
|
||||
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y);
|
||||
uicamera->perspective = false;
|
||||
@@ -29,33 +23,17 @@ MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
|
||||
|
||||
MenuScreen::~MenuScreen() = default;
|
||||
|
||||
void MenuScreen::onOpen() {
|
||||
engine.getContentControl().resetContent();
|
||||
|
||||
auto menu = engine.getGUI().getMenu();
|
||||
menu->reset();
|
||||
}
|
||||
|
||||
void MenuScreen::update(float delta) {
|
||||
}
|
||||
|
||||
void MenuScreen::draw(float delta) {
|
||||
auto assets = engine.getAssets();
|
||||
|
||||
display::clear();
|
||||
display::setBgColor(glm::vec3(0.2f));
|
||||
|
||||
const auto& size = engine.getWindow().getSize();
|
||||
uint width = size.x;
|
||||
uint height = size.y;
|
||||
|
||||
uicamera->setFov(height);
|
||||
uicamera->setAspectRatio(width / static_cast<float>(height));
|
||||
auto uishader = assets->get<Shader>("ui");
|
||||
uishader->use();
|
||||
uishader->uniformMatrix("u_projview", uicamera->getProjView());
|
||||
|
||||
auto bg = assets->get<Texture>("gui/menubg");
|
||||
batch->begin();
|
||||
batch->texture(bg);
|
||||
batch->rect(
|
||||
0, 0,
|
||||
width, height, 0, 0, 0,
|
||||
UVRegion(0, 0, width / bg->getWidth(), height / bg->getHeight()),
|
||||
false, false, glm::vec4(1.0f)
|
||||
);
|
||||
batch->flush();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,12 @@ public:
|
||||
MenuScreen(Engine& engine);
|
||||
~MenuScreen();
|
||||
|
||||
void onOpen() override;
|
||||
|
||||
void update(float delta) override;
|
||||
void draw(float delta) override;
|
||||
|
||||
const char* getName() const override {
|
||||
return "menu";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,7 +13,9 @@ protected:
|
||||
public:
|
||||
Screen(Engine& engine);
|
||||
virtual ~Screen();
|
||||
virtual void onOpen() = 0;
|
||||
virtual void update(float delta) = 0;
|
||||
virtual void draw(float delta) = 0;
|
||||
virtual void onEngineShutdown() {};
|
||||
virtual const char* getName() const = 0;
|
||||
};
|
||||
|
||||
@@ -57,6 +57,14 @@ static inline void draw_glyph(
|
||||
const FontStyle& style
|
||||
) {
|
||||
for (int i = 0; i <= style.bold; i++) {
|
||||
glm::vec4 color;
|
||||
|
||||
if (style.color == glm::vec4(1, 1, 1, 1)) {
|
||||
color = batch.getColor();
|
||||
} else {
|
||||
color = style.color;
|
||||
}
|
||||
|
||||
batch.sprite(
|
||||
pos.x + (offset.x + i / (right.x/glyphInterval/2.0f)) * right.x,
|
||||
pos.y + offset.y * right.y,
|
||||
@@ -65,7 +73,7 @@ static inline void draw_glyph(
|
||||
-0.15f * style.italic,
|
||||
16,
|
||||
c,
|
||||
batch.getColor() * style.color
|
||||
color
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -81,6 +89,15 @@ static inline void draw_glyph(
|
||||
const FontStyle& style
|
||||
) {
|
||||
for (int i = 0; i <= style.bold; i++) {
|
||||
glm::vec4 color;
|
||||
|
||||
if (style.color == glm::vec4(1, 1, 1, 1)) {
|
||||
color = batch.getColor();
|
||||
} else {
|
||||
color = style.color;
|
||||
}
|
||||
|
||||
|
||||
batch.sprite(
|
||||
pos + right * (offset.x + i) + up * offset.y,
|
||||
up, right / glyphInterval,
|
||||
@@ -88,7 +105,7 @@ static inline void draw_glyph(
|
||||
0.5f,
|
||||
16,
|
||||
c,
|
||||
batch.getColor() * style.color
|
||||
color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ImageData.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <assert.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
@@ -187,6 +188,41 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color
|
||||
}
|
||||
}
|
||||
|
||||
template<uint channels>
|
||||
static void draw_rect(ImageData& image, int dstX, int dstY, int width, int height, const glm::ivec4& color) {
|
||||
ubyte* data = image.getData();
|
||||
int imageWidth = image.getWidth();
|
||||
int imageHeight = image.getHeight();
|
||||
|
||||
int x1 = glm::min(glm::max(dstX, 0), imageWidth - 1);
|
||||
int y1 = glm::min(glm::max(dstY, 0), imageHeight - 1);
|
||||
|
||||
int x2 = glm::min(glm::max(dstX + width, 0), imageWidth - 1);
|
||||
int y2 = glm::min(glm::max(dstY + height, 0), imageHeight - 1);
|
||||
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
int index = (y * imageWidth + x) * channels;
|
||||
for (int i = 0; i < channels; i++) {
|
||||
data[index + i] = color[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) {
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888:
|
||||
draw_rect<3>(*this, x, y, width, height, color);
|
||||
break;
|
||||
case ImageFormat::rgba8888:
|
||||
draw_rect<4>(*this, x, y, width, height, color);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
|
||||
ubyte* source = image.getData();
|
||||
uint srcwidth = image.getWidth();
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
void flipY();
|
||||
|
||||
void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color);
|
||||
void drawRect(int x, int y, int width, int height, const glm::ivec4& color);
|
||||
void blit(const ImageData& image, int x, int y);
|
||||
void extrude(int x, int y, int w, int h);
|
||||
void fixAlphaColor();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -138,15 +137,21 @@ glshader compile_shader(GLenum type, const GLchar* source, const std::string& fi
|
||||
}
|
||||
|
||||
static GLuint compile_program(
|
||||
const Shader::Source& vertexSource, const Shader::Source& fragmentSource
|
||||
const Shader::Source& vertexSource,
|
||||
const Shader::Source& fragmentSource,
|
||||
const std::vector<std::string>& defines
|
||||
) {
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
|
||||
auto vertexCode = std::move(
|
||||
preprocessor.process(vertexSource.file, vertexSource.code).code
|
||||
preprocessor
|
||||
.process(vertexSource.file, vertexSource.code, false, defines)
|
||||
.code
|
||||
);
|
||||
auto fragmentCode = std::move(
|
||||
preprocessor.process(fragmentSource.file, fragmentSource.code).code
|
||||
preprocessor
|
||||
.process(fragmentSource.file, fragmentSource.code, false, defines)
|
||||
.code
|
||||
);
|
||||
|
||||
const GLchar* vCode = vertexCode.c_str();
|
||||
@@ -176,8 +181,8 @@ static GLuint compile_program(
|
||||
return program;
|
||||
}
|
||||
|
||||
void Shader::recompile() {
|
||||
GLuint newProgram = compile_program(vertexSource, fragmentSource);
|
||||
void Shader::recompile(const std::vector<std::string>& defines) {
|
||||
GLuint newProgram = compile_program(vertexSource, fragmentSource, defines);
|
||||
glDeleteProgram(id);
|
||||
id = newProgram;
|
||||
uniformLocations.clear();
|
||||
@@ -188,7 +193,7 @@ std::unique_ptr<Shader> Shader::create(
|
||||
Source&& vertexSource, Source&& fragmentSource
|
||||
) {
|
||||
return std::make_unique<Shader>(
|
||||
compile_program(vertexSource, fragmentSource),
|
||||
compile_program(vertexSource, fragmentSource, {}),
|
||||
std::move(vertexSource),
|
||||
std::move(fragmentSource)
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
@@ -50,7 +51,7 @@ public:
|
||||
void uniform4v(const std::string& name, int length, const float* v);
|
||||
|
||||
/// @brief Re-preprocess source code and re-compile shader program
|
||||
void recompile();
|
||||
void recompile(const std::vector<std::string>& defines);
|
||||
|
||||
/// @brief Create shader program using vertex and fragment shaders source.
|
||||
/// @return linked shader program containing vertex and fragment shaders
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "ShadowMap.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
ShadowMap::ShadowMap(int resolution) : resolution(resolution) {
|
||||
glGenTextures(1, &depthMap);
|
||||
glBindTexture(GL_TEXTURE_2D, depthMap);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
|
||||
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||
float border[4] {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, border);
|
||||
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
|
||||
glDrawBuffer(GL_NONE);
|
||||
glReadBuffer(GL_NONE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
ShadowMap::~ShadowMap() {
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
glDeleteTextures(1, &depthMap);
|
||||
}
|
||||
|
||||
void ShadowMap::bind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void ShadowMap::unbind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
uint ShadowMap::getDepthMap() const {
|
||||
return depthMap;
|
||||
}
|
||||
|
||||
int ShadowMap::getResolution() const {
|
||||
return resolution;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
class ShadowMap {
|
||||
public:
|
||||
ShadowMap(int resolution);
|
||||
~ShadowMap();
|
||||
|
||||
void bind();
|
||||
void unbind();
|
||||
uint getDepthMap() const;
|
||||
int getResolution() const;
|
||||
private:
|
||||
uint fbo;
|
||||
uint depthMap;
|
||||
int resolution;
|
||||
};
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "Shadows.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/commons.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/Weather.hpp"
|
||||
#include "world/World.hpp"
|
||||
|
||||
using namespace advanced_pipeline;
|
||||
|
||||
inline constexpr int MIN_SHADOW_MAP_RES = 512;
|
||||
inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0;
|
||||
|
||||
class ShadowMap {
|
||||
public:
|
||||
ShadowMap(int resolution) : resolution(resolution) {
|
||||
glGenTextures(1, &depthMap);
|
||||
glBindTexture(GL_TEXTURE_2D, depthMap);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
|
||||
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(
|
||||
GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE
|
||||
);
|
||||
float border[4] {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border);
|
||||
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glFramebufferTexture2D(
|
||||
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0
|
||||
);
|
||||
glDrawBuffer(GL_NONE);
|
||||
glReadBuffer(GL_NONE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
~ShadowMap() {
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
glDeleteTextures(1, &depthMap);
|
||||
}
|
||||
|
||||
void bind(){
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void unbind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
uint getDepthMap() const {
|
||||
return depthMap;
|
||||
}
|
||||
int getResolution() const {
|
||||
return resolution;
|
||||
}
|
||||
private:
|
||||
uint fbo;
|
||||
uint depthMap;
|
||||
int resolution;
|
||||
};
|
||||
|
||||
Shadows::Shadows(const Level& level) : level(level) {}
|
||||
|
||||
Shadows::~Shadows() = default;
|
||||
|
||||
void Shadows::setQuality(int quality) {
|
||||
int resolution = MIN_SHADOW_MAP_RES << quality;
|
||||
if (quality > 0 && !shadows) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
shadows = true;
|
||||
} else if (quality == 0 && shadows) {
|
||||
shadowMap.reset();
|
||||
wideShadowMap.reset();
|
||||
shadows = false;
|
||||
}
|
||||
if (shadows && shadowMap->getResolution() != resolution) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
}
|
||||
this->quality = quality;
|
||||
}
|
||||
|
||||
void Shadows::setup(Shader& shader, const Weather& weather) {
|
||||
if (shadows) {
|
||||
const auto& worldInfo = level.getWorld()->getInfo();
|
||||
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
|
||||
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_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
|
||||
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
|
||||
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
|
||||
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
|
||||
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(TEXTURE_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
void Shadows::refresh(const Camera& camera, const DrawContext& pctx, std::function<void(Camera&)> renderShadowPass) {
|
||||
static int frameid = 0;
|
||||
if (shadows) {
|
||||
if (frameid % 2 == 0) {
|
||||
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass);
|
||||
} else {
|
||||
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f, renderShadowPass);
|
||||
}
|
||||
}
|
||||
frameid++;
|
||||
}
|
||||
|
||||
void Shadows::generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale,
|
||||
std::function<void(Camera&)> renderShadowPass
|
||||
) {
|
||||
auto world = level.getWorld();
|
||||
const auto& worldInfo = world->getInfo();
|
||||
|
||||
int resolution = shadowMap.getResolution();
|
||||
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;
|
||||
float shadowMapSize = resolution * shadowMapScale;
|
||||
|
||||
glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f;
|
||||
glm::vec3 prevPos = shadowCamera.position;
|
||||
shadowCamera = Camera(
|
||||
glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos,
|
||||
shadowMapSize
|
||||
);
|
||||
shadowCamera.near = 0.1f;
|
||||
shadowCamera.far = 1000.0f;
|
||||
shadowCamera.perspective = false;
|
||||
shadowCamera.setAspectRatio(1.0f);
|
||||
|
||||
float t = worldInfo.daytime - 0.25f;
|
||||
if (t < 0.0f) {
|
||||
t += 1.0f;
|
||||
}
|
||||
t = fmod(t, 0.5f);
|
||||
|
||||
float sunCycleStep = 1.0f / 500.0f;
|
||||
float sunAngle = glm::radians(
|
||||
90.0f -
|
||||
((static_cast<int>(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f
|
||||
);
|
||||
float sunAltitude = glm::pi<float>() * 0.25f;
|
||||
shadowCamera.rotate(
|
||||
-glm::cos(sunAngle + glm::pi<float>() * 0.5f) * sunAltitude,
|
||||
sunAngle - glm::pi<float>() * 0.5f,
|
||||
glm::radians(0.0f)
|
||||
);
|
||||
|
||||
shadowCamera.position -= shadowCamera.front * 500.0f;
|
||||
shadowCamera.position += shadowCamera.up * 0.0f;
|
||||
shadowCamera.position += camera.front * 0.0f;
|
||||
|
||||
auto view = shadowCamera.getView();
|
||||
|
||||
auto currentPos = shadowCamera.position;
|
||||
auto topRight = shadowCamera.right + shadowCamera.up;
|
||||
auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
|
||||
shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f));
|
||||
|
||||
{
|
||||
auto sctx = pctx.sub();
|
||||
sctx.setDepthTest(true);
|
||||
sctx.setCullFace(true);
|
||||
sctx.setViewport({resolution, resolution});
|
||||
shadowMap.bind();
|
||||
if (renderShadowPass) {
|
||||
renderShadowPass(shadowCamera);
|
||||
}
|
||||
shadowMap.unbind();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
|
||||
class Shader;
|
||||
class Level;
|
||||
class Assets;
|
||||
struct Weather;
|
||||
class DrawContext;
|
||||
struct EngineSettings;
|
||||
class ShadowMap;
|
||||
|
||||
class Shadows {
|
||||
public:
|
||||
Shadows(const Level& level);
|
||||
~Shadows();
|
||||
|
||||
void setup(Shader& shader, const Weather& weather);
|
||||
void setQuality(int quality);
|
||||
void refresh(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
std::function<void(Camera&)> renderShadowPass
|
||||
);
|
||||
private:
|
||||
const Level& level;
|
||||
bool shadows = false;
|
||||
Camera shadowCamera;
|
||||
Camera wideShadowCamera;
|
||||
std::unique_ptr<ShadowMap> shadowMap;
|
||||
std::unique_ptr<ShadowMap> wideShadowMap;
|
||||
int quality = 0;
|
||||
|
||||
void generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale,
|
||||
std::function<void(Camera&)> renderShadowPass
|
||||
);
|
||||
};
|
||||
@@ -171,6 +171,9 @@ const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
|
||||
if (mesh == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (chunk->flags.dirtyHeights) {
|
||||
chunk->updateHeights();
|
||||
}
|
||||
if (culling) {
|
||||
glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D);
|
||||
glm::vec3 max(
|
||||
@@ -184,7 +187,7 @@ const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void ChunksRenderer::drawChunksShadowsPass(
|
||||
void ChunksRenderer::drawShadowsPass(
|
||||
const Camera& camera, Shader& shader, const Camera& playerCamera
|
||||
) {
|
||||
Frustum frustum;
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
const std::shared_ptr<Chunk>& chunk, bool important
|
||||
);
|
||||
|
||||
void drawChunksShadowsPass(
|
||||
void drawShadowsPass(
|
||||
const Camera& camera, Shader& shader, const Camera& playerCamera
|
||||
);
|
||||
|
||||
|
||||
+57
-13
@@ -1,15 +1,51 @@
|
||||
#include "GuidesRenderer.hpp"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include "DebugLinesRenderer.hpp"
|
||||
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "graphics/core/LineBatch.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/render/LinesRenderer.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "maths/voxmaths.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "constants.hpp"
|
||||
|
||||
void GuidesRenderer::drawBorders(
|
||||
bool DebugLinesRenderer::showPaths = false;
|
||||
|
||||
static void draw_route(
|
||||
LinesRenderer& lines, const voxels::Agent& agent
|
||||
) {
|
||||
const auto& route = agent.route;
|
||||
if (!route.found)
|
||||
return;
|
||||
|
||||
for (int i = 1; i < route.nodes.size(); i++) {
|
||||
const auto& a = route.nodes.at(i - 1);
|
||||
const auto& b = route.nodes.at(i);
|
||||
|
||||
if (i == 1) {
|
||||
lines.pushLine(
|
||||
glm::vec3(a.pos) + glm::vec3(0.5f),
|
||||
glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f),
|
||||
glm::vec4(1, 1, 1, 1)
|
||||
);
|
||||
}
|
||||
|
||||
lines.pushLine(
|
||||
glm::vec3(a.pos) + glm::vec3(0.5f),
|
||||
glm::vec3(b.pos) + glm::vec3(0.5f),
|
||||
glm::vec4(1, 0, 1, 1)
|
||||
);
|
||||
|
||||
lines.pushLine(
|
||||
glm::vec3(b.pos) + glm::vec3(0.5f),
|
||||
glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f),
|
||||
glm::vec4(1, 1, 1, 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugLinesRenderer::drawBorders(
|
||||
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
|
||||
) {
|
||||
int ww = ex - sx;
|
||||
@@ -37,7 +73,7 @@ void GuidesRenderer::drawBorders(
|
||||
batch.flush();
|
||||
}
|
||||
|
||||
void GuidesRenderer::drawCoordSystem(
|
||||
void DebugLinesRenderer::drawCoordSystem(
|
||||
LineBatch& batch, const DrawContext& pctx, float length
|
||||
) {
|
||||
auto ctx = pctx.sub();
|
||||
@@ -55,14 +91,22 @@ void GuidesRenderer::drawCoordSystem(
|
||||
batch.line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
void GuidesRenderer::renderDebugLines(
|
||||
const DrawContext& pctx,
|
||||
|
||||
void DebugLinesRenderer::render(
|
||||
DrawContext& pctx,
|
||||
const Camera& camera,
|
||||
LineBatch& batch,
|
||||
LinesRenderer& renderer,
|
||||
LineBatch& linesBatch,
|
||||
Shader& linesShader,
|
||||
bool showChunkBorders
|
||||
) {
|
||||
DrawContext ctx = pctx.sub(&batch);
|
||||
// In-world lines
|
||||
if (showPaths) {
|
||||
for (const auto& [_, agent] : level.pathfinding->getAgents()) {
|
||||
draw_route(renderer, agent);
|
||||
}
|
||||
}
|
||||
DrawContext ctx = pctx.sub(&linesBatch);
|
||||
const auto& viewport = ctx.getViewport();
|
||||
|
||||
ctx.setDepthTest(true);
|
||||
@@ -78,7 +122,7 @@ void GuidesRenderer::renderDebugLines(
|
||||
int cz = floordiv(static_cast<int>(coord.z), CHUNK_D);
|
||||
|
||||
drawBorders(
|
||||
batch,
|
||||
linesBatch,
|
||||
cx * CHUNK_W,
|
||||
0,
|
||||
cz * CHUNK_D,
|
||||
@@ -103,5 +147,5 @@ void GuidesRenderer::renderDebugLines(
|
||||
) * model *
|
||||
glm::inverse(camera.rotation)
|
||||
);
|
||||
drawCoordSystem(batch, ctx, length);
|
||||
drawCoordSystem(linesBatch, ctx, length);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
class DrawContext;
|
||||
class Camera;
|
||||
class LineBatch;
|
||||
class LinesRenderer;
|
||||
class Shader;
|
||||
class Level;
|
||||
|
||||
class DebugLinesRenderer {
|
||||
public:
|
||||
static bool showPaths;
|
||||
|
||||
DebugLinesRenderer(const Level& level)
|
||||
: level(level) {};
|
||||
|
||||
/// @brief Render debug lines in the world
|
||||
/// @param ctx Draw context
|
||||
/// @param camera Camera used for rendering
|
||||
/// @param renderer Lines renderer used for rendering lines
|
||||
/// @param linesShader Shader used for rendering lines
|
||||
/// @param showChunkBorders Whether to show chunk borders
|
||||
void render(
|
||||
DrawContext& ctx,
|
||||
const Camera& camera,
|
||||
LinesRenderer& renderer,
|
||||
LineBatch& linesBatch,
|
||||
Shader& linesShader,
|
||||
bool showChunkBorders
|
||||
);
|
||||
private:
|
||||
const Level& level;
|
||||
|
||||
void drawBorders(
|
||||
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
|
||||
);
|
||||
void drawCoordSystem(
|
||||
LineBatch& batch, const DrawContext& pctx, float length
|
||||
);
|
||||
|
||||
};
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "window/Camera.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
Emitter::Emitter(
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class LineBatch;
|
||||
class DrawContext;
|
||||
class Camera;
|
||||
class Shader;
|
||||
|
||||
class GuidesRenderer {
|
||||
public:
|
||||
void drawBorders(
|
||||
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
|
||||
);
|
||||
void drawCoordSystem(
|
||||
LineBatch& batch, const DrawContext& pctx, float length
|
||||
);
|
||||
|
||||
/// @brief Render all debug lines (chunks borders, coord system guides)
|
||||
/// @param context graphics context
|
||||
/// @param camera active camera
|
||||
/// @param linesShader shader used
|
||||
void renderDebugLines(
|
||||
const DrawContext& context,
|
||||
const Camera& camera,
|
||||
LineBatch& batch,
|
||||
Shader& linesShader,
|
||||
bool showChunkBorders
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "HandsRenderer.hpp"
|
||||
|
||||
#include <glm/ext.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "ModelBatch.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "graphics/commons/Model.hpp"
|
||||
#include "objects/rigging.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
|
||||
using namespace rigging;
|
||||
|
||||
HandsRenderer::HandsRenderer(
|
||||
const Assets& assets,
|
||||
ModelBatch& modelBatch,
|
||||
std::shared_ptr<Skeleton> skeleton
|
||||
)
|
||||
: assets(assets),
|
||||
modelBatch(modelBatch),
|
||||
skeleton(std::move(skeleton)) {
|
||||
}
|
||||
|
||||
void HandsRenderer::renderHands(
|
||||
const Camera& camera, float delta
|
||||
) {
|
||||
auto& skeleton = *this->skeleton;
|
||||
const auto& config = *skeleton.config;
|
||||
|
||||
// render
|
||||
modelBatch.setLightsOffset(camera.position);
|
||||
config.update(skeleton, glm::mat4(1.0f), glm::vec3());
|
||||
config.render(assets, modelBatch, skeleton, glm::mat4(1.0f), glm::vec3());
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Assets;
|
||||
class Camera;
|
||||
class ModelBatch;
|
||||
|
||||
namespace rigging {
|
||||
struct Skeleton;
|
||||
}
|
||||
|
||||
class HandsRenderer {
|
||||
public:
|
||||
HandsRenderer(
|
||||
const Assets& assets,
|
||||
ModelBatch& modelBatch,
|
||||
std::shared_ptr<rigging::Skeleton> skeleton
|
||||
);
|
||||
|
||||
void renderHands(const Camera& camera, float delta);
|
||||
private:
|
||||
const Assets& assets;
|
||||
ModelBatch& modelBatch;
|
||||
std::shared_ptr<rigging::Skeleton> skeleton;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "LinesRenderer.hpp"
|
||||
|
||||
#include "graphics/core/LineBatch.hpp"
|
||||
|
||||
void LinesRenderer::draw(LineBatch& batch) {
|
||||
for (const auto& line : queue) {
|
||||
batch.line(line.a, line.b, line.color);
|
||||
}
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
void LinesRenderer::pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color) {
|
||||
queue.push_back({a, b, color});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
#include <vector>
|
||||
|
||||
class LineBatch;
|
||||
|
||||
class LinesRenderer {
|
||||
public:
|
||||
struct Line {
|
||||
glm::vec3 a;
|
||||
glm::vec3 b;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
void draw(LineBatch& batch);
|
||||
|
||||
void pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color);
|
||||
private:
|
||||
std::vector<Line> queue;
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "core_defs.hpp"
|
||||
|
||||
static debug::Logger logger("models-generator");
|
||||
|
||||
@@ -67,11 +68,18 @@ void ModelsGenerator::prepareModel(
|
||||
} else {
|
||||
auto srcModel = assets.get<model::Model>(blockModel.name);
|
||||
if (srcModel) {
|
||||
bool defaultAssigned = variant.textureFaces[0] != TEXTURE_NOTFOUND;
|
||||
auto model = std::make_unique<model::Model>(*srcModel);
|
||||
for (auto& mesh : model->meshes) {
|
||||
if (mesh.texture.length() && mesh.texture[0] == '$') {
|
||||
int index = std::stoll(mesh.texture.substr(1));
|
||||
mesh.texture = "blocks:" + variant.textureFaces[index];
|
||||
} else if (!defaultAssigned && !mesh.texture.empty()) {
|
||||
size_t sepPos = mesh.texture.find(':');
|
||||
if (sepPos == std::string::npos)
|
||||
continue;
|
||||
variant.textureFaces[0] = mesh.texture.substr(sepPos + 1);
|
||||
defaultAssigned = true;
|
||||
}
|
||||
}
|
||||
blockModel.name = modelName;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "NamedSkeletons.hpp"
|
||||
|
||||
#include "objects/rigging.hpp"
|
||||
|
||||
using namespace rigging;
|
||||
|
||||
NamedSkeletons::NamedSkeletons() = default;
|
||||
|
||||
std::shared_ptr<rigging::Skeleton> NamedSkeletons::createSkeleton(
|
||||
const std::string& name, const SkeletonConfig* config
|
||||
) {
|
||||
auto skeleton = std::make_shared<Skeleton>(config);
|
||||
skeletons[name] = skeleton;
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
rigging::Skeleton* NamedSkeletons::getSkeleton(const std::string& name) {
|
||||
const auto& found = skeletons.find(name);
|
||||
if (found == skeletons.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return found->second.get();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace rigging {
|
||||
struct Skeleton;
|
||||
class SkeletonConfig;
|
||||
}
|
||||
|
||||
class NamedSkeletons {
|
||||
public:
|
||||
NamedSkeletons();
|
||||
|
||||
std::shared_ptr<rigging::Skeleton> createSkeleton(
|
||||
const std::string& name, const rigging::SkeletonConfig* config
|
||||
);
|
||||
|
||||
rigging::Skeleton* getSkeleton(const std::string& name);
|
||||
private:
|
||||
std::unordered_map<std::string, std::shared_ptr<rigging::Skeleton>> skeletons;
|
||||
};
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/LevelEvents.hpp"
|
||||
@@ -42,14 +43,17 @@
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/core/Font.hpp"
|
||||
#include "graphics/core/ShadowMap.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 "GuidesRenderer.hpp"
|
||||
#include "LinesRenderer.hpp"
|
||||
#include "DebugLinesRenderer.hpp"
|
||||
#include "ModelBatch.hpp"
|
||||
#include "Skybox.hpp"
|
||||
#include "Emitter.hpp"
|
||||
@@ -59,8 +63,6 @@ using namespace advanced_pipeline;
|
||||
|
||||
inline constexpr size_t BATCH3D_CAPACITY = 4096;
|
||||
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
|
||||
inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0;
|
||||
inline constexpr int MIN_SHADOW_MAP_RES = 512;
|
||||
|
||||
bool WorldRenderer::showChunkBorders = false;
|
||||
bool WorldRenderer::showEntitiesDebug = false;
|
||||
@@ -78,8 +80,7 @@ WorldRenderer::WorldRenderer(
|
||||
modelBatch(std::make_unique<ModelBatch>(
|
||||
MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings()
|
||||
)),
|
||||
guides(std::make_unique<GuidesRenderer>()),
|
||||
chunks(std::make_unique<ChunksRenderer>(
|
||||
chunksRenderer(std::make_unique<ChunksRenderer>(
|
||||
&level,
|
||||
*player.chunks,
|
||||
assets,
|
||||
@@ -100,17 +101,42 @@ WorldRenderer::WorldRenderer(
|
||||
auto& settings = engine.getSettings();
|
||||
level.events->listen(
|
||||
LevelEventType::CHUNK_HIDDEN,
|
||||
[this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); }
|
||||
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
|
||||
);
|
||||
auto assets = engine.getAssets();
|
||||
skybox = std::make_unique<Skybox>(
|
||||
settings.graphics.skyboxResolution.get(),
|
||||
assets->require<Shader>("skybox_gen")
|
||||
);
|
||||
|
||||
const auto& content = level.content;
|
||||
skeletons = std::make_unique<NamedSkeletons>();
|
||||
const auto& skeletonConfig = content.requireSkeleton(
|
||||
content.getDefaults()["hand-skeleton"].asString()
|
||||
);
|
||||
hands = std::make_unique<HandsRenderer>(
|
||||
*assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig)
|
||||
);
|
||||
lines = std::make_unique<LinesRenderer>();
|
||||
shadowMapping = std::make_unique<Shadows>(level);
|
||||
debugLines = std::make_unique<DebugLinesRenderer>(level);
|
||||
}
|
||||
|
||||
WorldRenderer::~WorldRenderer() = default;
|
||||
|
||||
static void setup_weather(Shader& shader, const Weather& weather) {
|
||||
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
|
||||
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
|
||||
shader.uniform1f("u_weatherFogCurve", weather.fogCurve());
|
||||
}
|
||||
|
||||
static void setup_camera(Shader& shader, const Camera& camera) {
|
||||
shader.uniformMatrix("u_model", glm::mat4(1.0f));
|
||||
shader.uniformMatrix("u_proj", camera.getProjection());
|
||||
shader.uniformMatrix("u_view", camera.getView());
|
||||
shader.uniform3f("u_cameraPos", camera.position);
|
||||
}
|
||||
|
||||
void WorldRenderer::setupWorldShader(
|
||||
Shader& shader,
|
||||
const Camera& camera,
|
||||
@@ -118,45 +144,20 @@ void WorldRenderer::setupWorldShader(
|
||||
float fogFactor
|
||||
) {
|
||||
shader.use();
|
||||
shader.uniformMatrix("u_model", glm::mat4(1.0f));
|
||||
shader.uniformMatrix("u_proj", camera.getProjection());
|
||||
shader.uniformMatrix("u_view", camera.getView());
|
||||
|
||||
setup_camera(shader, camera);
|
||||
setup_weather(shader, weather);
|
||||
shadowMapping->setup(shader, weather);
|
||||
|
||||
shader.uniform1f("u_timer", timer);
|
||||
shader.uniform1f("u_gamma", settings.graphics.gamma.get());
|
||||
shader.uniform1f("u_fogFactor", fogFactor);
|
||||
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
|
||||
shader.uniform1i("u_debugLights", lightsDebug);
|
||||
shader.uniform1i("u_debugNormals", false);
|
||||
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
|
||||
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
|
||||
shader.uniform1f("u_weatherFogCurve", weather.fogCurve());
|
||||
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
|
||||
shader.uniform2f("u_lightDir", skybox->getLightDir());
|
||||
shader.uniform3f("u_cameraPos", camera.position);
|
||||
shader.uniform1i("u_skybox", 1);
|
||||
shader.uniform1i("u_enableShadows", shadows);
|
||||
|
||||
if (shadows) {
|
||||
const auto& worldInfo = level.getWorld()->getInfo();
|
||||
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
|
||||
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_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
|
||||
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
|
||||
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
|
||||
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
|
||||
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(TEXTURE_MAIN);
|
||||
}
|
||||
shader.uniform1i("u_skybox", TARGET_SKYBOX);
|
||||
|
||||
auto indices = level.content.getIndices();
|
||||
// Light emission when an emissive item is chosen
|
||||
@@ -175,7 +176,7 @@ void WorldRenderer::setupWorldShader(
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::renderLevel(
|
||||
void WorldRenderer::renderOpaque(
|
||||
const DrawContext& ctx,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
@@ -204,7 +205,9 @@ void WorldRenderer::renderLevel(
|
||||
*modelBatch,
|
||||
culling ? frustumCulling.get() : nullptr,
|
||||
delta,
|
||||
pause
|
||||
pause,
|
||||
player.currentCamera.get() == player.fpCamera.get() ? player.getEntity()
|
||||
: 0
|
||||
);
|
||||
modelBatch->render();
|
||||
particles->render(camera, delta * !pause);
|
||||
@@ -214,7 +217,7 @@ void WorldRenderer::renderLevel(
|
||||
|
||||
setupWorldShader(shader, camera, settings, fogFactor);
|
||||
|
||||
chunks->drawChunks(camera, shader);
|
||||
chunksRenderer->drawChunks(camera, shader);
|
||||
blockWraps->draw(ctx, player);
|
||||
|
||||
if (hudVisible) {
|
||||
@@ -273,143 +276,7 @@ void WorldRenderer::renderLines(
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::renderHands(
|
||||
const Camera& camera, float delta
|
||||
) {
|
||||
auto& entityShader = assets.require<Shader>("entity");
|
||||
auto indices = level.content.getIndices();
|
||||
|
||||
// get current chosen item
|
||||
const auto& inventory = player.getInventory();
|
||||
int slot = player.getChosenSlot();
|
||||
const ItemStack& stack = inventory->getSlot(slot);
|
||||
const auto& def = indices->items.require(stack.getItemId());
|
||||
|
||||
// prepare modified HUD camera
|
||||
Camera hudcam = camera;
|
||||
hudcam.far = 10.0f;
|
||||
hudcam.setFov(0.9f);
|
||||
hudcam.position = {};
|
||||
|
||||
// configure model matrix
|
||||
const glm::vec3 itemOffset(0.06f, 0.035f, -0.1);
|
||||
|
||||
static glm::mat4 prevRotation(1.0f);
|
||||
|
||||
const float speed = 24.0f;
|
||||
glm::mat4 matrix = glm::translate(glm::mat4(1.0f), itemOffset);
|
||||
matrix = glm::scale(matrix, glm::vec3(0.1f));
|
||||
glm::mat4 rotation = camera.rotation;
|
||||
glm::quat rot0 = glm::quat_cast(prevRotation);
|
||||
glm::quat rot1 = glm::quat_cast(rotation);
|
||||
glm::quat finalRot =
|
||||
glm::slerp(rot0, rot1, static_cast<float>(delta * speed));
|
||||
rotation = glm::mat4_cast(finalRot);
|
||||
matrix = rotation * matrix *
|
||||
glm::rotate(
|
||||
glm::mat4(1.0f), -glm::pi<float>() * 0.5f, glm::vec3(0, 1, 0)
|
||||
);
|
||||
prevRotation = rotation;
|
||||
glm::vec3 cameraRotation = player.getRotation();
|
||||
auto offset = -(camera.position - player.getPosition());
|
||||
float angle = glm::radians(cameraRotation.x - 90);
|
||||
float cos = glm::cos(angle);
|
||||
float sin = glm::sin(angle);
|
||||
|
||||
float newX = offset.x * cos - offset.z * sin;
|
||||
float newZ = offset.x * sin + offset.z * cos;
|
||||
offset = glm::vec3(newX, offset.y, newZ);
|
||||
matrix = matrix * glm::translate(glm::mat4(1.0f), offset);
|
||||
|
||||
// render
|
||||
modelBatch->setLightsOffset(camera.position);
|
||||
modelBatch->draw(
|
||||
matrix,
|
||||
glm::vec3(1.0f),
|
||||
assets.get<model::Model>(def.modelName),
|
||||
nullptr
|
||||
);
|
||||
display::clearDepth();
|
||||
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
|
||||
skybox->bind();
|
||||
modelBatch->render();
|
||||
modelBatch->setLightsOffset(glm::vec3());
|
||||
skybox->unbind();
|
||||
}
|
||||
|
||||
void WorldRenderer::generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale
|
||||
) {
|
||||
auto& shadowsShader = assets.require<Shader>("shadows");
|
||||
|
||||
auto world = level.getWorld();
|
||||
const auto& worldInfo = world->getInfo();
|
||||
|
||||
const auto& settings = engine.getSettings();
|
||||
int resolution = shadowMap.getResolution();
|
||||
int quality = settings.graphics.shadowsQuality.get();
|
||||
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;
|
||||
float shadowMapSize = resolution * shadowMapScale;
|
||||
|
||||
glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f;
|
||||
glm::vec3 prevPos = shadowCamera.position;
|
||||
shadowCamera = Camera(
|
||||
glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos,
|
||||
shadowMapSize
|
||||
);
|
||||
shadowCamera.near = 0.1f;
|
||||
shadowCamera.far = 1000.0f;
|
||||
shadowCamera.perspective = false;
|
||||
shadowCamera.setAspectRatio(1.0f);
|
||||
|
||||
float t = worldInfo.daytime - 0.25f;
|
||||
if (t < 0.0f) {
|
||||
t += 1.0f;
|
||||
}
|
||||
t = fmod(t, 0.5f);
|
||||
|
||||
float sunCycleStep = 1.0f / 500.0f;
|
||||
float sunAngle = glm::radians(
|
||||
90.0f -
|
||||
((static_cast<int>(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f
|
||||
);
|
||||
float sunAltitude = glm::pi<float>() * 0.25f;
|
||||
shadowCamera.rotate(
|
||||
-glm::cos(sunAngle + glm::pi<float>() * 0.5f) * sunAltitude,
|
||||
sunAngle - glm::pi<float>() * 0.5f,
|
||||
glm::radians(0.0f)
|
||||
);
|
||||
|
||||
shadowCamera.position -= shadowCamera.front * 500.0f;
|
||||
shadowCamera.position += shadowCamera.up * 0.0f;
|
||||
shadowCamera.position += camera.front * 0.0f;
|
||||
|
||||
auto view = shadowCamera.getView();
|
||||
|
||||
auto currentPos = shadowCamera.position;
|
||||
auto topRight = shadowCamera.right + shadowCamera.up;
|
||||
auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
|
||||
shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f));
|
||||
|
||||
{
|
||||
auto sctx = pctx.sub();
|
||||
sctx.setDepthTest(true);
|
||||
sctx.setCullFace(true);
|
||||
sctx.setViewport({resolution, resolution});
|
||||
shadowMap.bind();
|
||||
setupWorldShader(shadowsShader, shadowCamera, settings, 0.0f);
|
||||
chunks->drawChunksShadowsPass(shadowCamera, shadowsShader, camera);
|
||||
shadowMap.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::draw(
|
||||
void WorldRenderer::renderFrame(
|
||||
const DrawContext& pctx,
|
||||
Camera& camera,
|
||||
bool hudVisible,
|
||||
@@ -418,6 +285,9 @@ void WorldRenderer::draw(
|
||||
PostProcessing& postProcessing
|
||||
) {
|
||||
// TODO: REFACTOR WHOLE RENDER ENGINE
|
||||
|
||||
auto projView = camera.getProjView();
|
||||
|
||||
float delta = uiDelta * !pause;
|
||||
timer += delta;
|
||||
weather.update(delta);
|
||||
@@ -433,22 +303,17 @@ void WorldRenderer::draw(
|
||||
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
|
||||
const auto& settings = engine.getSettings();
|
||||
|
||||
Shader* affectedShaders[] {
|
||||
&mainShader, &entityShader, &translucentShader, &deferredShader
|
||||
};
|
||||
|
||||
gbufferPipeline = settings.graphics.advancedRender.get();
|
||||
int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline;
|
||||
int resolution = MIN_SHADOW_MAP_RES << shadowsQuality;
|
||||
if (shadowsQuality > 0 && !shadows) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
shadows = true;
|
||||
} else if (shadowsQuality == 0 && shadows) {
|
||||
shadowMap.reset();
|
||||
wideShadowMap.reset();
|
||||
shadows = false;
|
||||
}
|
||||
shadowMapping->setQuality(shadowsQuality);
|
||||
|
||||
CompileTimeShaderSettings currentSettings {
|
||||
gbufferPipeline,
|
||||
shadows,
|
||||
shadowsQuality != 0,
|
||||
settings.graphics.ssao.get() && gbufferPipeline
|
||||
};
|
||||
if (
|
||||
@@ -456,19 +321,15 @@ void WorldRenderer::draw(
|
||||
prevCTShaderSettings.shadows != currentSettings.shadows ||
|
||||
prevCTShaderSettings.ssao != currentSettings.ssao
|
||||
) {
|
||||
Shader::preprocessor->setDefined("ENABLE_SHADOWS", currentSettings.shadows);
|
||||
Shader::preprocessor->setDefined("ENABLE_SSAO", currentSettings.ssao);
|
||||
Shader::preprocessor->setDefined("ADVANCED_RENDER", currentSettings.advancedRender);
|
||||
mainShader.recompile();
|
||||
entityShader.recompile();
|
||||
deferredShader.recompile();
|
||||
translucentShader.recompile();
|
||||
prevCTShaderSettings = currentSettings;
|
||||
}
|
||||
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 (shadows && shadowMap->getResolution() != resolution) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
for (auto shader : affectedShaders) {
|
||||
shader->recompile(defines);
|
||||
}
|
||||
prevCTShaderSettings = currentSettings;
|
||||
}
|
||||
|
||||
const auto& worldInfo = world->getInfo();
|
||||
@@ -479,38 +340,24 @@ void WorldRenderer::draw(
|
||||
|
||||
skybox->refresh(pctx, worldInfo.daytime, mie, 4);
|
||||
|
||||
chunks->update();
|
||||
chunksRenderer->update();
|
||||
|
||||
static int frameid = 0;
|
||||
if (shadows) {
|
||||
if (frameid % 2 == 0) {
|
||||
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f);
|
||||
} else {
|
||||
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f);
|
||||
}
|
||||
}
|
||||
frameid++;
|
||||
|
||||
auto& linesShader = assets.require<Shader>("lines");
|
||||
/* World render scope with diegetic HUD included */ {
|
||||
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
|
||||
auto& shader = assets.require<Shader>("shadows");
|
||||
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
|
||||
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
|
||||
});
|
||||
{
|
||||
DrawContext wctx = pctx.sub();
|
||||
postProcessing.use(wctx, gbufferPipeline);
|
||||
|
||||
display::clearDepth();
|
||||
|
||||
/* Actually world render with depth buffer on */ {
|
||||
/* Main opaque pass (GBuffer pass) */ {
|
||||
DrawContext ctx = wctx.sub();
|
||||
ctx.setDepthTest(true);
|
||||
ctx.setCullFace(true);
|
||||
renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible);
|
||||
// Debug lines
|
||||
if (hudVisible) {
|
||||
if (debug) {
|
||||
guides->renderDebugLines(
|
||||
ctx, camera, *lineBatch, linesShader, showChunkBorders
|
||||
);
|
||||
}
|
||||
}
|
||||
renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible);
|
||||
}
|
||||
texts->render(pctx, camera, settings, hudVisible, true);
|
||||
}
|
||||
@@ -531,19 +378,33 @@ void WorldRenderer::draw(
|
||||
} else {
|
||||
postProcessing.getFramebuffer()->bind();
|
||||
}
|
||||
// Drawing background sky plane
|
||||
|
||||
// Background sky plane
|
||||
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds);
|
||||
|
||||
auto& linesShader = assets.require<Shader>("lines");
|
||||
linesShader.use();
|
||||
if (debug && hudVisible) {
|
||||
debugLines->render(
|
||||
ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders
|
||||
);
|
||||
}
|
||||
linesShader.uniformMatrix("u_projview", projView);
|
||||
lines->draw(*lineBatch);
|
||||
lineBatch->flush();
|
||||
|
||||
// Translucent blocks
|
||||
{
|
||||
auto sctx = ctx.sub();
|
||||
sctx.setCullFace(true);
|
||||
skybox->bind();
|
||||
translucentShader.use();
|
||||
setupWorldShader(translucentShader, camera, settings, fogFactor);
|
||||
chunks->drawSortedMeshes(camera, translucentShader);
|
||||
chunksRenderer->drawSortedMeshes(camera, translucentShader);
|
||||
skybox->unbind();
|
||||
}
|
||||
|
||||
// Weather effects
|
||||
entityShader.use();
|
||||
setupWorldShader(entityShader, camera, settings, fogFactor);
|
||||
|
||||
@@ -568,7 +429,22 @@ void WorldRenderer::draw(
|
||||
DrawContext ctx = pctx.sub();
|
||||
ctx.setDepthTest(true);
|
||||
ctx.setCullFace(true);
|
||||
renderHands(camera, delta);
|
||||
|
||||
// prepare modified HUD camera
|
||||
Camera hudcam = camera;
|
||||
hudcam.far = 10.0f;
|
||||
hudcam.setFov(0.9f);
|
||||
hudcam.position = {};
|
||||
|
||||
hands->renderHands(camera, delta);
|
||||
|
||||
display::clearDepth();
|
||||
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
|
||||
|
||||
skybox->bind();
|
||||
modelBatch->render();
|
||||
modelBatch->setLightsOffset(glm::vec3());
|
||||
skybox->unbind();
|
||||
}
|
||||
renderBlockOverlay(pctx);
|
||||
|
||||
@@ -620,7 +496,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
|
||||
}
|
||||
|
||||
void WorldRenderer::clear() {
|
||||
chunks->clear();
|
||||
chunksRenderer->clear();
|
||||
}
|
||||
|
||||
void WorldRenderer::setDebug(bool flag) {
|
||||
|
||||
@@ -20,7 +20,9 @@ class ChunksRenderer;
|
||||
class ParticlesRenderer;
|
||||
class BlockWrapsRenderer;
|
||||
class PrecipitationRenderer;
|
||||
class GuidesRenderer;
|
||||
class HandsRenderer;
|
||||
class NamedSkeletons;
|
||||
class LinesRenderer;
|
||||
class TextsRenderer;
|
||||
class Shader;
|
||||
class Frustum;
|
||||
@@ -31,8 +33,9 @@ class PostProcessing;
|
||||
class DrawContext;
|
||||
class ModelBatch;
|
||||
class Assets;
|
||||
class ShadowMap;
|
||||
class Shadows;
|
||||
class GBuffer;
|
||||
class DebugLinesRenderer;
|
||||
struct EngineSettings;
|
||||
|
||||
struct CompileTimeShaderSettings {
|
||||
@@ -50,27 +53,22 @@ class WorldRenderer {
|
||||
std::unique_ptr<LineBatch> lineBatch;
|
||||
std::unique_ptr<Batch3D> batch3d;
|
||||
std::unique_ptr<ModelBatch> modelBatch;
|
||||
std::unique_ptr<GuidesRenderer> guides;
|
||||
std::unique_ptr<ChunksRenderer> chunks;
|
||||
std::unique_ptr<ChunksRenderer> chunksRenderer;
|
||||
std::unique_ptr<HandsRenderer> hands;
|
||||
std::unique_ptr<Skybox> skybox;
|
||||
std::unique_ptr<ShadowMap> shadowMap;
|
||||
std::unique_ptr<ShadowMap> wideShadowMap;
|
||||
std::unique_ptr<Shadows> shadowMapping;
|
||||
std::unique_ptr<DebugLinesRenderer> debugLines;
|
||||
Weather weather {};
|
||||
Camera shadowCamera;
|
||||
Camera wideShadowCamera;
|
||||
|
||||
float timer = 0.0f;
|
||||
bool debug = false;
|
||||
bool lightsDebug = false;
|
||||
bool gbufferPipeline = false;
|
||||
bool shadows = false;
|
||||
|
||||
CompileTimeShaderSettings prevCTShaderSettings {};
|
||||
|
||||
/// @brief Render block selection lines
|
||||
void renderBlockSelection();
|
||||
|
||||
void renderHands(const Camera& camera, float delta);
|
||||
|
||||
/// @brief Render lines (selection and debug)
|
||||
/// @param camera active camera
|
||||
@@ -88,18 +86,25 @@ class WorldRenderer {
|
||||
float fogFactor
|
||||
);
|
||||
|
||||
void generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale
|
||||
/// @brief Render opaque pass
|
||||
/// @param context graphics context
|
||||
/// @param camera active camera
|
||||
/// @param settings engine settings
|
||||
void renderOpaque(
|
||||
const DrawContext& context,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float delta,
|
||||
bool pause,
|
||||
bool hudVisible
|
||||
);
|
||||
public:
|
||||
std::unique_ptr<ParticlesRenderer> particles;
|
||||
std::unique_ptr<TextsRenderer> texts;
|
||||
std::unique_ptr<BlockWrapsRenderer> blockWraps;
|
||||
std::unique_ptr<PrecipitationRenderer> precipitation;
|
||||
std::unique_ptr<NamedSkeletons> skeletons;
|
||||
std::unique_ptr<LinesRenderer> lines;
|
||||
|
||||
static bool showChunkBorders;
|
||||
static bool showEntitiesDebug;
|
||||
@@ -107,7 +112,7 @@ public:
|
||||
WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
|
||||
~WorldRenderer();
|
||||
|
||||
void draw(
|
||||
void renderFrame(
|
||||
const DrawContext& context,
|
||||
Camera& camera,
|
||||
bool hudVisible,
|
||||
@@ -116,19 +121,6 @@ public:
|
||||
PostProcessing& postProcessing
|
||||
);
|
||||
|
||||
/// @brief Render level without diegetic interface
|
||||
/// @param context graphics context
|
||||
/// @param camera active camera
|
||||
/// @param settings engine settings
|
||||
void renderLevel(
|
||||
const DrawContext& context,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float delta,
|
||||
bool pause,
|
||||
bool hudVisible
|
||||
);
|
||||
|
||||
void clear();
|
||||
|
||||
void setDebug(bool flag);
|
||||
|
||||
+11
-10
@@ -50,12 +50,19 @@ GUI::GUI(Engine& engine)
|
||||
tooltip = guiutil::create(
|
||||
*this,
|
||||
"<container color='#000000A0' interactive='false' z-index='999'>"
|
||||
"<label id='tooltip.label' pos='2' autoresize='true' multiline='true' text-wrap='false'></label>"
|
||||
"<label id='tooltip.label' markup='md' pos='2' autoresize='true' multiline='true' text-wrap='false'></label>"
|
||||
"</container>"
|
||||
);
|
||||
store("tooltip", tooltip);
|
||||
store("tooltip.label", UINode::find(tooltip, "tooltip.label"));
|
||||
container->add(tooltip);
|
||||
|
||||
rootDocument = std::make_unique<UiDocument>(
|
||||
"core:root",
|
||||
uidocscript {},
|
||||
std::dynamic_pointer_cast<gui::UINode>(container),
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
GUI::~GUI() = default;
|
||||
@@ -74,15 +81,8 @@ std::shared_ptr<Menu> GUI::getMenu() {
|
||||
}
|
||||
|
||||
void GUI::onAssetsLoad(Assets* assets) {
|
||||
assets->store(
|
||||
std::make_unique<UiDocument>(
|
||||
"core:root",
|
||||
uidocscript {},
|
||||
std::dynamic_pointer_cast<gui::UINode>(container),
|
||||
nullptr
|
||||
),
|
||||
"core:root"
|
||||
);
|
||||
rootDocument->rebuildIndices();
|
||||
assets->store(rootDocument, "core:root");
|
||||
}
|
||||
|
||||
void GUI::resetTooltip() {
|
||||
@@ -302,6 +302,7 @@ bool GUI::isFocusCaught() const {
|
||||
}
|
||||
|
||||
void GUI::add(std::shared_ptr<UINode> node) {
|
||||
UINode::getIndices(node, rootDocument->getMapWriteable());
|
||||
container->add(std::move(node));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace devtools {
|
||||
class Editor;
|
||||
}
|
||||
|
||||
class UiDocument;
|
||||
|
||||
/*
|
||||
Some info about padding and margin.
|
||||
Padding is element inner space, margin is outer
|
||||
@@ -70,6 +72,7 @@ namespace gui {
|
||||
std::shared_ptr<UINode> pressed;
|
||||
std::shared_ptr<UINode> focus;
|
||||
std::shared_ptr<UINode> tooltip;
|
||||
std::shared_ptr<UiDocument> rootDocument;
|
||||
std::unordered_map<std::string, std::shared_ptr<UINode>> storage;
|
||||
|
||||
std::unique_ptr<Camera> uicamera;
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#include "InventoryView.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <utility>
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "assets/assets_util.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "frontend/LevelFrontend.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
#include "graphics/core/Atlas.hpp"
|
||||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Font.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/render/BlocksPreview.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "items/Inventories.hpp"
|
||||
#include "items/Inventory.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
@@ -15,17 +26,6 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "graphics/core/Atlas.hpp"
|
||||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "graphics/core/Font.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/render/BlocksPreview.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <utility>
|
||||
|
||||
using namespace gui;
|
||||
|
||||
@@ -37,21 +37,24 @@ SlotLayout::SlotLayout(
|
||||
slotcallback updateFunc,
|
||||
slotcallback shareFunc,
|
||||
slotcallback rightClick
|
||||
) : index(index),
|
||||
position(position),
|
||||
background(background),
|
||||
itemSource(itemSource),
|
||||
updateFunc(std::move(updateFunc)),
|
||||
shareFunc(std::move(shareFunc)),
|
||||
rightClick(std::move(rightClick)) {}
|
||||
)
|
||||
: index(index),
|
||||
position(position),
|
||||
background(background),
|
||||
itemSource(itemSource),
|
||||
updateFunc(std::move(updateFunc)),
|
||||
shareFunc(std::move(shareFunc)),
|
||||
rightClick(std::move(rightClick)) {
|
||||
}
|
||||
|
||||
InventoryBuilder::InventoryBuilder(GUI& gui) : gui(gui) {
|
||||
view = std::make_shared<InventoryView>(gui);
|
||||
}
|
||||
|
||||
void InventoryBuilder::addGrid(
|
||||
int cols, int count,
|
||||
glm::vec2 pos,
|
||||
int cols,
|
||||
int count,
|
||||
glm::vec2 pos,
|
||||
glm::vec4 padding,
|
||||
bool addpanel,
|
||||
const SlotLayout& slotLayout
|
||||
@@ -61,9 +64,11 @@ void InventoryBuilder::addGrid(
|
||||
|
||||
int rows = ceildiv(count, cols);
|
||||
|
||||
uint width = cols * (slotSize + interval) - interval + padding.x + padding.z;
|
||||
uint height = rows * (slotSize + interval) - interval + padding.y + padding.w;
|
||||
|
||||
uint width =
|
||||
cols * (slotSize + interval) - interval + padding.x + padding.z;
|
||||
uint height =
|
||||
rows * (slotSize + interval) - interval + padding.y + padding.w;
|
||||
|
||||
glm::vec2 vsize = view->getSize();
|
||||
if (pos.x + width > vsize.x) {
|
||||
vsize.x = pos.x + width;
|
||||
@@ -85,7 +90,7 @@ void InventoryBuilder::addGrid(
|
||||
if (row * cols + col >= count) {
|
||||
break;
|
||||
}
|
||||
glm::vec2 position (
|
||||
glm::vec2 position(
|
||||
col * (slotSize + interval) + padding.x,
|
||||
row * (slotSize + interval) + padding.y
|
||||
);
|
||||
@@ -105,24 +110,72 @@ std::shared_ptr<InventoryView> InventoryBuilder::build() {
|
||||
return view;
|
||||
}
|
||||
|
||||
SlotView::SlotView(
|
||||
GUI& gui, SlotLayout layout
|
||||
) : UINode(gui, glm::vec2(InventoryView::SLOT_SIZE)),
|
||||
layout(std::move(layout))
|
||||
{
|
||||
SlotView::SlotView(GUI& gui, SlotLayout layout)
|
||||
: UINode(gui, glm::vec2(InventoryView::SLOT_SIZE)),
|
||||
layout(std::move(layout)) {
|
||||
setColor(glm::vec4(0, 0, 0, 0.2f));
|
||||
setTooltipDelay(0.0f);
|
||||
}
|
||||
// TODO: Refactor
|
||||
static std::wstring get_caption_string(
|
||||
const ItemStack& stack, const ItemDef& item
|
||||
) {
|
||||
dv::value* caption = stack.getField("caption");
|
||||
if (caption != nullptr) {
|
||||
return util::pascal_case(
|
||||
langs::get(util::str2wstr_utf8(caption->asString()))
|
||||
);
|
||||
} else {
|
||||
return util::pascal_case(
|
||||
langs::get(util::str2wstr_utf8(item.caption))
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: Refactor
|
||||
static std::wstring get_description_string(
|
||||
const ItemStack& stack, const ItemDef& item
|
||||
) {
|
||||
dv::value* description = stack.getField("description");
|
||||
|
||||
if (description != nullptr) {
|
||||
return langs::get(util::str2wstr_utf8(description->asString()));
|
||||
} else {
|
||||
return langs::get(util::str2wstr_utf8(item.description));
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_same_tooltip(const ItemStack& stack, const ItemStack& cache) {
|
||||
if (stack.getItemId() != cache.getItemId()) {
|
||||
return false;
|
||||
}
|
||||
auto caption = stack.getField("caption");
|
||||
auto cCaption = cache.getField("caption");
|
||||
auto description = stack.getField("description");
|
||||
auto cDescription = cache.getField("description");
|
||||
|
||||
if (((caption != nullptr) != (cCaption != nullptr)) ||
|
||||
((description != nullptr) != (cDescription != nullptr))) {
|
||||
return false;
|
||||
}
|
||||
return (caption ? caption->asString() == cCaption->asString() : true) &&
|
||||
(description ? description->asString() == cDescription->asString()
|
||||
: true);
|
||||
}
|
||||
|
||||
void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) {
|
||||
itemid_t itemid = stack.getItemId();
|
||||
if (itemid == cache.stack.getItemId()) {
|
||||
|
||||
if (is_same_tooltip(stack, cache.stack)) {
|
||||
return;
|
||||
}
|
||||
if (itemid) {
|
||||
tooltip = util::pascal_case(
|
||||
langs::get(util::str2wstr_utf8(item.caption))
|
||||
);
|
||||
std::wstring caption = get_caption_string(stack, item);
|
||||
std::wstring description = get_description_string(stack, item);
|
||||
if (description.length() > 0) {
|
||||
tooltip = caption + L"\n" + description;
|
||||
} else {
|
||||
tooltip = caption;
|
||||
}
|
||||
} else {
|
||||
tooltip.clear();
|
||||
}
|
||||
@@ -148,19 +201,37 @@ void SlotView::drawItemIcon(
|
||||
|
||||
UVRegion region = previews.get(block.name);
|
||||
batch.rect(
|
||||
pos.x, pos.y, SLOT_SIZE, SLOT_SIZE,
|
||||
0, 0, 0, region, false, true, tint
|
||||
pos.x,
|
||||
pos.y,
|
||||
SLOT_SIZE,
|
||||
SLOT_SIZE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
region,
|
||||
false,
|
||||
true,
|
||||
tint
|
||||
);
|
||||
break;
|
||||
}
|
||||
case ItemIconType::SPRITE: {
|
||||
auto textureRegion =
|
||||
util::get_texture_region(assets, item.icon, "blocks:notfound");
|
||||
|
||||
|
||||
batch.texture(textureRegion.texture);
|
||||
batch.rect(
|
||||
pos.x, pos.y, SLOT_SIZE, SLOT_SIZE,
|
||||
0, 0, 0, textureRegion.region, false, true, tint
|
||||
pos.x,
|
||||
pos.y,
|
||||
SLOT_SIZE,
|
||||
SLOT_SIZE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
textureRegion.region,
|
||||
false,
|
||||
true,
|
||||
tint
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -179,12 +250,12 @@ void SlotView::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
cache.countStr = std::to_wstring(stack.getCount());
|
||||
}
|
||||
refreshTooltip(stack, item);
|
||||
cache.stack.set(ItemStack(stack.getItemId(), stack.getCount()));
|
||||
cache.stack.set(stack);
|
||||
|
||||
glm::vec4 tint(1, 1, 1, isEnabled() ? 1 : 0.5f);
|
||||
glm::vec2 pos = calcPos();
|
||||
glm::vec4 color = getColor();
|
||||
|
||||
|
||||
if (hover || highlighted) {
|
||||
tint *= 1.333f;
|
||||
color = glm::vec4(1, 1, 1, 0.2f);
|
||||
@@ -262,7 +333,7 @@ void SlotView::drawItemInfo(
|
||||
batch.setColor({0, 0, 0, 0.75f});
|
||||
batch.rect(pos.x - 2, pos.y - 2, 6, SLOT_SIZE + 4);
|
||||
float t = static_cast<float>(uses) / item.uses;
|
||||
|
||||
|
||||
int height = SLOT_SIZE * t;
|
||||
batch.setColor({(1.0f - t * 0.8f), 0.4f, t * 0.8f + 0.2f, 1.0f});
|
||||
batch.rect(pos.x, pos.y + SLOT_SIZE - height, 2, height);
|
||||
@@ -317,8 +388,7 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (layout.itemSource)
|
||||
return;
|
||||
if (layout.itemSource) return;
|
||||
if (grabbed.isEmpty()) {
|
||||
if (!stack.isEmpty() && layout.taking) {
|
||||
grabbed.set(std::move(stack));
|
||||
@@ -342,15 +412,15 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
|
||||
} else {
|
||||
grabbed = ItemStack(stack.getItemId(), count - 1);
|
||||
}
|
||||
} else if (stack.accepts(grabbed) && stack.getCount() < stackDef.stackSize) {
|
||||
} else if (stack.accepts(grabbed) &&
|
||||
stack.getCount() < stackDef.stackSize) {
|
||||
stack.setCount(stack.getCount() + 1);
|
||||
grabbed.setCount(grabbed.getCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void SlotView::clicked(Mousecode button) {
|
||||
if (bound == nullptr)
|
||||
return;
|
||||
if (bound == nullptr) return;
|
||||
auto exchangeSlot =
|
||||
std::dynamic_pointer_cast<SlotView>(gui.get(EXCHANGE_SLOT_NAME));
|
||||
if (exchangeSlot == nullptr) {
|
||||
@@ -358,7 +428,7 @@ void SlotView::clicked(Mousecode button) {
|
||||
}
|
||||
ItemStack& grabbed = exchangeSlot->getStack();
|
||||
ItemStack& stack = *bound;
|
||||
|
||||
|
||||
if (button == Mousecode::BUTTON_1) {
|
||||
performLeftClick(stack, grabbed);
|
||||
} else if (button == Mousecode::BUTTON_2) {
|
||||
@@ -382,9 +452,7 @@ const std::wstring& SlotView::getTooltip() const {
|
||||
}
|
||||
|
||||
void SlotView::bind(
|
||||
int64_t inventoryid,
|
||||
ItemStack& stack,
|
||||
const Content* content
|
||||
int64_t inventoryid, ItemStack& stack, const Content* content
|
||||
) {
|
||||
this->inventoryid = inventoryid;
|
||||
bound = &stack;
|
||||
@@ -403,11 +471,11 @@ InventoryView::InventoryView(GUI& gui) : Container(gui, glm::vec2()) {
|
||||
setColor(glm::vec4(0, 0, 0, 0.0f));
|
||||
}
|
||||
|
||||
InventoryView::~InventoryView() {}
|
||||
|
||||
InventoryView::~InventoryView() {
|
||||
}
|
||||
|
||||
std::shared_ptr<SlotView> InventoryView::addSlot(const SlotLayout& layout) {
|
||||
uint width = InventoryView::SLOT_SIZE + layout.padding;
|
||||
uint width = InventoryView::SLOT_SIZE + layout.padding;
|
||||
uint height = InventoryView::SLOT_SIZE + layout.padding;
|
||||
|
||||
auto pos = layout.position;
|
||||
@@ -432,14 +500,12 @@ std::shared_ptr<Inventory> InventoryView::getInventory() const {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
|
||||
size_t InventoryView::getSlotsCount() const {
|
||||
return slots.size();
|
||||
}
|
||||
|
||||
void InventoryView::bind(
|
||||
const std::shared_ptr<Inventory>& inventory,
|
||||
const Content* content
|
||||
const std::shared_ptr<Inventory>& inventory, const Content* content
|
||||
) {
|
||||
this->inventory = inventory;
|
||||
this->content = content;
|
||||
|
||||
@@ -810,6 +810,85 @@ void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) {
|
||||
}
|
||||
}
|
||||
|
||||
static int calc_indent(int linestart, std::wstring_view input) {
|
||||
int indent = 0;
|
||||
while (linestart + indent < input.length() &&
|
||||
input[linestart + indent] == L' ')
|
||||
indent++;
|
||||
return indent;
|
||||
}
|
||||
|
||||
void TextBox::onTab(bool shiftPressed) {
|
||||
std::wstring indentStr = L" ";
|
||||
|
||||
if (!shiftPressed && getSelectionLength() == 0) {
|
||||
paste(indentStr);
|
||||
return;
|
||||
}
|
||||
if (getSelectionLength() == 0) {
|
||||
selectionStart = caret;
|
||||
selectionEnd = caret;
|
||||
selectionOrigin = caret;
|
||||
}
|
||||
|
||||
int lineA = getLineAt(selectionStart);
|
||||
int lineB = getLineAt(selectionEnd);
|
||||
int caretLine = getLineAt(caret);
|
||||
|
||||
size_t lineAStart = getLinePos(lineA);
|
||||
size_t lineBStart = getLinePos(lineB);
|
||||
size_t caretLineStart = getLinePos(caretLine);
|
||||
size_t caretIndent = calc_indent(caretLineStart, input);
|
||||
size_t aIndent = calc_indent(lineAStart, input);
|
||||
size_t bIndent = calc_indent(lineBStart, input);
|
||||
|
||||
int lastSelectionStart = selectionStart;
|
||||
int lastSelectionEnd = selectionEnd;
|
||||
size_t lastCaret = caret;
|
||||
|
||||
auto combination = history->beginCombination();
|
||||
|
||||
resetSelection();
|
||||
|
||||
for (int line = lineA; line <= lineB; line++) {
|
||||
size_t linestart = getLinePos(line);
|
||||
int indent = calc_indent(linestart, input);
|
||||
|
||||
if (shiftPressed) {
|
||||
if (indent >= indentStr.length()) {
|
||||
setCaret(linestart);
|
||||
select(linestart, linestart + indentStr.length());
|
||||
eraseSelected();
|
||||
}
|
||||
} else {
|
||||
setCaret(linestart);
|
||||
paste(indentStr);
|
||||
}
|
||||
refreshLabel(); // todo: replace with textbox cache
|
||||
}
|
||||
|
||||
int linestart = getLinePos(caretLine);
|
||||
int linestartA = getLinePos(lineA);
|
||||
int linestartB = getLinePos(lineB);
|
||||
int la = lastSelectionStart - lineAStart;
|
||||
int lb = lastSelectionEnd - lineBStart;
|
||||
if (shiftPressed) {
|
||||
setCaret(lastCaret - caretLineStart + linestart - std::min<int>(caretIndent, indentStr.length()));
|
||||
selectionStart = la + linestartA - std::min<int>(std::min<int>(la, aIndent), indentStr.length());
|
||||
selectionEnd = lb + linestartB - std::min<int>(std::min<int>(lb, bIndent), indentStr.length());
|
||||
} else {
|
||||
setCaret(lastCaret - caretLineStart + linestart + indentStr.length());
|
||||
selectionStart = la + linestartA + indentStr.length();
|
||||
selectionEnd = lb + linestartB + indentStr.length();
|
||||
}
|
||||
if (selectionOrigin == lastSelectionStart) {
|
||||
selectionOrigin = selectionStart;
|
||||
} else {
|
||||
selectionOrigin = selectionEnd;
|
||||
}
|
||||
historian->sync();
|
||||
}
|
||||
|
||||
void TextBox::refreshSyntax() {
|
||||
if (!syntax.empty()) {
|
||||
const auto& processor = gui.getEditor().getSyntaxProcessor();
|
||||
@@ -868,7 +947,7 @@ void TextBox::performEditingKeyboardEvents(Keycode key) {
|
||||
}
|
||||
}
|
||||
} else if (key == Keycode::TAB) {
|
||||
paste(L" ");
|
||||
onTab(shiftPressed);
|
||||
} else if (key == Keycode::LEFT) {
|
||||
stepLeft(shiftPressed, breakSelection);
|
||||
} else if (key == Keycode::RIGHT) {
|
||||
|
||||
@@ -71,6 +71,8 @@ namespace gui {
|
||||
void stepDefaultDown(bool shiftPressed, bool breakSelection);
|
||||
void stepDefaultUp(bool shiftPressed, bool breakSelection);
|
||||
|
||||
void onTab(bool shiftPressed);
|
||||
|
||||
size_t normalizeIndex(int index);
|
||||
|
||||
int calcIndexAt(int x, int y) const;
|
||||
|
||||
@@ -83,9 +83,14 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
||||
builder.add("language", &settings.ui.language);
|
||||
builder.add("world-preview-size", &settings.ui.worldPreviewSize);
|
||||
|
||||
builder.section("pathfinding");
|
||||
builder.add("steps-per-async-agent", &settings.pathfinding.stepsPerAsyncAgent);
|
||||
|
||||
builder.section("debug");
|
||||
builder.add("generator-test-mode", &settings.debug.generatorTestMode);
|
||||
builder.add("do-write-lights", &settings.debug.doWriteLights);
|
||||
builder.add("do-trace-shaders", &settings.debug.doTraceShaders);
|
||||
builder.add("enable-experimental", &settings.debug.enableExperimental);
|
||||
}
|
||||
|
||||
dv::value SettingsHandler::getValue(const std::string& name) const {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
ItemDef::ItemDef(const std::string& name) : name(name) {
|
||||
caption = util::id_to_caption(name);
|
||||
description = "";
|
||||
}
|
||||
void ItemDef::cloneTo(ItemDef& dst) {
|
||||
dst.caption = caption;
|
||||
dst.description = description;
|
||||
dst.stackSize = stackSize;
|
||||
dst.generated = generated;
|
||||
std::copy(&emission[0], &emission[3], dst.emission);
|
||||
@@ -17,4 +19,5 @@ void ItemDef::cloneTo(ItemDef& dst) {
|
||||
dst.modelName = modelName;
|
||||
dst.uses = uses;
|
||||
dst.usesDisplay = usesDisplay;
|
||||
dst.tags = tags;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "data/dv.hpp"
|
||||
#include "typedefs.hpp"
|
||||
@@ -34,6 +36,9 @@ struct ItemDef {
|
||||
/// @brief Item name will shown in inventory
|
||||
std::string caption;
|
||||
|
||||
/// @brief Item description will shown in inventory
|
||||
std::string description;
|
||||
|
||||
dv::value properties = nullptr;
|
||||
|
||||
/// @brief Item max stack size
|
||||
@@ -61,11 +66,15 @@ struct ItemDef {
|
||||
|
||||
std::string scriptFile;
|
||||
|
||||
std::vector<std::string> tags;
|
||||
|
||||
struct {
|
||||
itemid_t id;
|
||||
blockid_t placingBlock;
|
||||
ItemFuncsSet funcsset {};
|
||||
bool emissive = false;
|
||||
|
||||
std::set<int> tags;
|
||||
} rt {};
|
||||
|
||||
ItemDef(const std::string& name);
|
||||
|
||||
@@ -127,7 +127,7 @@ void BlocksController::update(float delta, uint padding) {
|
||||
onBlocksTick(blocksTickClock.getPart(), blocksTickClock.getParts());
|
||||
}
|
||||
if (worldTickClock.update(delta)) {
|
||||
scripting::on_world_tick();
|
||||
scripting::on_world_tick(worldTickClock.getTickRate());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "objects/Player.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "scripting/scripting.hpp"
|
||||
#include "lighting/Lighting.hpp"
|
||||
#include "settings.hpp"
|
||||
@@ -69,6 +70,9 @@ LevelController::LevelController(
|
||||
}
|
||||
|
||||
void LevelController::update(float delta, bool pause) {
|
||||
level->pathfinding->performAllAsync(
|
||||
settings.pathfinding.stepsPerAsyncAgent.get()
|
||||
);
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
continue;
|
||||
@@ -91,7 +95,6 @@ void LevelController::update(float delta, bool pause) {
|
||||
if (!pause) {
|
||||
// update all objects that needed
|
||||
blocks->update(delta, settings.chunks.padding.get());
|
||||
level->entities->updatePhysics(delta);
|
||||
level->entities->update(delta);
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "items/ItemStack.hpp"
|
||||
#include "lighting/Lighting.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
@@ -270,7 +271,6 @@ void PlayerController::update(float delta, const Input* inputEvents) {
|
||||
} else {
|
||||
resetKeyboard();
|
||||
}
|
||||
updatePlayer(delta);
|
||||
}
|
||||
|
||||
void PlayerController::postUpdate(
|
||||
@@ -309,20 +309,7 @@ void PlayerController::updateKeyboard(const Input& inputEvents) {
|
||||
}
|
||||
|
||||
void PlayerController::resetKeyboard() {
|
||||
input.zoom = false;
|
||||
input.moveForward = false;
|
||||
input.moveBack = false;
|
||||
input.moveLeft = false;
|
||||
input.moveRight = false;
|
||||
input.sprint = false;
|
||||
input.shift = false;
|
||||
input.cheat = false;
|
||||
input.jump = false;
|
||||
input.delta = {};
|
||||
}
|
||||
|
||||
void PlayerController::updatePlayer(float delta) {
|
||||
player.updateInput(input, delta);
|
||||
input = {};
|
||||
}
|
||||
|
||||
static int determine_rotation(
|
||||
@@ -338,7 +325,8 @@ static int determine_rotation(
|
||||
if (norm.z > 0.0f) return BLOCK_DIR_NORTH;
|
||||
if (norm.z < 0.0f) return BLOCK_DIR_SOUTH;
|
||||
} else if (name == "pane" || name == "stairs") {
|
||||
int verticalBit = (name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0;
|
||||
int verticalBit =
|
||||
(name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0;
|
||||
if (abs(camDir.x) > abs(camDir.z)) {
|
||||
if (camDir.x > 0.0f) return BLOCK_DIR_EAST | verticalBit;
|
||||
if (camDir.x < 0.0f) return BLOCK_DIR_WEST | verticalBit;
|
||||
|
||||
@@ -60,7 +60,6 @@ class PlayerController {
|
||||
|
||||
void updateKeyboard(const Input& inputEvents);
|
||||
void resetKeyboard();
|
||||
void updatePlayer(float delta);
|
||||
void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick);
|
||||
void updateInteraction(const Input& inputEvents, float delta);
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#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();
|
||||
};
|
||||
}
|
||||
@@ -38,9 +38,11 @@ extern const luaL_Reg mat4lib[];
|
||||
extern const luaL_Reg networklib[];
|
||||
extern const luaL_Reg packlib[];
|
||||
extern const luaL_Reg particleslib[]; // gfx.particles
|
||||
extern const luaL_Reg pathfindinglib[];
|
||||
extern const luaL_Reg playerlib[];
|
||||
extern const luaL_Reg posteffectslib[]; // gfx.posteffects
|
||||
extern const luaL_Reg quatlib[];
|
||||
extern const luaL_Reg randomlib[];
|
||||
extern const luaL_Reg text3dlib[]; // gfx.text3d
|
||||
extern const luaL_Reg timelib[];
|
||||
extern const luaL_Reg tomllib[];
|
||||
|
||||
@@ -54,7 +54,7 @@ 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::tonumber(L, 2);
|
||||
entity->getRigidbody().hitbox.gravityScale = lua::tovec3(L, 2).y;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -62,6 +62,15 @@ static int l_set_gravity_scale(lua::State* L) {
|
||||
static int l_is_vdamping(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
return lua::pushboolean(
|
||||
L, entity->getRigidbody().hitbox.verticalDamping > 0.0
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_vdamping(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
return lua::pushnumber(
|
||||
L, entity->getRigidbody().hitbox.verticalDamping
|
||||
);
|
||||
}
|
||||
@@ -70,7 +79,11 @@ static int l_is_vdamping(lua::State* L) {
|
||||
|
||||
static int l_set_vdamping(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
entity->getRigidbody().hitbox.verticalDamping = lua::toboolean(L, 2);
|
||||
if (lua::isboolean(L, 2)) {
|
||||
entity->getRigidbody().hitbox.verticalDamping = lua::toboolean(L, 2);
|
||||
} else {
|
||||
entity->getRigidbody().hitbox.verticalDamping = lua::tonumber(L, 2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -144,6 +157,7 @@ const luaL_Reg rigidbodylib[] = {
|
||||
{"get_linear_damping", lua::wrap<l_get_linear_damping>},
|
||||
{"set_linear_damping", lua::wrap<l_set_linear_damping>},
|
||||
{"is_vdamping", lua::wrap<l_is_vdamping>},
|
||||
{"get_vdamping", lua::wrap<l_get_vdamping>},
|
||||
{"set_vdamping", lua::wrap<l_set_vdamping>},
|
||||
{"is_grounded", lua::wrap<l_is_grounded>},
|
||||
{"is_crouching", lua::wrap<l_is_crouching>},
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#include "objects/rigging.hpp"
|
||||
#include "libentity.hpp"
|
||||
|
||||
#include "graphics/render/WorldRenderer.hpp"
|
||||
#include "graphics/render/NamedSkeletons.hpp"
|
||||
|
||||
namespace scripting {
|
||||
extern WorldRenderer* renderer;
|
||||
}
|
||||
|
||||
static int index_range_check(
|
||||
const rigging::Skeleton& skeleton, lua::Integer index
|
||||
) {
|
||||
@@ -13,25 +20,33 @@ static int index_range_check(
|
||||
return static_cast<int>(index);
|
||||
}
|
||||
|
||||
static int l_get_model(lua::State* L) {
|
||||
static rigging::Skeleton* get_skeleton(lua::State* L) {
|
||||
if (lua::isstring(L, 1)) {
|
||||
return scripting::renderer->skeletons->getSkeleton(lua::tostring(L, 1));
|
||||
}
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
auto* rigConfig = skeleton.config;
|
||||
auto index = index_range_check(skeleton, lua::tointeger(L, 2));
|
||||
const auto& modelOverride = skeleton.modelOverrides[index];
|
||||
return &entity->getSkeleton();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static int l_get_model(lua::State* L) {
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
auto& rigConfig = *skeleton->config;
|
||||
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||
const auto& modelOverride = skeleton->modelOverrides[index];
|
||||
if (!modelOverride.model) {
|
||||
return lua::pushstring(L, modelOverride.name);
|
||||
}
|
||||
return lua::pushstring(L, rigConfig->getBones()[index]->model.name);
|
||||
return lua::pushstring(L, rigConfig.getBones()[index]->model.name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_model(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
auto index = index_range_check(skeleton, lua::tointeger(L, 2));
|
||||
auto& modelOverride = skeleton.modelOverrides[index];
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||
auto& modelOverride = skeleton->modelOverrides[index];
|
||||
if (lua::isnoneornil(L, 3)) {
|
||||
modelOverride = {"", nullptr, true};
|
||||
} else {
|
||||
@@ -42,28 +57,25 @@ static int l_set_model(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_get_matrix(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
auto index = index_range_check(skeleton, lua::tointeger(L, 2));
|
||||
return lua::pushmat4(L, skeleton.pose.matrices[index]);
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||
return lua::pushmat4(L, skeleton->pose.matrices[index]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_matrix(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
auto index = index_range_check(skeleton, lua::tointeger(L, 2));
|
||||
skeleton.pose.matrices[index] = lua::tomat4(L, 3);
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||
skeleton->pose.matrices[index] = lua::tomat4(L, 3);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_texture(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
const auto& found = skeleton.textures.find(lua::require_string(L, 2));
|
||||
if (found != skeleton.textures.end()) {
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
const auto& found = skeleton->textures.find(lua::require_string(L, 2));
|
||||
if (found != skeleton->textures.end()) {
|
||||
return lua::pushstring(L, found->second);
|
||||
}
|
||||
}
|
||||
@@ -71,18 +83,16 @@ static int l_get_texture(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_set_texture(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
skeleton.textures[lua::require_string(L, 2)] =
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
skeleton->textures[lua::require_string(L, 2)] =
|
||||
lua::require_string(L, 3);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_index(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
if (auto bone = skeleton.config->find(lua::require_string(L, 2))) {
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
if (auto bone = skeleton->config->find(lua::require_string(L, 2))) {
|
||||
return lua::pushinteger(L, bone->getIndex());
|
||||
}
|
||||
}
|
||||
@@ -90,62 +100,60 @@ static int l_index(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_is_visible(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
if (!lua::isnoneornil(L, 2)) {
|
||||
auto index = index_range_check(skeleton, lua::tointeger(L, 2));
|
||||
return lua::pushboolean(L, skeleton.flags.at(index).visible);
|
||||
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||
return lua::pushboolean(L, skeleton->flags.at(index).visible);
|
||||
}
|
||||
return lua::pushboolean(L, skeleton.visible);
|
||||
return lua::pushboolean(L, skeleton->visible);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_visible(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
if (!lua::isnoneornil(L, 3)) {
|
||||
auto index = index_range_check(skeleton, lua::tointeger(L, 2));
|
||||
skeleton.flags.at(index).visible = lua::toboolean(L, 3);
|
||||
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||
skeleton->flags.at(index).visible = lua::toboolean(L, 3);
|
||||
} else {
|
||||
skeleton.visible = lua::toboolean(L, 2);
|
||||
skeleton->visible = lua::toboolean(L, 2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_color(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
return lua::pushvec(L, skeleton.tint);
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
return lua::pushvec(L, skeleton->tint);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_color(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
skeleton.tint = lua::tovec3(L, 2);
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
skeleton->tint = lua::tovec3(L, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_interpolated(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
return lua::pushboolean(L, skeleton.interpolation.isEnabled());
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
return lua::pushboolean(L, skeleton->interpolation.isEnabled());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_interpolated(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
skeleton.interpolation.setEnabled(lua::toboolean(L, 2));
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
skeleton->interpolation.setEnabled(lua::toboolean(L, 2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_exists(lua::State* L) {
|
||||
return lua::pushboolean(L, get_skeleton(L));
|
||||
}
|
||||
|
||||
const luaL_Reg skeletonlib[] = {
|
||||
{"get_model", lua::wrap<l_get_model>},
|
||||
{"set_model", lua::wrap<l_set_model>},
|
||||
@@ -160,4 +168,6 @@ const luaL_Reg skeletonlib[] = {
|
||||
{"set_color", lua::wrap<l_set_color>},
|
||||
{"is_interpolated", lua::wrap<l_is_interpolated>},
|
||||
{"set_interpolated", lua::wrap<l_set_interpolated>},
|
||||
{NULL, NULL}};
|
||||
{"exists", lua::wrap<l_exists>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -23,6 +23,9 @@ static void load_texture(
|
||||
}
|
||||
|
||||
static int l_load_texture(lua::State* L) {
|
||||
if (lua::isstring(L, 3) && lua::require_lstring(L, 3) != "png") {
|
||||
throw std::runtime_error("unsupportd image format");
|
||||
}
|
||||
if (lua::istable(L, 1)) {
|
||||
lua::pushvalue(L, 1);
|
||||
size_t size = lua::objlen(L, 1);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
template<std::string(*encode_func)(const ubyte*, size_t)>
|
||||
static int l_encode(lua::State* L) {
|
||||
if (lua::istable(L, 1)) {
|
||||
lua::pushvalue(L, 1);
|
||||
@@ -13,12 +14,12 @@ static int l_encode(lua::State* L) {
|
||||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
return lua::pushstring(L, util::base64_encode(
|
||||
return lua::pushstring(L, encode_func(
|
||||
reinterpret_cast<const ubyte*>(buffer.data()), buffer.size()
|
||||
));
|
||||
} else {
|
||||
auto string = lua::bytearray_as_string(L, 1);
|
||||
auto out = util::base64_encode(
|
||||
auto out = encode_func(
|
||||
reinterpret_cast<const ubyte*>(string.data()),
|
||||
string.size()
|
||||
);
|
||||
@@ -28,8 +29,9 @@ static int l_encode(lua::State* L) {
|
||||
throw std::runtime_error("array or ByteArray expected");
|
||||
}
|
||||
|
||||
template<util::Buffer<ubyte>(*decode_func)(std::string_view)>
|
||||
static int l_decode(lua::State* L) {
|
||||
auto buffer = util::base64_decode(lua::require_lstring(L, 1));
|
||||
auto buffer = decode_func(lua::require_lstring(L, 1));
|
||||
if (lua::toboolean(L, 2)) {
|
||||
lua::createtable(L, buffer.size(), 0);
|
||||
for (size_t i = 0; i < buffer.size(); i++) {
|
||||
@@ -43,7 +45,9 @@ static int l_decode(lua::State* L) {
|
||||
}
|
||||
|
||||
const luaL_Reg base64lib[] = {
|
||||
{"encode", lua::wrap<l_encode>},
|
||||
{"decode", lua::wrap<l_decode>},
|
||||
{"encode", lua::wrap<l_encode<util::base64_encode>>},
|
||||
{"decode", lua::wrap<l_decode<util::base64_decode>>},
|
||||
{"encode_urlsafe", lua::wrap<l_encode<util::base64_urlsafe_encode>>},
|
||||
{"decode_urlsafe", lua::wrap<l_decode<util::base64_urlsafe_decode>>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -20,21 +20,21 @@
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static inline const Block* require_block(lua::State* L) {
|
||||
static inline const Block* get_block_def(lua::State* L) {
|
||||
auto indices = content->getIndices();
|
||||
auto id = lua::tointeger(L, 1);
|
||||
return indices->blocks.get(id);
|
||||
}
|
||||
|
||||
static inline int l_get_def(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
return lua::pushstring(L, def->name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_material(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
return lua::pushstring(L, def->material);
|
||||
}
|
||||
return 0;
|
||||
@@ -59,14 +59,14 @@ static int l_index(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_is_extended(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
return lua::pushboolean(L, def->rt.extended);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_size(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
return lua::pushivec_stack(L, glm::ivec3(def->size));
|
||||
}
|
||||
return 0;
|
||||
@@ -101,12 +101,9 @@ static int l_set(lua::State* L) {
|
||||
if (static_cast<size_t>(id) >= indices->blocks.count()) {
|
||||
return 0;
|
||||
}
|
||||
int cx = floordiv<CHUNK_W>(x);
|
||||
int cz = floordiv<CHUNK_D>(z);
|
||||
if (!blocks_agent::get_chunk(*level->chunks, cx, cz)) {
|
||||
if (!blocks_agent::set(*level->chunks, x, y, z, id, int2blockstate(state))) {
|
||||
return 0;
|
||||
}
|
||||
blocks_agent::set(*level->chunks, x, y, z, id, int2blockstate(state));
|
||||
|
||||
auto chunksController = controller->getChunksController();
|
||||
if (chunksController == nullptr) {
|
||||
@@ -346,14 +343,14 @@ static int l_is_replaceable_at(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_caption(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
return lua::pushstring(L, def->caption);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_textures(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
lua::createtable(L, 6, 0);
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
lua::pushstring(L, def->defaults.textureFaces[i]); // TODO: variant argument
|
||||
@@ -364,8 +361,21 @@ static int l_get_textures(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int l_model_name(lua::State* L) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
// TODO: variant argument
|
||||
const auto& modelName = def->defaults.model.name;
|
||||
if (modelName.empty()) {
|
||||
return lua::pushlstring(L, def->name + ".model");
|
||||
}
|
||||
return lua::pushlstring(L, modelName);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_model(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
// TODO: variant argument
|
||||
return lua::pushlstring(L, BlockModelTypeMeta.getName(def->defaults.model.type));
|
||||
}
|
||||
@@ -373,7 +383,7 @@ static int l_get_model(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_get_hitbox(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
size_t rotation = lua::tointeger(L, 2);
|
||||
if (def->rotatable) {
|
||||
rotation %= def->rotations.MAX_COUNT;
|
||||
@@ -394,14 +404,14 @@ static int l_get_hitbox(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_get_rotation_profile(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
return lua::pushstring(L, def->rotations.name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_picking_item(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
return lua::pushinteger(L, def->rt.pickingItem);
|
||||
}
|
||||
return 0;
|
||||
@@ -688,6 +698,49 @@ static int l_reload_script(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_has_tag(lua::State* L) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
auto tag = lua::require_string(L, 2);
|
||||
const auto& tags = def->rt.tags;
|
||||
return lua::pushboolean(L, tags.find(content->getTagIndex(tag)) != tags.end());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_tags(lua::State* L) {
|
||||
if (auto def = get_block_def(L)) {
|
||||
if (def->tags.empty()) {
|
||||
return 0;
|
||||
}
|
||||
lua::createtable(L, 0, def->tags.size());
|
||||
for (const auto& tag : def->tags) {
|
||||
lua::pushboolean(L, true);
|
||||
lua::setfield(L, tag);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_pull_register_events(lua::State* L) {
|
||||
auto events = blocks_agent::pull_register_events();
|
||||
if (events.empty())
|
||||
return 0;
|
||||
|
||||
lua::createtable(L, events.size() * 4, 0);
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
const auto& event = events[i];
|
||||
lua::pushinteger(L, static_cast<int>(event.type) | event.id << 16);
|
||||
lua::rawseti(L, i * 4 + 1);
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
lua::pushinteger(L, event.coord[j]);
|
||||
lua::rawseti(L, i * 4 + j + 2);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg blocklib[] = {
|
||||
{"index", lua::wrap<l_index>},
|
||||
{"name", lua::wrap<l_get_def>},
|
||||
@@ -713,6 +766,7 @@ const luaL_Reg blocklib[] = {
|
||||
{"get_size", lua::wrap<l_get_size>},
|
||||
{"is_segment", lua::wrap<l_is_segment>},
|
||||
{"seek_origin", lua::wrap<l_seek_origin>},
|
||||
{"model_name", lua::wrap<l_model_name>},
|
||||
{"get_textures", lua::wrap<l_get_textures>},
|
||||
{"get_model", lua::wrap<l_get_model>},
|
||||
{"get_hitbox", lua::wrap<l_get_hitbox>},
|
||||
@@ -726,5 +780,8 @@ const luaL_Reg blocklib[] = {
|
||||
{"get_field", lua::wrap<l_get_field>},
|
||||
{"set_field", lua::wrap<l_set_field>},
|
||||
{"reload_script", lua::wrap<l_reload_script>},
|
||||
{"has_tag", lua::wrap<l_has_tag>},
|
||||
{"__get_tags", lua::wrap<l_get_tags>},
|
||||
{"__pull_register_events", lua::wrap<l_pull_register_events>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -197,9 +197,16 @@ static int l_tpack(lua::State* L) {
|
||||
return pack(L, format, true);
|
||||
}
|
||||
|
||||
static int l_get_size(lua::State* L) {
|
||||
return lua::pushinteger(
|
||||
L, static_cast<int>(calc_size(lua::require_string(L, 1)))
|
||||
);
|
||||
}
|
||||
|
||||
const luaL_Reg byteutillib[] = {
|
||||
{"pack", lua::wrap<l_pack>},
|
||||
{"tpack", lua::wrap<l_tpack>},
|
||||
{"unpack", lua::wrap<l_unpack>},
|
||||
{"get_size", lua::wrap<l_get_size>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "engine/Engine.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "objects/Rigidbody.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/rigging.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "frontend/hud.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "api_lua.hpp"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "util/stringutil.hpp"
|
||||
#include "api_lua.hpp"
|
||||
#include "../lua_engine.hpp"
|
||||
#include "logic/scripting/descriptors_manager.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace scripting;
|
||||
@@ -258,6 +259,149 @@ static int l_create_zip(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_open_descriptor(lua::State* L) {
|
||||
io::path path = lua::require_string(L, 1);
|
||||
auto mode = lua::require_lstring(L, 2);
|
||||
|
||||
bool write = mode.find('w') != std::string::npos;
|
||||
bool read = mode.find('r') != std::string::npos;
|
||||
|
||||
if (write && !is_writeable(path.entryPoint())) {
|
||||
throw std::runtime_error("access denied");
|
||||
}
|
||||
|
||||
if(!write && !read) {
|
||||
throw std::runtime_error("mode must contain read or write flag");
|
||||
}
|
||||
|
||||
if(write && read) {
|
||||
throw std::runtime_error("random access file i/o is not supported");
|
||||
}
|
||||
|
||||
bool wplusMode = write && mode.find('+') != std::string::npos;
|
||||
|
||||
std::vector<char> buffer;
|
||||
|
||||
if(wplusMode) {
|
||||
int temp_descriptor = scripting::descriptors_manager::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);
|
||||
|
||||
in_stream->seekg(0, std::ios::end);
|
||||
std::streamsize size = in_stream->tellg();
|
||||
in_stream->seekg(0, std::ios::beg);
|
||||
|
||||
buffer.resize(size);
|
||||
in_stream->read(buffer.data(), size);
|
||||
|
||||
scripting::descriptors_manager::close(temp_descriptor);
|
||||
}
|
||||
|
||||
int descriptor = scripting::descriptors_manager::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);
|
||||
out_stream->write(buffer.data(), buffer.size());
|
||||
out_stream->flush();
|
||||
}
|
||||
|
||||
return lua::pushinteger(L, descriptor);
|
||||
}
|
||||
|
||||
static int l_has_descriptor(lua::State* L) {
|
||||
return lua::pushboolean(L, scripting::descriptors_manager::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);
|
||||
|
||||
util::Buffer<char> buffer(maxlen);
|
||||
|
||||
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()) {
|
||||
throw std::runtime_error("failed to write to stream");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_flush_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");
|
||||
}
|
||||
|
||||
scripting::descriptors_manager::flush(descriptor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_close_descriptor(lua::State* L) {
|
||||
int descriptor = lua::tointeger(L, 1);
|
||||
|
||||
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
|
||||
throw std::runtime_error("unknown descriptor");
|
||||
}
|
||||
|
||||
scripting::descriptors_manager::close(descriptor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_close_all_descriptors(lua::State* L) {
|
||||
scripting::descriptors_manager::close_all_descriptors();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg filelib[] = {
|
||||
{"exists", lua::wrap<l_exists>},
|
||||
{"find", lua::wrap<l_find>},
|
||||
@@ -283,5 +427,12 @@ const luaL_Reg filelib[] = {
|
||||
{"mount", lua::wrap<l_mount>},
|
||||
{"unmount", lua::wrap<l_unmount>},
|
||||
{"create_zip", lua::wrap<l_create_zip>},
|
||||
{"__open_descriptor", lua::wrap<l_open_descriptor>},
|
||||
{"__has_descriptor", lua::wrap<l_has_descriptor>},
|
||||
{"__read_descriptor", lua::wrap<l_read_descriptor>},
|
||||
{"__write_descriptor", lua::wrap<l_write_descriptor>},
|
||||
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
|
||||
{"__close_descriptor", lua::wrap<l_close_descriptor>},
|
||||
{"__close_all_descriptors", lua::wrap<l_close_all_descriptors>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -43,11 +43,19 @@ static int l_create_fragment(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_load_fragment(lua::State* L) {
|
||||
io::path path = lua::require_string(L, 1);
|
||||
if (!io::exists(path)) {
|
||||
throw std::runtime_error("file "+path.string()+" does not exist");
|
||||
dv::value map;
|
||||
if (!lua::isstring(L, 1)) {
|
||||
io::path path = lua::require_string(L, 1);
|
||||
if (!io::exists(path)) {
|
||||
throw std::runtime_error("file "+path.string()+" does not exist");
|
||||
}
|
||||
map = io::read_binary_json(path);
|
||||
} else {
|
||||
auto bytearray = lua::bytearray_as_string(L, 1);
|
||||
map = json::from_binary(
|
||||
reinterpret_cast<const ubyte*>(bytearray.data()), bytearray.size()
|
||||
);
|
||||
}
|
||||
auto map = io::read_binary_json(path);
|
||||
|
||||
auto fragment = std::make_shared<VoxelFragment>();
|
||||
fragment->deserialize(map);
|
||||
|
||||
@@ -79,9 +79,12 @@ static int l_textbox_paste(lua::State* L) {
|
||||
|
||||
static int l_container_add(lua::State* L) {
|
||||
auto docnode = get_document_node(L);
|
||||
if (docnode.document == nullptr) {
|
||||
throw std::runtime_error("target document not found");
|
||||
}
|
||||
auto node = dynamic_cast<Container*>(docnode.node.get());
|
||||
if (node == nullptr) {
|
||||
return 0;
|
||||
throw std::runtime_error("target container not found");
|
||||
}
|
||||
auto xmlsrc = lua::require_string(L, 2);
|
||||
try {
|
||||
@@ -99,7 +102,7 @@ static int l_container_add(lua::State* L) {
|
||||
UINode::getIndices(subnode, docnode.document->getMapWriteable());
|
||||
node->add(std::move(subnode));
|
||||
} catch (const std::exception& err) {
|
||||
throw std::runtime_error(err.what());
|
||||
throw std::runtime_error("container:add(...): " + std::string(err.what()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ static int l_mousecode(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_add_callback(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
std::string bindname = lua::require_string(L, 1);
|
||||
size_t pos = bindname.find(':');
|
||||
|
||||
@@ -75,10 +77,14 @@ static int l_add_callback(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_get_mouse_pos(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
return lua::pushvec2(L, engine->getInput().getCursor().pos);
|
||||
}
|
||||
|
||||
static int l_get_bindings(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
const auto& bindings = engine->getInput().getBindings().getAll();
|
||||
lua::createtable(L, bindings.size(), 0);
|
||||
|
||||
@@ -92,18 +98,24 @@ static int l_get_bindings(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_get_binding_text(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
auto bindname = lua::require_string(L, 1);
|
||||
const auto& bind = engine->getInput().getBindings().require(bindname);
|
||||
return lua::pushstring(L, bind.text());
|
||||
}
|
||||
|
||||
static int l_is_active(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
auto bindname = lua::require_string(L, 1);
|
||||
auto& bind = engine->getInput().getBindings().require(bindname);
|
||||
return lua::pushboolean(L, bind.active());
|
||||
}
|
||||
|
||||
static int l_is_pressed(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
std::string code = lua::require_string(L, 1);
|
||||
size_t sep = code.find(':');
|
||||
if (sep == std::string::npos) {
|
||||
@@ -136,6 +148,8 @@ static void reset_pack_bindings(const io::path& packFolder) {
|
||||
}
|
||||
|
||||
static int l_reset_bindings(lua::State*) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
reset_pack_bindings("res:");
|
||||
for (const auto& pack : content_control->getContentPacks()) {
|
||||
reset_pack_bindings(pack.folder);
|
||||
@@ -144,6 +158,8 @@ static int l_reset_bindings(lua::State*) {
|
||||
}
|
||||
|
||||
static int l_set_enabled(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
std::string bindname = lua::require_string(L, 1);
|
||||
bool enabled = lua::toboolean(L, 2);
|
||||
engine->getInput().getBindings().require(bindname).enabled = enabled;
|
||||
@@ -161,4 +177,5 @@ const luaL_Reg inputlib[] = {
|
||||
{"is_pressed", lua::wrap<l_is_pressed>},
|
||||
{"reset_bindings", lua::wrap<l_reset_bindings>},
|
||||
{"set_enabled", lua::wrap<l_set_enabled>},
|
||||
{NULL, NULL}};
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -45,6 +45,12 @@ namespace {
|
||||
|
||||
template <SlotFunc func>
|
||||
int wrap_slot(lua::State* L) {
|
||||
if (lua::isnoneornil(L, 1)) {
|
||||
throw std::runtime_error("inventory id is nil");
|
||||
}
|
||||
if (lua::isnoneornil(L, 2)) {
|
||||
throw std::runtime_error("slot index is nil");
|
||||
}
|
||||
auto invid = lua::tointeger(L, 1);
|
||||
auto slotid = lua::tointeger(L, 2);
|
||||
auto& inv = get_inventory(invid);
|
||||
|
||||
@@ -57,6 +57,13 @@ static int l_caption(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_description(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
return lua::pushstring(L, def->description);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_placing_block(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
return lua::pushinteger(L, def->rt.placingBlock);
|
||||
@@ -101,6 +108,30 @@ static int l_reload_script(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_has_tag(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
auto tag = lua::require_string(L, 2);
|
||||
const auto& tags = def->rt.tags;
|
||||
return lua::pushboolean(L, tags.find(content->getTagIndex(tag)) != tags.end());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_tags(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
if (def->tags.empty()) {
|
||||
return 0;
|
||||
}
|
||||
lua::createtable(L, 0, def->tags.size());
|
||||
for (const auto& tag : def->tags) {
|
||||
lua::pushboolean(L, true);
|
||||
lua::setfield(L, tag);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg itemlib[] = {
|
||||
{"index", lua::wrap<l_index>},
|
||||
{"name", lua::wrap<l_name>},
|
||||
@@ -108,10 +139,13 @@ const luaL_Reg itemlib[] = {
|
||||
{"defs_count", lua::wrap<l_defs_count>},
|
||||
{"icon", lua::wrap<l_get_icon>},
|
||||
{"caption", lua::wrap<l_caption>},
|
||||
{"description", lua::wrap<l_description>},
|
||||
{"placing_block", lua::wrap<l_placing_block>},
|
||||
{"model_name", lua::wrap<l_model_name>},
|
||||
{"emission", lua::wrap<l_emission>},
|
||||
{"uses", lua::wrap<l_uses>},
|
||||
{"reload_script", lua::wrap<l_reload_script>},
|
||||
{"has_tag", lua::wrap<l_has_tag>},
|
||||
{"__get_tags", lua::wrap<l_get_tags>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -1,76 +1,126 @@
|
||||
#include "api_lua.hpp"
|
||||
|
||||
#include "coders/json.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "coders/json.hpp"
|
||||
|
||||
#include <variant>
|
||||
#include <utility>
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static int l_get(lua::State* L, network::Network& network) {
|
||||
std::string url(lua::require_lstring(L, 1));
|
||||
enum NetworkEventType {
|
||||
CLIENT_CONNECTED = 1,
|
||||
CONNECTED_TO_SERVER,
|
||||
DATAGRAM,
|
||||
RESPONSE,
|
||||
};
|
||||
|
||||
lua::pushvalue(L, 2);
|
||||
auto onResponse = lua::create_lambda_nothrow(L);
|
||||
struct ConnectionEventDto {
|
||||
u64id_t server;
|
||||
u64id_t client;
|
||||
};
|
||||
|
||||
network::OnReject onReject = nullptr;
|
||||
if (lua::gettop(L) >= 3) {
|
||||
lua::pushvalue(L, 3);
|
||||
auto callback = lua::create_lambda_nothrow(L);
|
||||
onReject = [callback](int code) {
|
||||
callback({code});
|
||||
};
|
||||
struct ResponseEventDto {
|
||||
int status;
|
||||
bool binary;
|
||||
int requestId;
|
||||
std::vector<char> bytes;
|
||||
};
|
||||
|
||||
enum NetworkDatagramSide {
|
||||
ON_SERVER = 1,
|
||||
ON_CLIENT
|
||||
};
|
||||
|
||||
struct NetworkDatagramEventDto {
|
||||
NetworkDatagramSide side;
|
||||
u64id_t server;
|
||||
u64id_t client;
|
||||
std::string addr;
|
||||
int port;
|
||||
std::vector<char> buffer;
|
||||
};
|
||||
|
||||
struct NetworkEvent {
|
||||
using Payload = std::variant<
|
||||
ConnectionEventDto,
|
||||
ResponseEventDto,
|
||||
NetworkDatagramEventDto
|
||||
>;
|
||||
NetworkEventType type;
|
||||
|
||||
Payload payload;
|
||||
|
||||
NetworkEvent(
|
||||
NetworkEventType type,
|
||||
Payload payload
|
||||
) : type(type), payload(std::move(payload)) {}
|
||||
|
||||
virtual ~NetworkEvent() = default;
|
||||
};
|
||||
|
||||
static std::vector<NetworkEvent> events_queue {};
|
||||
static std::mutex events_queue_mutex;
|
||||
|
||||
static void push_event(NetworkEvent&& event) {
|
||||
std::lock_guard lock(events_queue_mutex);
|
||||
events_queue.push_back(std::move(event));
|
||||
}
|
||||
|
||||
static std::vector<std::string> read_headers(lua::State* L, int index) {
|
||||
std::vector<std::string> headers;
|
||||
if (lua::istable(L, index)) {
|
||||
int len = lua::objlen(L, index);
|
||||
for (int i = 1; i <= len; i++) {
|
||||
lua::rawgeti(L, i, index);
|
||||
headers.push_back(lua::tostring(L, -1));
|
||||
lua::pop(L);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
network.get(url, [onResponse](std::vector<char> bytes) {
|
||||
engine->postRunnable([=]() {
|
||||
onResponse({std::string(bytes.data(), bytes.size())});
|
||||
});
|
||||
}, std::move(onReject));
|
||||
return 0;
|
||||
static int request_id = 1;
|
||||
|
||||
static int perform_get(lua::State* L, network::Network& network, bool binary) {
|
||||
std::string url(lua::require_lstring(L, 1));
|
||||
auto headers = read_headers(L, 2);
|
||||
|
||||
int currentRequestId = request_id++;
|
||||
|
||||
network.get(
|
||||
url,
|
||||
[currentRequestId, binary](std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
200, binary, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
[currentRequestId, binary](int code, std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
code, binary, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
std::move(headers)
|
||||
);
|
||||
return lua::pushinteger(L, currentRequestId);
|
||||
}
|
||||
|
||||
static int l_get(lua::State* L, network::Network& network) {
|
||||
return perform_get(L, network, false);
|
||||
}
|
||||
|
||||
static int l_get_binary(lua::State* L, network::Network& network) {
|
||||
std::string url(lua::require_lstring(L, 1));
|
||||
|
||||
lua::pushvalue(L, 2);
|
||||
auto onResponse = lua::create_lambda_nothrow(L);
|
||||
|
||||
network::OnReject onReject = nullptr;
|
||||
if (lua::gettop(L) >= 3) {
|
||||
lua::pushvalue(L, 3);
|
||||
auto callback = lua::create_lambda_nothrow(L);
|
||||
onReject = [callback](int code) {
|
||||
callback({code});
|
||||
};
|
||||
}
|
||||
|
||||
network.get(url, [onResponse](std::vector<char> bytes) {
|
||||
auto buffer = std::make_shared<util::Buffer<ubyte>>(
|
||||
reinterpret_cast<const ubyte*>(bytes.data()), bytes.size()
|
||||
);
|
||||
engine->postRunnable([=]() {
|
||||
onResponse({buffer});
|
||||
});
|
||||
}, std::move(onReject));
|
||||
return 0;
|
||||
return perform_get(L, network, true);
|
||||
}
|
||||
|
||||
static int l_post(lua::State* L, network::Network& network) {
|
||||
std::string url(lua::require_lstring(L, 1));
|
||||
auto data = lua::tovalue(L, 2);
|
||||
|
||||
lua::pushvalue(L, 3);
|
||||
auto onResponse = lua::create_lambda_nothrow(L);
|
||||
|
||||
network::OnReject onReject = nullptr;
|
||||
if (lua::gettop(L) >= 4) {
|
||||
lua::pushvalue(L, 4);
|
||||
auto callback = lua::create_lambda_nothrow(L);
|
||||
onReject = [callback](int code) {
|
||||
callback({code});
|
||||
};
|
||||
}
|
||||
|
||||
std::string string;
|
||||
if (data.isString()) {
|
||||
string = data.asString();
|
||||
@@ -78,15 +128,29 @@ static int l_post(lua::State* L, network::Network& network) {
|
||||
string = json::stringify(data, false);
|
||||
}
|
||||
|
||||
engine->getNetwork().post(url, string, [onResponse](std::vector<char> bytes) {
|
||||
auto buffer = std::make_shared<util::Buffer<ubyte>>(
|
||||
reinterpret_cast<const ubyte*>(bytes.data()), bytes.size()
|
||||
);
|
||||
engine->postRunnable([=]() {
|
||||
onResponse({std::string(bytes.data(), bytes.size())});
|
||||
});
|
||||
}, std::move(onReject));
|
||||
return 0;
|
||||
auto headers = read_headers(L, 3);
|
||||
int currentRequestId = request_id++;
|
||||
|
||||
engine->getNetwork().post(
|
||||
url,
|
||||
string,
|
||||
[currentRequestId](std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
200, false, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
[currentRequestId](int code, std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
code, false, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
std::move(headers)
|
||||
);
|
||||
return lua::pushinteger(L, currentRequestId);
|
||||
}
|
||||
|
||||
static int l_close(lua::State* L, network::Network& network) {
|
||||
@@ -134,17 +198,58 @@ static int l_send(lua::State* L, network::Network& network) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_udp_server_send_to(lua::State* L, network::Network& network) {
|
||||
u64id_t id = lua::tointeger(L, 1);
|
||||
|
||||
if (auto server = network.getServer(id)) {
|
||||
if (server->getTransportType() != network::TransportType::UDP)
|
||||
throw std::runtime_error("the server must work on UDP transport");
|
||||
|
||||
const std::string& addr = lua::tostring(L, 2);
|
||||
const int& port = lua::tointeger(L, 3);
|
||||
|
||||
auto udpServer = dynamic_cast<network::UdpServer*>(server);
|
||||
|
||||
if (lua::istable(L, 4)) {
|
||||
lua::pushvalue(L, 4);
|
||||
size_t size = lua::objlen(L, 4);
|
||||
util::Buffer<char> buffer(size);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
lua::rawgeti(L, i + 1);
|
||||
buffer[i] = lua::tointeger(L, -1);
|
||||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
udpServer->sendTo(addr, port, buffer.data(), size);
|
||||
} else if (lua::isstring(L, 4)) {
|
||||
auto string = lua::tolstring(L, 4);
|
||||
udpServer->sendTo(addr, port, string.data(), string.length());
|
||||
} else {
|
||||
auto string = lua::bytearray_as_string(L, 4);
|
||||
udpServer->sendTo(addr, port, string.data(), string.length());
|
||||
lua::pop(L);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_recv(lua::State* L, network::Network& network) {
|
||||
u64id_t id = lua::tointeger(L, 1);
|
||||
int length = lua::tointeger(L, 2);
|
||||
|
||||
auto connection = engine->getNetwork().getConnection(id);
|
||||
if (connection == nullptr) {
|
||||
|
||||
if (connection == nullptr || connection->getTransportType() != network::TransportType::TCP) {
|
||||
return 0;
|
||||
}
|
||||
length = glm::min(length, connection->available());
|
||||
|
||||
auto tcpConnection = dynamic_cast<network::TcpConnection*>(connection);
|
||||
|
||||
length = glm::min(length, tcpConnection->available());
|
||||
util::Buffer<char> buffer(length);
|
||||
|
||||
int size = connection->recv(buffer.data(), length);
|
||||
int size = tcpConnection->recv(buffer.data(), length);
|
||||
if (size == -1) {
|
||||
return 0;
|
||||
}
|
||||
@@ -162,38 +267,78 @@ static int l_recv(lua::State* L, network::Network& network) {
|
||||
|
||||
static int l_available(lua::State* L, network::Network& network) {
|
||||
u64id_t id = lua::tointeger(L, 1);
|
||||
|
||||
if (auto connection = network.getConnection(id)) {
|
||||
return lua::pushinteger(L, connection->available());
|
||||
return lua::pushinteger(L, dynamic_cast<network::TcpConnection*>(connection)->available());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum NetworkEventType {
|
||||
CLIENT_CONNECTED = 1,
|
||||
CONNECTED_TO_SERVER
|
||||
};
|
||||
|
||||
struct NetworkEvent {
|
||||
NetworkEventType type;
|
||||
u64id_t server;
|
||||
u64id_t client;
|
||||
};
|
||||
|
||||
static std::vector<NetworkEvent> events_queue {};
|
||||
|
||||
static int l_connect(lua::State* L, network::Network& network) {
|
||||
static int l_connect_tcp(lua::State* L, network::Network& network) {
|
||||
std::string address = lua::require_string(L, 1);
|
||||
int port = lua::tointeger(L, 2);
|
||||
u64id_t id = network.connect(address, port, [](u64id_t cid) {
|
||||
events_queue.push_back({CONNECTED_TO_SERVER, 0, cid});
|
||||
u64id_t id = network.connectTcp(address, port, [](u64id_t cid) {
|
||||
push_event(NetworkEvent(
|
||||
CONNECTED_TO_SERVER,
|
||||
ConnectionEventDto {0, cid}
|
||||
));
|
||||
});
|
||||
return lua::pushinteger(L, id);
|
||||
}
|
||||
|
||||
static int l_open(lua::State* L, network::Network& network) {
|
||||
static int l_open_tcp(lua::State* L, network::Network& network) {
|
||||
int port = lua::tointeger(L, 1);
|
||||
u64id_t id = network.openServer(port, [](u64id_t sid, u64id_t id) {
|
||||
events_queue.push_back({CLIENT_CONNECTED, sid, id});
|
||||
u64id_t id = network.openTcpServer(port, [](u64id_t sid, u64id_t id) {
|
||||
push_event(NetworkEvent(
|
||||
CLIENT_CONNECTED,
|
||||
ConnectionEventDto {sid, id}
|
||||
));
|
||||
});
|
||||
return lua::pushinteger(L, id);
|
||||
}
|
||||
|
||||
static int l_connect_udp(lua::State* L, network::Network& network) {
|
||||
std::string address = lua::require_string(L, 1);
|
||||
int port = lua::tointeger(L, 2);
|
||||
u64id_t id = network.connectUdp(address, port, [](u64id_t cid) {
|
||||
push_event(NetworkEvent(
|
||||
CONNECTED_TO_SERVER,
|
||||
ConnectionEventDto {0, cid}
|
||||
));
|
||||
}, [address, port](
|
||||
u64id_t cid,
|
||||
const char* buffer,
|
||||
size_t length
|
||||
) {
|
||||
push_event(NetworkEvent(
|
||||
DATAGRAM,
|
||||
NetworkDatagramEventDto {
|
||||
ON_CLIENT, 0, cid,
|
||||
address, port, std::vector<char>(buffer, buffer + length)
|
||||
}
|
||||
));
|
||||
});
|
||||
return lua::pushinteger(L, id);
|
||||
}
|
||||
|
||||
static int l_open_udp(lua::State* L, network::Network& network) {
|
||||
int port = lua::tointeger(L, 1);
|
||||
u64id_t id = network.openUdpServer(port, [](
|
||||
u64id_t sid,
|
||||
const std::string& addr,
|
||||
int port,
|
||||
const char* buffer,
|
||||
size_t length) {
|
||||
push_event(
|
||||
NetworkEvent(
|
||||
DATAGRAM,
|
||||
NetworkDatagramEventDto {
|
||||
ON_SERVER, sid, 0,
|
||||
addr, port, std::vector<char>(buffer, buffer + length)
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
return lua::pushinteger(L, id);
|
||||
}
|
||||
@@ -204,7 +349,10 @@ static int l_is_alive(lua::State* L, network::Network& network) {
|
||||
return lua::pushboolean(
|
||||
L,
|
||||
connection->getState() != network::ConnectionState::CLOSED ||
|
||||
connection->available() > 0
|
||||
(
|
||||
connection->getTransportType() == network::TransportType::TCP &&
|
||||
dynamic_cast<network::TcpConnection*>(connection)->available() > 0
|
||||
)
|
||||
);
|
||||
}
|
||||
return lua::pushboolean(L, false);
|
||||
@@ -255,22 +403,78 @@ static int l_get_total_download(lua::State* L, network::Network& network) {
|
||||
}
|
||||
|
||||
static int l_pull_events(lua::State* L, network::Network& network) {
|
||||
lua::createtable(L, events_queue.size(), 0);
|
||||
for (size_t i = 0; i < events_queue.size(); i++) {
|
||||
lua::createtable(L, 3, 0);
|
||||
std::vector<NetworkEvent> local_queue;
|
||||
{
|
||||
std::lock_guard lock(events_queue_mutex);
|
||||
local_queue.swap(events_queue);
|
||||
}
|
||||
|
||||
lua::pushinteger(L, events_queue[i].type);
|
||||
lua::rawseti(L, 1);
|
||||
lua::createtable(L, local_queue.size(), 0);
|
||||
|
||||
lua::pushinteger(L, events_queue[i].server);
|
||||
lua::rawseti(L, 2);
|
||||
for (size_t i = 0; i < local_queue.size(); i++) {
|
||||
lua::createtable(L, 7, 0);
|
||||
|
||||
lua::pushinteger(L, events_queue[i].client);
|
||||
lua::rawseti(L, 3);
|
||||
|
||||
const auto& event = local_queue[i];
|
||||
switch (event.type) {
|
||||
case CLIENT_CONNECTED:
|
||||
case CONNECTED_TO_SERVER: {
|
||||
const auto& dto = std::get<ConnectionEventDto>(event.payload);
|
||||
lua::pushinteger(L, event.type);
|
||||
lua::rawseti(L, 1);
|
||||
|
||||
lua::pushinteger(L, dto.server);
|
||||
lua::rawseti(L, 2);
|
||||
|
||||
lua::pushinteger(L, dto.client);
|
||||
lua::rawseti(L, 3);
|
||||
break;
|
||||
}
|
||||
case DATAGRAM: {
|
||||
const auto& dto = std::get<NetworkDatagramEventDto>(event.payload);
|
||||
lua::pushinteger(L, event.type);
|
||||
lua::rawseti(L, 1);
|
||||
|
||||
lua::pushinteger(L, dto.server);
|
||||
lua::rawseti(L, 2);
|
||||
|
||||
lua::pushinteger(L, dto.client);
|
||||
lua::rawseti(L, 3);
|
||||
|
||||
lua::pushstring(L, dto.addr);
|
||||
lua::rawseti(L, 4);
|
||||
|
||||
lua::pushinteger(L, dto.port);
|
||||
lua::rawseti(L, 5);
|
||||
|
||||
lua::pushinteger(L, dto.side);
|
||||
lua::rawseti(L, 6);
|
||||
|
||||
lua::create_bytearray(L, dto.buffer.data(), dto.buffer.size());
|
||||
lua::rawseti(L, 7);
|
||||
break;
|
||||
}
|
||||
case RESPONSE: {
|
||||
const auto& dto = std::get<ResponseEventDto>(event.payload);
|
||||
lua::pushinteger(L, event.type);
|
||||
lua::rawseti(L, 1);
|
||||
|
||||
lua::pushinteger(L, dto.status);
|
||||
lua::rawseti(L, 2);
|
||||
|
||||
lua::pushinteger(L, dto.requestId);
|
||||
lua::rawseti(L, 3);
|
||||
|
||||
if (dto.binary) {
|
||||
lua::create_bytearray(L, dto.bytes.data(), dto.bytes.size());
|
||||
} else {
|
||||
lua::pushlstring(L, std::string_view(dto.bytes.data(), dto.bytes.size()));
|
||||
}
|
||||
lua::rawseti(L, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
events_queue.clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -292,15 +496,18 @@ int wrap(lua_State* L) {
|
||||
}
|
||||
|
||||
const luaL_Reg networklib[] = {
|
||||
{"get", wrap<l_get>},
|
||||
{"get_binary", wrap<l_get_binary>},
|
||||
{"post", wrap<l_post>},
|
||||
{"__get", wrap<l_get>},
|
||||
{"__get_binary", wrap<l_get_binary>},
|
||||
{"__post", wrap<l_post>},
|
||||
{"get_total_upload", wrap<l_get_total_upload>},
|
||||
{"get_total_download", wrap<l_get_total_download>},
|
||||
{"__pull_events", wrap<l_pull_events>},
|
||||
{"__open", wrap<l_open>},
|
||||
{"__open_tcp", wrap<l_open_tcp>},
|
||||
{"__open_udp", wrap<l_open_udp>},
|
||||
{"__closeserver", wrap<l_closeserver>},
|
||||
{"__connect", wrap<l_connect>},
|
||||
{"__udp_server_send_to", wrap<l_udp_server_send_to>},
|
||||
{"__connect_tcp", wrap<l_connect_tcp>},
|
||||
{"__connect_udp", wrap<l_connect_udp>},
|
||||
{"__close", wrap<l_close>},
|
||||
{"__send", wrap<l_send>},
|
||||
{"__recv", wrap<l_recv>},
|
||||
|
||||
@@ -102,19 +102,20 @@ static int l_pack_get_info(
|
||||
auto& dpack = pack.dependencies[i];
|
||||
std::string prefix;
|
||||
switch (dpack.level) {
|
||||
case DependencyLevel::required:
|
||||
case DependencyLevel::REQUIRED:
|
||||
prefix = "!";
|
||||
break;
|
||||
case DependencyLevel::optional:
|
||||
case DependencyLevel::OPTIONAL:
|
||||
prefix = "?";
|
||||
break;
|
||||
case DependencyLevel::weak:
|
||||
case DependencyLevel::WEAK:
|
||||
prefix = "~";
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("");
|
||||
}
|
||||
lua::pushfstring(L, "%s%s", prefix.c_str(), dpack.id.c_str());
|
||||
|
||||
lua::pushfstring(L, "%s%s@%s%s", prefix.c_str(), dpack.id.c_str(), dpack.op.c_str(), dpack.version.c_str());
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
lua::setfield(L, "dependencies");
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "api_lua.hpp"
|
||||
|
||||
#include "content/Content.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static voxels::Agent* get_agent(lua::State* L) {
|
||||
return level->pathfinding->getAgent(lua::tointeger(L, 1));
|
||||
}
|
||||
|
||||
static int l_create_agent(lua::State* L) {
|
||||
return lua::pushinteger(L, level->pathfinding->createAgent());
|
||||
}
|
||||
|
||||
static int l_remove_agent(lua::State* L) {
|
||||
int id = lua::tointeger(L, 1);
|
||||
return lua::pushboolean(L, level->pathfinding->removeAgent(id));
|
||||
}
|
||||
|
||||
static int l_set_enabled(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
agent->enabled = lua::toboolean(L, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_enabled(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
return lua::pushboolean(L, agent->enabled);
|
||||
}
|
||||
return lua::pushboolean(L, false);
|
||||
}
|
||||
|
||||
static int push_route(lua::State* L, const voxels::Route& route) {
|
||||
lua::createtable(L, route.nodes.size(), 1);
|
||||
for (int i = 0; i < route.nodes.size(); i++) {
|
||||
lua::pushvec3(L, route.nodes[i].pos);
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
lua::pushinteger(L, route.totalVisited);
|
||||
lua::setfield(L, "total_visited");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_make_route(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
auto start = lua::tovec3(L, 2);
|
||||
auto target = lua::tovec3(L, 3);
|
||||
agent->state = {};
|
||||
agent->start = glm::floor(start);
|
||||
agent->target = target;
|
||||
auto route = level->pathfinding->perform(*agent);
|
||||
if (!route.found) {
|
||||
return 0;
|
||||
}
|
||||
return push_route(L, route);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_make_route_async(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
auto start = lua::tovec3(L, 2);
|
||||
auto target = lua::tovec3(L, 3);
|
||||
agent->state = {};
|
||||
agent->start = glm::floor(start);
|
||||
agent->target = target;
|
||||
level->pathfinding->perform(*agent, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_pull_route(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
auto& route = agent->route;
|
||||
if (!agent->state.finished) {
|
||||
return 0;
|
||||
}
|
||||
if (!route.found && !agent->mayBeIncomplete) {
|
||||
return lua::createtable(L, 0, 0);
|
||||
}
|
||||
return push_route(L, route);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_max_visited_blocks(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
agent->maxVisitedBlocks = lua::tointeger(L, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_jump_height(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
agent->jumpHeight = lua::tointeger(L, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_avoid_tag(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
int index =
|
||||
content->getTagIndex(std::string(lua::require_lstring(L, 2)));
|
||||
if (index != -1) {
|
||||
int cost = lua::tonumber(L, 3);
|
||||
if (cost == 0) {
|
||||
cost = 10;
|
||||
}
|
||||
agent->avoidTags.insert({index, cost});
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg pathfindinglib[] = {
|
||||
{"create_agent", lua::wrap<l_create_agent>},
|
||||
{"remove_agent", lua::wrap<l_remove_agent>},
|
||||
{"set_enabled", lua::wrap<l_set_enabled>},
|
||||
{"is_enabled", lua::wrap<l_is_enabled>},
|
||||
{"make_route", lua::wrap<l_make_route>},
|
||||
{"make_route_async", lua::wrap<l_make_route_async>},
|
||||
{"pull_route", lua::wrap<l_pull_route>},
|
||||
{"set_max_visited", lua::wrap<l_set_max_visited_blocks>},
|
||||
{"set_jump_height", lua::wrap<l_set_jump_height>},
|
||||
{"avoid_tag", lua::wrap<l_avoid_tag>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "api_lua.hpp"
|
||||
|
||||
#include "util/random.hpp"
|
||||
|
||||
static std::random_device random_device;
|
||||
|
||||
static int l_random(lua::State* L) {
|
||||
int argc = lua::gettop(L);
|
||||
|
||||
auto randomEngine = util::seeded_random_engine(random_device);
|
||||
if (argc == 0) {
|
||||
std::uniform_real_distribution<> dist(0.0, 1.0);
|
||||
return lua::pushnumber(L, dist(randomEngine));
|
||||
} else if (argc == 1) {
|
||||
std::uniform_int_distribution<integer_t> dist(1, lua::tointeger(L, 1));
|
||||
return lua::pushinteger(L, dist(randomEngine));
|
||||
} else {
|
||||
std::uniform_int_distribution<integer_t> dist(
|
||||
lua::tointeger(L, 1), lua::tointeger(L, 2)
|
||||
);
|
||||
return lua::pushinteger(L, dist(randomEngine));
|
||||
}
|
||||
}
|
||||
|
||||
static int l_bytes(lua::State* L) {
|
||||
size_t size = lua::tointeger(L, 1);
|
||||
|
||||
auto randomEngine = util::seeded_random_engine(random_device);
|
||||
static std::uniform_int_distribution<integer_t> dist(0, 0xFF);
|
||||
std::vector<ubyte> bytes (size);
|
||||
for (size_t i = 0; i < bytes.size(); i++) {
|
||||
bytes[i] = dist(randomEngine);
|
||||
}
|
||||
return lua::create_bytearray(L, bytes);
|
||||
}
|
||||
|
||||
static int l_uuid(lua::State* L) {
|
||||
return lua::pushlstring(L, util::generate_uuid());
|
||||
}
|
||||
|
||||
const luaL_Reg randomlib[] = {
|
||||
{"random", lua::wrap<l_random>},
|
||||
{"bytes", lua::wrap<l_bytes>},
|
||||
{"uuid", lua::wrap<l_uuid>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
@@ -1,8 +1,13 @@
|
||||
#include "engine/Engine.hpp"
|
||||
#include "api_lua.hpp"
|
||||
#include <ctime>
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define USE_MSVC_TIME_SAFE
|
||||
#endif
|
||||
|
||||
static int l_uptime(lua::State* L) {
|
||||
return lua::pushnumber(L, engine->getTime().getTime());
|
||||
}
|
||||
@@ -11,8 +16,57 @@ static int l_delta(lua::State* L) {
|
||||
return lua::pushnumber(L, engine->getTime().getDelta());
|
||||
}
|
||||
|
||||
static int l_utc_time(lua::State* L) {
|
||||
return lua::pushnumber(L, std::time(nullptr));
|
||||
}
|
||||
|
||||
static int l_local_time(lua::State* L) {
|
||||
std::time_t t = std::time(nullptr);
|
||||
|
||||
std::tm gmt_tm{};
|
||||
std::tm local_tm{};
|
||||
|
||||
#if defined(USE_MSVC_TIME_SAFE)
|
||||
gmtime_s(&gmt_tm, &t);
|
||||
localtime_s(&local_tm, &t);
|
||||
#else
|
||||
gmtime_r(&t, &gmt_tm);
|
||||
localtime_r(&t, &local_tm);
|
||||
#endif
|
||||
|
||||
std::time_t utc_time = std::mktime(&gmt_tm);
|
||||
std::time_t local_time = std::mktime(&local_tm);
|
||||
std::time_t offset = local_time - utc_time;
|
||||
|
||||
return lua::pushnumber(L, t + offset);
|
||||
}
|
||||
|
||||
static int l_utc_offset(lua::State* L) {
|
||||
std::time_t t = std::time(nullptr);
|
||||
|
||||
std::tm gmt_tm{};
|
||||
std::tm local_tm{};
|
||||
|
||||
#if defined(USE_MSVC_TIME_SAFE)
|
||||
gmtime_s(&gmt_tm, &t);
|
||||
localtime_s(&local_tm, &t);
|
||||
#else
|
||||
gmtime_r(&t, &gmt_tm);
|
||||
localtime_r(&t, &local_tm);
|
||||
#endif
|
||||
|
||||
std::time_t utc_time = std::mktime(&gmt_tm);
|
||||
std::time_t local_time = std::mktime(&local_tm);
|
||||
std::time_t offset = local_time - utc_time;
|
||||
|
||||
return lua::pushnumber(L, offset);
|
||||
}
|
||||
|
||||
const luaL_Reg timelib[] = {
|
||||
{"uptime", lua::wrap<l_uptime>},
|
||||
{"delta", lua::wrap<l_delta>},
|
||||
{"utc_time", lua::wrap<l_utc_time>},
|
||||
{"utc_offset", lua::wrap<l_utc_offset>},
|
||||
{"local_time", lua::wrap<l_local_time>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -15,10 +15,24 @@ inline T angle(glm::vec<2, T> vec) {
|
||||
return val;
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_mix(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 3, 4);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
auto t = lua::tonumber(L, 3);
|
||||
|
||||
if (argc == 3) {
|
||||
return lua::pushvec(L, a * (1.0 - t) + b * t);
|
||||
} else {
|
||||
return lua::setvec(L, 4, a * (1.0 - t) + b * t);
|
||||
}
|
||||
}
|
||||
|
||||
template <int n, template <class> class Op>
|
||||
static int l_binop(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 2, 3);
|
||||
auto a = lua::tovec<n>(L, 1);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
|
||||
if (lua::isnumber(L, 2)) { // scalar second operand overload
|
||||
auto b = lua::tonumber(L, 2);
|
||||
@@ -31,10 +45,10 @@ static int l_binop(lua::State* L) {
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
return lua::setvec(L, 3, op(a, glm::vec<n, float>(b)));
|
||||
return lua::setvec(L, 3, op(a, glm::vec<n, number_t>(b)));
|
||||
}
|
||||
} else {
|
||||
auto b = lua::tovec<n>(L, 2);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
Op op;
|
||||
if (argc == 2) {
|
||||
lua::createtable(L, n, 0);
|
||||
@@ -49,10 +63,10 @@ static int l_binop(lua::State* L) {
|
||||
}
|
||||
}
|
||||
|
||||
template <int n, glm::vec<n, float> (*func)(const glm::vec<n, float>&)>
|
||||
template <int n, glm::vec<n, number_t> (*func)(const glm::vec<n, number_t>&)>
|
||||
static int l_unaryop(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 1, 2);
|
||||
auto vec = func(lua::tovec<n>(L, 1));
|
||||
auto vec = func(lua::tovec<n, number_t>(L, 1));
|
||||
switch (argc) {
|
||||
case 1:
|
||||
lua::createtable(L, n, 0);
|
||||
@@ -67,17 +81,25 @@ static int l_unaryop(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <int n, float (*func)(const glm::vec<n, float>&)>
|
||||
template <int n, number_t (*func)(const glm::vec<n, number_t>&)>
|
||||
static int l_scalar_op(lua::State* L) {
|
||||
lua::check_argc(L, 1);
|
||||
auto vec = lua::tovec<n>(L, 1);
|
||||
auto vec = lua::tovec<n, number_t>(L, 1);
|
||||
return lua::pushnumber(L, func(vec));
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_distance(lua::State* L) {
|
||||
lua::check_argc(L, 2);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
return lua::pushnumber(L,glm::distance(a, b));
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_pow(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 2, 3);
|
||||
auto a = lua::tovec<n>(L, 1);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
|
||||
if (lua::isnumber(L, 2)) {
|
||||
auto b = lua::tonumber(L, 2);
|
||||
@@ -89,10 +111,10 @@ static int l_pow(lua::State* L) {
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
return lua::setvec(L, 3, pow(a, glm::vec<n, float>(b)));
|
||||
return lua::setvec(L, 3, pow(a, glm::vec<n, number_t>(b)));
|
||||
}
|
||||
} else {
|
||||
auto b = lua::tovec<n>(L, 2);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
if (argc == 2) {
|
||||
lua::createtable(L, n, 0);
|
||||
for (uint i = 0; i < n; i++) {
|
||||
@@ -109,15 +131,15 @@ static int l_pow(lua::State* L) {
|
||||
template <int n>
|
||||
static int l_dot(lua::State* L) {
|
||||
lua::check_argc(L, 2);
|
||||
const auto& a = lua::tovec<n>(L, 1);
|
||||
const auto& b = lua::tovec<n>(L, 2);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
return lua::pushnumber(L, glm::dot(a, b));
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_inverse(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 1, 2);
|
||||
auto vec = lua::tovec<n>(L, 1);
|
||||
auto vec = lua::tovec<n, number_t>(L, 1);
|
||||
switch (argc) {
|
||||
case 1:
|
||||
lua::createtable(L, n, 0);
|
||||
@@ -141,7 +163,7 @@ static int l_spherical_rand(lua::State* L) {
|
||||
return lua::setvec(
|
||||
L,
|
||||
2,
|
||||
glm::sphericalRand(static_cast<float>(lua::tonumber(L, 1)))
|
||||
glm::sphericalRand(lua::tonumber(L, 1))
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
@@ -161,10 +183,22 @@ static int l_vec2_angle(lua::State* L) {
|
||||
}
|
||||
}
|
||||
|
||||
static int l_vec2_rotate(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 2, 3);
|
||||
auto vec = lua::tovec<2, number_t>(L, 1);
|
||||
auto angle = glm::radians(lua::tonumber(L, 2));
|
||||
|
||||
if (argc == 2) {
|
||||
return lua::pushvec(L, glm::rotate(vec, angle));
|
||||
} else {
|
||||
return lua::setvec(L, 3, glm::rotate(vec, angle));
|
||||
}
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_tostring(lua::State* L) {
|
||||
lua::check_argc(L, 1);
|
||||
auto vec = lua::tovec<n>(L, 1);
|
||||
auto vec = lua::tovec<n, number_t>(L, 1);
|
||||
std::stringstream ss;
|
||||
ss << "vec" << std::to_string(n) << "{";
|
||||
for (int i = 0; i < n; i++) {
|
||||
@@ -182,6 +216,7 @@ const luaL_Reg vec2lib[] = {
|
||||
{"sub", lua::wrap<l_binop<2, std::minus>>},
|
||||
{"mul", lua::wrap<l_binop<2, std::multiplies>>},
|
||||
{"div", lua::wrap<l_binop<2, std::divides>>},
|
||||
{"distance", lua::wrap<l_distance<2>>},
|
||||
{"normalize", lua::wrap<l_unaryop<2, glm::normalize>>},
|
||||
{"length", lua::wrap<l_scalar_op<2, glm::length>>},
|
||||
{"tostring", lua::wrap<l_tostring<2>>},
|
||||
@@ -191,6 +226,8 @@ const luaL_Reg vec2lib[] = {
|
||||
{"pow", lua::wrap<l_pow<2>>},
|
||||
{"dot", lua::wrap<l_dot<2>>},
|
||||
{"angle", lua::wrap<l_vec2_angle>},
|
||||
{"mix", lua::wrap<l_mix<2>>},
|
||||
{"rotate", lua::wrap<l_vec2_rotate>},
|
||||
{NULL, NULL}};
|
||||
|
||||
const luaL_Reg vec3lib[] = {
|
||||
@@ -198,6 +235,7 @@ const luaL_Reg vec3lib[] = {
|
||||
{"sub", lua::wrap<l_binop<3, std::minus>>},
|
||||
{"mul", lua::wrap<l_binop<3, std::multiplies>>},
|
||||
{"div", lua::wrap<l_binop<3, std::divides>>},
|
||||
{"distance", lua::wrap<l_distance<3>>},
|
||||
{"normalize", lua::wrap<l_unaryop<3, glm::normalize>>},
|
||||
{"length", lua::wrap<l_scalar_op<3, glm::length>>},
|
||||
{"tostring", lua::wrap<l_tostring<3>>},
|
||||
@@ -207,6 +245,7 @@ const luaL_Reg vec3lib[] = {
|
||||
{"pow", lua::wrap<l_pow<3>>},
|
||||
{"dot", lua::wrap<l_dot<3>>},
|
||||
{"spherical_rand", lua::wrap<l_spherical_rand>},
|
||||
{"mix", lua::wrap<l_mix<3>>},
|
||||
{NULL, NULL}};
|
||||
|
||||
const luaL_Reg vec4lib[] = {
|
||||
@@ -214,6 +253,7 @@ const luaL_Reg vec4lib[] = {
|
||||
{"sub", lua::wrap<l_binop<4, std::minus>>},
|
||||
{"mul", lua::wrap<l_binop<4, std::multiplies>>},
|
||||
{"div", lua::wrap<l_binop<4, std::divides>>},
|
||||
{"distance", lua::wrap<l_distance<4>>},
|
||||
{"normalize", lua::wrap<l_unaryop<4, glm::normalize>>},
|
||||
{"length", lua::wrap<l_scalar_op<4, glm::length>>},
|
||||
{"tostring", lua::wrap<l_tostring<4>>},
|
||||
@@ -222,4 +262,5 @@ const luaL_Reg vec4lib[] = {
|
||||
{"inverse", lua::wrap<l_inverse<4>>},
|
||||
{"pow", lua::wrap<l_pow<4>>},
|
||||
{"dot", lua::wrap<l_dot<4>>},
|
||||
{"mix", lua::wrap<l_mix<4>>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@@ -51,6 +51,7 @@ static void create_libs(State* L, StateType stateType) {
|
||||
openlib(L, "mat4", mat4lib);
|
||||
openlib(L, "pack", packlib);
|
||||
openlib(L, "quat", quatlib);
|
||||
openlib(L, "random", randomlib);
|
||||
openlib(L, "toml", tomllib);
|
||||
openlib(L, "utf8", utf8lib);
|
||||
openlib(L, "vec2", vec2lib);
|
||||
@@ -72,6 +73,7 @@ static void create_libs(State* L, StateType stateType) {
|
||||
openlib(L, "input", inputlib);
|
||||
openlib(L, "inventory", inventorylib);
|
||||
openlib(L, "network", networklib);
|
||||
openlib(L, "pathfinding", pathfindinglib);
|
||||
openlib(L, "player", playerlib);
|
||||
openlib(L, "time", timelib);
|
||||
openlib(L, "world", worldlib);
|
||||
|
||||
@@ -48,8 +48,8 @@ namespace lua {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <int n>
|
||||
inline int pushvec(lua::State* L, const glm::vec<n, float>& vec) {
|
||||
template <int n, typename T = float>
|
||||
inline int pushvec(lua::State* L, const glm::vec<n, T>& vec) {
|
||||
createtable(L, n, 0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
pushnumber(L, vec[i]);
|
||||
@@ -161,8 +161,8 @@ namespace lua {
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
template <int n>
|
||||
inline int setvec(lua::State* L, int idx, glm::vec<n, float> vec) {
|
||||
template <int n, typename T = float>
|
||||
inline int setvec(lua::State* L, int idx, glm::vec<n, T> vec) {
|
||||
pushvalue(L, idx);
|
||||
for (int i = 0; i < n; i++) {
|
||||
pushnumber(L, vec[i]);
|
||||
@@ -305,15 +305,15 @@ namespace lua {
|
||||
setglobal(L, name);
|
||||
}
|
||||
|
||||
template <int n>
|
||||
inline glm::vec<n, float> tovec(lua::State* L, int idx) {
|
||||
template <int n, typename T = float>
|
||||
inline glm::vec<n, T> tovec(lua::State* L, int idx) {
|
||||
pushvalue(L, idx);
|
||||
if (!istable(L, idx) || objlen(L, idx) < n) {
|
||||
throw std::runtime_error(
|
||||
"value must be an array of " + std::to_string(n) + " numbers"
|
||||
);
|
||||
}
|
||||
glm::vec<n, float> vec;
|
||||
glm::vec<n, T> vec;
|
||||
for (int i = 0; i < n; i++) {
|
||||
rawgeti(L, i + 1);
|
||||
vec[i] = tonumber(L, -1);
|
||||
@@ -455,7 +455,7 @@ namespace lua {
|
||||
|
||||
inline bool getfield(lua::State* L, const std::string& name, int idx = -1) {
|
||||
lua_getfield(L, idx, name.c_str());
|
||||
if (isnil(L, idx)) {
|
||||
if (isnoneornil(L, -1)) {
|
||||
pop(L);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -19,22 +19,20 @@
|
||||
#include "lua/lua_engine.hpp"
|
||||
#include "lua/lua_custom_types.hpp"
|
||||
#include "maths/Heightmap.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "util/timeutil.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/blocks_agent.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "interfaces/Process.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static debug::Logger logger("scripting");
|
||||
|
||||
static inline const std::string STDCOMP = "stdcomp";
|
||||
|
||||
std::ostream* scripting::output_stream = &std::cout;
|
||||
std::ostream* scripting::error_stream = &std::cerr;
|
||||
Engine* scripting::engine = nullptr;
|
||||
@@ -116,11 +114,47 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Process> scripting::start_coroutine(
|
||||
class LuaProjectScript : public IClientProjectScript {
|
||||
public:
|
||||
LuaProjectScript(lua::State* L, scriptenv env) : L(L), env(std::move(env)) {}
|
||||
|
||||
void onScreenChange(const std::string& name, bool show) override {
|
||||
if (!lua::pushenv(L, *env)) {
|
||||
return;
|
||||
}
|
||||
if (!lua::getfield(L, "on_" + name + (show ? "_setup" : "_clear"))) {
|
||||
lua::pop(L);
|
||||
return;
|
||||
}
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
private:
|
||||
lua::State* L;
|
||||
scriptenv env;
|
||||
};
|
||||
|
||||
std::unique_ptr<IClientProjectScript> scripting::load_client_project_script(
|
||||
const io::path& script
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
if (lua::getglobal(L, "__vc_start_coroutine")) {
|
||||
auto source = io::read_string(script);
|
||||
auto env = create_environment(nullptr);
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::getglobal(L, "__vc_app")) {
|
||||
lua::setfield(L, "app");
|
||||
}
|
||||
lua::pop(L);
|
||||
|
||||
lua::loadbuffer(L, *env, source, script.name());
|
||||
lua::call(L, 0);
|
||||
return std::make_unique<LuaProjectScript>(L, std::move(env));
|
||||
}
|
||||
|
||||
std::unique_ptr<Process> scripting::start_coroutine(const io::path& script) {
|
||||
auto L = lua::get_main_state();
|
||||
auto method = "__vc_start_coroutine";
|
||||
if (lua::getglobal(L, method)) {
|
||||
auto source = io::read_string(script);
|
||||
lua::loadbuffer(L, 0, source, script.name());
|
||||
if (lua::call(L, 1)) {
|
||||
@@ -197,36 +231,6 @@ std::unique_ptr<Process> scripting::start_coroutine(
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] static scriptenv create_component_environment(
|
||||
const scriptenv& parent, int entityIdx, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
int id = lua::create_environment(L, *parent);
|
||||
|
||||
lua::pushvalue(L, entityIdx);
|
||||
|
||||
lua::pushenv(L, id);
|
||||
|
||||
lua::pushvalue(L, -1);
|
||||
lua::setfield(L, "this");
|
||||
|
||||
lua::pushvalue(L, -2);
|
||||
lua::setfield(L, "entity");
|
||||
|
||||
lua::pop(L);
|
||||
if (lua::getfield(L, "components")) {
|
||||
lua::pushenv(L, id);
|
||||
lua::setfield(L, name);
|
||||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
|
||||
return std::shared_ptr<int>(new int(id), [=](int* id) { //-V508
|
||||
lua::remove_environment(L, *id);
|
||||
delete id;
|
||||
});
|
||||
}
|
||||
|
||||
void scripting::process_post_runnables() {
|
||||
auto L = lua::get_main_state();
|
||||
if (lua::getglobal(L, "__process_post_runnables")) {
|
||||
@@ -293,12 +297,20 @@ void scripting::on_world_load(LevelController* controller) {
|
||||
}
|
||||
|
||||
for (auto& pack : content_control->getAllContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ":.worldopen");
|
||||
lua::emit_event(L, pack.id + ":.worldopen", [](auto L) {
|
||||
return lua::pushboolean(
|
||||
L, !scripting::level->getWorld()->getInfo().isLoaded
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_world_tick() {
|
||||
void scripting::on_world_tick(int tps) {
|
||||
auto L = lua::get_main_state();
|
||||
if (lua::getglobal(L, "__vc_on_world_tick")) {
|
||||
lua::pushinteger(L, tps);
|
||||
lua::call_nothrow(L, 1, 0);
|
||||
}
|
||||
for (auto& pack : content_control->getAllContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ":.worldtick");
|
||||
}
|
||||
@@ -453,6 +465,7 @@ void scripting::on_chunk_present(const Chunk& chunk, bool loaded) {
|
||||
);
|
||||
}
|
||||
}
|
||||
blocks_agent::on_chunk_present(*content->getIndices(), chunk);
|
||||
}
|
||||
|
||||
void scripting::on_chunk_remove(const Chunk& chunk) {
|
||||
@@ -467,6 +480,7 @@ void scripting::on_chunk_remove(const Chunk& chunk) {
|
||||
);
|
||||
}
|
||||
}
|
||||
blocks_agent::on_chunk_remove(*content->getIndices(), chunk);
|
||||
}
|
||||
|
||||
void scripting::on_inventory_open(const Player* player, const Inventory& inventory) {
|
||||
@@ -554,244 +568,6 @@ bool scripting::on_item_break_block(
|
||||
);
|
||||
}
|
||||
|
||||
dv::value scripting::get_component_value(
|
||||
const scriptenv& env, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::getfield(L, name)) {
|
||||
return lua::tovalue(L, -1);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void scripting::on_entity_spawn(
|
||||
const EntityDef&,
|
||||
entityid_t eid,
|
||||
const std::vector<std::unique_ptr<UserComponent>>& components,
|
||||
const dv::value& args,
|
||||
const dv::value& saved
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::stackguard guard(L);
|
||||
lua::requireglobal(L, STDCOMP);
|
||||
if (lua::getfield(L, "new_Entity")) {
|
||||
lua::pushinteger(L, eid);
|
||||
lua::call(L, 1);
|
||||
}
|
||||
if (components.size() > 1) {
|
||||
for (size_t i = 0; i < components.size() - 1; i++) {
|
||||
lua::pushvalue(L, -1);
|
||||
}
|
||||
}
|
||||
for (auto& component : components) {
|
||||
auto compenv = create_component_environment(
|
||||
get_root_environment(), -1, component->name
|
||||
);
|
||||
lua::get_from(L, lua::CHUNKS_TABLE, component->name, true);
|
||||
lua::pushenv(L, *compenv);
|
||||
|
||||
if (args != nullptr) {
|
||||
std::string compfieldname = component->name;
|
||||
util::replaceAll(compfieldname, ":", "__");
|
||||
if (args.has(compfieldname)) {
|
||||
lua::pushvalue(L, args[compfieldname]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
lua::setfield(L, "ARGS");
|
||||
|
||||
if (saved == nullptr) {
|
||||
lua::createtable(L, 0, 0);
|
||||
} else {
|
||||
if (saved.has(component->name)) {
|
||||
lua::pushvalue(L, saved[component->name]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::setfield(L, "SAVED_DATA");
|
||||
|
||||
lua::setfenv(L);
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
|
||||
lua::pushenv(L, *compenv);
|
||||
auto& funcsset = component->funcsset;
|
||||
funcsset.on_grounded = lua::hasfield(L, "on_grounded");
|
||||
funcsset.on_fall = lua::hasfield(L, "on_fall");
|
||||
funcsset.on_despawn = lua::hasfield(L, "on_despawn");
|
||||
funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter");
|
||||
funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit");
|
||||
funcsset.on_save = lua::hasfield(L, "on_save");
|
||||
funcsset.on_aim_on = lua::hasfield(L, "on_aim_on");
|
||||
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
|
||||
funcsset.on_attacked = lua::hasfield(L, "on_attacked");
|
||||
funcsset.on_used = lua::hasfield(L, "on_used");
|
||||
lua::pop(L, 2);
|
||||
|
||||
component->env = compenv;
|
||||
}
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const scriptenv& env,
|
||||
const std::string& name,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::hasfield(L, "__disabled")) {
|
||||
lua::pop(L);
|
||||
return;
|
||||
}
|
||||
if (lua::getfield(L, name)) {
|
||||
if (args) {
|
||||
lua::call_nothrow(L, args(L), 0);
|
||||
} else {
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const Entity& entity,
|
||||
const std::string& name,
|
||||
bool EntityFuncsSet::*flag,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
const auto& script = entity.getScripting();
|
||||
for (auto& component : script.components) {
|
||||
if (component->funcsset.*flag) {
|
||||
process_entity_callback(component->env, name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_entity_despawn(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr
|
||||
);
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, "stdcomp", "remove_Entity", true);
|
||||
lua::pushinteger(L, entity.getUID());
|
||||
lua::call(L, 1, 0);
|
||||
}
|
||||
|
||||
void scripting::on_entity_grounded(const Entity& entity, float force) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_grounded",
|
||||
&EntityFuncsSet::on_grounded,
|
||||
[force](auto L) { return lua::pushnumber(L, force); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_fall(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_fall", &EntityFuncsSet::on_fall, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_save(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_save", &EntityFuncsSet::on_save, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_enter(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_enter",
|
||||
&EntityFuncsSet::on_sensor_enter,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_exit(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_exit",
|
||||
&EntityFuncsSet::on_sensor_exit,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_on(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_on",
|
||||
&EntityFuncsSet::on_aim_on,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_off(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_off",
|
||||
&EntityFuncsSet::on_aim_off,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_attacked(
|
||||
const Entity& entity, Player* player, entityid_t attacker
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_attacked",
|
||||
&EntityFuncsSet::on_attacked,
|
||||
[player, attacker](auto L) {
|
||||
lua::pushinteger(L, attacker);
|
||||
lua::pushinteger(L, player->getId());
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_used(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_used",
|
||||
&EntityFuncsSet::on_used,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entities_update(int tps, int parts, int part) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "update", true);
|
||||
lua::pushinteger(L, tps);
|
||||
lua::pushinteger(L, parts);
|
||||
lua::pushinteger(L, part);
|
||||
lua::call_nothrow(L, 3, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_entities_render(float delta) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "render", true);
|
||||
lua::pushnumber(L, delta);
|
||||
lua::call_nothrow(L, 1, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_ui_open(
|
||||
UiDocument* layout, std::vector<dv::value> args
|
||||
) {
|
||||
@@ -875,6 +651,8 @@ void scripting::load_content_script(
|
||||
register_event(env, "on_replaced", prefix + ".replaced");
|
||||
funcsset.oninteract =
|
||||
register_event(env, "on_interact", prefix + ".interact");
|
||||
funcsset.onblocktick =
|
||||
register_event(env, "on_block_tick", prefix + ".blocktick");
|
||||
funcsset.onblockstick =
|
||||
register_event(env, "on_blocks_tick", prefix + ".blockstick");
|
||||
}
|
||||
|
||||
@@ -65,12 +65,21 @@ namespace scripting {
|
||||
|
||||
void process_post_runnables();
|
||||
|
||||
std::unique_ptr<Process> start_coroutine(
|
||||
class IClientProjectScript {
|
||||
public:
|
||||
virtual ~IClientProjectScript() {}
|
||||
|
||||
virtual void onScreenChange(const std::string& name, bool show) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IClientProjectScript> load_client_project_script(
|
||||
const io::path& script
|
||||
);
|
||||
|
||||
std::unique_ptr<Process> start_coroutine(const io::path& script);
|
||||
|
||||
void on_world_load(LevelController* controller);
|
||||
void on_world_tick();
|
||||
void on_world_tick(int tps);
|
||||
void on_world_save();
|
||||
void on_world_quit();
|
||||
void cleanup();
|
||||
@@ -130,6 +139,7 @@ namespace scripting {
|
||||
void on_entity_fall(const Entity& entity);
|
||||
void on_entity_save(const Entity& entity);
|
||||
void on_entities_update(int tps, int parts, int part);
|
||||
void on_entities_physics_update(float delta);
|
||||
void on_entities_render(float delta);
|
||||
void on_sensor_enter(const Entity& entity, size_t index, entityid_t oid);
|
||||
void on_sensor_exit(const Entity& entity, size_t index, entityid_t oid);
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include "lua/lua_engine.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static inline const std::string STDCOMP = "stdcomp";
|
||||
|
||||
[[nodiscard]] static scriptenv create_component_environment(
|
||||
const scriptenv& parent, int entityIdx, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
int id = lua::create_environment(L, *parent);
|
||||
|
||||
lua::pushvalue(L, entityIdx);
|
||||
|
||||
lua::pushenv(L, id);
|
||||
|
||||
lua::pushvalue(L, -1);
|
||||
lua::setfield(L, "this");
|
||||
|
||||
lua::pushvalue(L, -2);
|
||||
lua::setfield(L, "entity");
|
||||
|
||||
lua::pop(L);
|
||||
if (lua::getfield(L, "components")) {
|
||||
lua::pushenv(L, id);
|
||||
lua::setfield(L, name);
|
||||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
|
||||
return std::shared_ptr<int>(new int(id), [=](int* id) { //-V508
|
||||
lua::remove_environment(L, *id);
|
||||
delete id;
|
||||
});
|
||||
}
|
||||
|
||||
dv::value scripting::get_component_value(
|
||||
const scriptenv& env, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::getfield(L, name)) {
|
||||
return lua::tovalue(L, -1);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void create_component(
|
||||
lua::State* L,
|
||||
int entityIdx,
|
||||
UserComponent& component,
|
||||
const dv::value& args,
|
||||
const dv::value& saved
|
||||
) {
|
||||
lua::pushvalue(L, entityIdx);
|
||||
auto compenv = create_component_environment(
|
||||
get_root_environment(), -1, component.name
|
||||
);
|
||||
lua::get_from(L, lua::CHUNKS_TABLE, component.name, true);
|
||||
lua::pushenv(L, *compenv);
|
||||
|
||||
if (args != nullptr) {
|
||||
std::string compfieldname = component.name;
|
||||
util::replaceAll(compfieldname, ":", "__");
|
||||
if (args.has(compfieldname)) {
|
||||
lua::pushvalue(L, args[compfieldname]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
} else if (component.params != nullptr) {
|
||||
lua::pushvalue(L, component.params);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
lua::setfield(L, "ARGS");
|
||||
|
||||
if (saved == nullptr) {
|
||||
lua::createtable(L, 0, 0);
|
||||
} else {
|
||||
if (saved.has(component.name)) {
|
||||
lua::pushvalue(L, saved[component.name]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::setfield(L, "SAVED_DATA");
|
||||
|
||||
lua::setfenv(L);
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
|
||||
lua::pushenv(L, *compenv);
|
||||
auto& funcsset = component.funcsset;
|
||||
funcsset.on_grounded = lua::hasfield(L, "on_grounded");
|
||||
funcsset.on_fall = lua::hasfield(L, "on_fall");
|
||||
funcsset.on_despawn = lua::hasfield(L, "on_despawn");
|
||||
funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter");
|
||||
funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit");
|
||||
funcsset.on_save = lua::hasfield(L, "on_save");
|
||||
funcsset.on_aim_on = lua::hasfield(L, "on_aim_on");
|
||||
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
|
||||
funcsset.on_attacked = lua::hasfield(L, "on_attacked");
|
||||
funcsset.on_used = lua::hasfield(L, "on_used");
|
||||
lua::pop(L, 2);
|
||||
|
||||
component.env = compenv;
|
||||
}
|
||||
|
||||
void scripting::on_entity_spawn(
|
||||
const EntityDef&,
|
||||
entityid_t eid,
|
||||
const std::vector<std::unique_ptr<UserComponent>>& components,
|
||||
const dv::value& args,
|
||||
const dv::value& saved
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::stackguard guard(L);
|
||||
lua::requireglobal(L, STDCOMP);
|
||||
if (lua::getfield(L, "new_Entity")) {
|
||||
lua::pushinteger(L, eid);
|
||||
lua::call(L, 1);
|
||||
}
|
||||
for (auto& component : components) {
|
||||
create_component(L, -1, *component, args, saved);
|
||||
}
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const scriptenv& env,
|
||||
const std::string& name,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::hasfield(L, "__disabled")) {
|
||||
lua::pop(L);
|
||||
return;
|
||||
}
|
||||
if (lua::getfield(L, name)) {
|
||||
if (args) {
|
||||
lua::call_nothrow(L, args(L), 0);
|
||||
} else {
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const Entity& entity,
|
||||
const std::string& name,
|
||||
bool EntityFuncsSet::*flag,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
const auto& script = entity.getScripting();
|
||||
for (auto& component : script.components) {
|
||||
if (component->funcsset.*flag) {
|
||||
process_entity_callback(component->env, name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_entity_despawn(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr
|
||||
);
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, "stdcomp", "remove_Entity", true);
|
||||
lua::pushinteger(L, entity.getUID());
|
||||
lua::call(L, 1, 0);
|
||||
}
|
||||
|
||||
void scripting::on_entity_grounded(const Entity& entity, float force) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_grounded",
|
||||
&EntityFuncsSet::on_grounded,
|
||||
[force](auto L) { return lua::pushnumber(L, force); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_fall(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_fall", &EntityFuncsSet::on_fall, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_save(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_save", &EntityFuncsSet::on_save, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_enter(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_enter",
|
||||
&EntityFuncsSet::on_sensor_enter,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_exit(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_exit",
|
||||
&EntityFuncsSet::on_sensor_exit,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_on(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_on",
|
||||
&EntityFuncsSet::on_aim_on,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_off(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_off",
|
||||
&EntityFuncsSet::on_aim_off,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_attacked(
|
||||
const Entity& entity, Player* player, entityid_t attacker
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_attacked",
|
||||
&EntityFuncsSet::on_attacked,
|
||||
[player, attacker](auto L) {
|
||||
lua::pushinteger(L, attacker);
|
||||
lua::pushinteger(L, player->getId());
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_used(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_used",
|
||||
&EntityFuncsSet::on_used,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entities_update(int tps, int parts, int part) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "update", true);
|
||||
lua::pushinteger(L, tps);
|
||||
lua::pushinteger(L, parts);
|
||||
lua::pushinteger(L, part);
|
||||
lua::call_nothrow(L, 3, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_entities_physics_update(float delta) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "physics_update", true);
|
||||
lua::pushnumber(L, delta);
|
||||
lua::call_nothrow(L, 1, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_entities_render(float delta) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "render", true);
|
||||
lua::pushnumber(L, delta);
|
||||
lua::call_nothrow(L, 1, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
+293
-26
@@ -57,6 +57,7 @@ struct Request {
|
||||
long maxSize;
|
||||
bool followLocation = false;
|
||||
std::string data;
|
||||
std::vector<std::string> headers;
|
||||
};
|
||||
|
||||
class CurlRequests : public Requests {
|
||||
@@ -86,10 +87,18 @@ public:
|
||||
const std::string& url,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject,
|
||||
std::vector<std::string> headers,
|
||||
long maxSize
|
||||
) override {
|
||||
Request request {
|
||||
RequestType::GET, url, onResponse, onReject, maxSize, false, ""};
|
||||
RequestType::GET,
|
||||
url,
|
||||
onResponse,
|
||||
onReject,
|
||||
maxSize,
|
||||
true,
|
||||
"",
|
||||
std::move(headers)};
|
||||
processRequest(std::move(request));
|
||||
}
|
||||
|
||||
@@ -98,10 +107,18 @@ public:
|
||||
const std::string& data,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject=nullptr,
|
||||
std::vector<std::string> headers = {},
|
||||
long maxSize=0
|
||||
) override {
|
||||
Request request {
|
||||
RequestType::POST, url, onResponse, onReject, maxSize, false, ""};
|
||||
RequestType::POST,
|
||||
url,
|
||||
onResponse,
|
||||
onReject,
|
||||
maxSize,
|
||||
false,
|
||||
"",
|
||||
std::move(headers)};
|
||||
request.data = data;
|
||||
processRequest(std::move(request));
|
||||
}
|
||||
@@ -121,6 +138,10 @@ public:
|
||||
curl_easy_setopt(curl, CURLOPT_POST, request.type == RequestType::POST);
|
||||
|
||||
curl_slist* hs = NULL;
|
||||
|
||||
for (const auto& header : request.headers) {
|
||||
hs = curl_slist_append(hs, header.c_str());
|
||||
}
|
||||
|
||||
switch (request.type) {
|
||||
case RequestType::GET:
|
||||
@@ -152,7 +173,7 @@ public:
|
||||
auto message = curl_multi_strerror(res);
|
||||
logger.error() << message << " (" << url << ")";
|
||||
if (onReject) {
|
||||
onReject(HTTP_BAD_GATEWAY);
|
||||
onReject(HTTP_BAD_GATEWAY, {});
|
||||
}
|
||||
url = "";
|
||||
}
|
||||
@@ -167,7 +188,7 @@ public:
|
||||
auto message = curl_multi_strerror(res);
|
||||
logger.error() << message << " (" << url << ")";
|
||||
if (onReject) {
|
||||
onReject(HTTP_BAD_GATEWAY);
|
||||
onReject(HTTP_BAD_GATEWAY, {});
|
||||
}
|
||||
curl_multi_remove_handle(multiHandle, curl);
|
||||
url = "";
|
||||
@@ -192,9 +213,14 @@ public:
|
||||
onResponse(std::move(buffer));
|
||||
}
|
||||
} else {
|
||||
logger.error() << "response code " << response << " (" << url << ")";
|
||||
logger.error()
|
||||
<< "response code " << response << " (" << url << ")"
|
||||
<< (buffer.empty()
|
||||
? ""
|
||||
: std::to_string(buffer.size()) + " byte(s)");
|
||||
totalDownload += buffer.size();
|
||||
if (onReject) {
|
||||
onReject(response);
|
||||
onReject(response, std::move(buffer));
|
||||
}
|
||||
}
|
||||
url = "";
|
||||
@@ -291,7 +317,7 @@ static std::string to_string(const sockaddr_in& addr, bool port=true) {
|
||||
return "";
|
||||
}
|
||||
|
||||
class SocketConnection : public Connection {
|
||||
class SocketTcpConnection : public TcpConnection {
|
||||
SOCKET descriptor;
|
||||
sockaddr_in addr;
|
||||
size_t totalUpload = 0;
|
||||
@@ -317,10 +343,10 @@ class SocketConnection : public Connection {
|
||||
state = ConnectionState::CONNECTED;
|
||||
}
|
||||
public:
|
||||
SocketConnection(SOCKET descriptor, sockaddr_in addr)
|
||||
SocketTcpConnection(SOCKET descriptor, sockaddr_in addr)
|
||||
: descriptor(descriptor), addr(std::move(addr)), buffer(16'384) {}
|
||||
|
||||
~SocketConnection() {
|
||||
~SocketTcpConnection() {
|
||||
if (state != ConnectionState::CLOSED) {
|
||||
shutdown(descriptor, 2);
|
||||
}
|
||||
@@ -442,7 +468,7 @@ public:
|
||||
return to_string(addr, false);
|
||||
}
|
||||
|
||||
static std::shared_ptr<SocketConnection> connect(
|
||||
static std::shared_ptr<SocketTcpConnection> connect(
|
||||
const std::string& address, int port, runnable callback
|
||||
) {
|
||||
addrinfo hints {};
|
||||
@@ -466,7 +492,7 @@ public:
|
||||
if (descriptor == -1) {
|
||||
throw std::runtime_error("Could not create socket");
|
||||
}
|
||||
auto socket = std::make_shared<SocketConnection>(descriptor, std::move(serverAddress));
|
||||
auto socket = std::make_shared<SocketTcpConnection>(descriptor, std::move(serverAddress));
|
||||
socket->connect(std::move(callback));
|
||||
return socket;
|
||||
}
|
||||
@@ -476,7 +502,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class SocketTcpSServer : public TcpServer {
|
||||
class SocketTcpServer : public TcpServer {
|
||||
u64id_t id;
|
||||
Network* network;
|
||||
SOCKET descriptor;
|
||||
@@ -486,10 +512,10 @@ class SocketTcpSServer : public TcpServer {
|
||||
std::unique_ptr<std::thread> thread = nullptr;
|
||||
int port;
|
||||
public:
|
||||
SocketTcpSServer(u64id_t id, Network* network, SOCKET descriptor, int port)
|
||||
SocketTcpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
|
||||
: id(id), network(network), descriptor(descriptor), port(port) {}
|
||||
|
||||
~SocketTcpSServer() {
|
||||
~SocketTcpServer() {
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
@@ -510,7 +536,7 @@ public:
|
||||
break;
|
||||
}
|
||||
logger.info() << "client connected: " << to_string(address);
|
||||
auto socket = std::make_shared<SocketConnection>(
|
||||
auto socket = std::make_shared<SocketTcpConnection>(
|
||||
clientDescriptor, address
|
||||
);
|
||||
socket->startClient();
|
||||
@@ -558,7 +584,7 @@ public:
|
||||
return port;
|
||||
}
|
||||
|
||||
static std::shared_ptr<SocketTcpSServer> openServer(
|
||||
static std::shared_ptr<SocketTcpServer> openServer(
|
||||
u64id_t id, Network* network, int port, ConnectCallback handler
|
||||
) {
|
||||
SOCKET descriptor = socket(
|
||||
@@ -569,10 +595,12 @@ public:
|
||||
}
|
||||
int opt = 1;
|
||||
int flags = SO_REUSEADDR;
|
||||
# ifndef _WIN32
|
||||
# if !defined(_WIN32) && !defined(__APPLE__)
|
||||
flags |= SO_REUSEPORT;
|
||||
# endif
|
||||
if (setsockopt(descriptor, SOL_SOCKET, flags, (const char*)&opt, sizeof(opt))) {
|
||||
logger.error() << "setsockopt(SO_REUSEADDR) failed with errno: "
|
||||
<< errno << "(" << std::strerror(errno) << ")";
|
||||
closesocket(descriptor);
|
||||
throw std::runtime_error("setsockopt");
|
||||
}
|
||||
@@ -586,7 +614,221 @@ public:
|
||||
}
|
||||
logger.info() << "opened server at port " << port;
|
||||
auto server =
|
||||
std::make_shared<SocketTcpSServer>(id, network, descriptor, port);
|
||||
std::make_shared<SocketTcpServer>(id, network, descriptor, port);
|
||||
server->startListen(std::move(handler));
|
||||
return server;
|
||||
}
|
||||
};
|
||||
|
||||
class SocketUdpConnection : public UdpConnection {
|
||||
u64id_t id;
|
||||
SOCKET descriptor;
|
||||
sockaddr_in addr{};
|
||||
bool open = true;
|
||||
std::unique_ptr<std::thread> thread;
|
||||
ClientDatagramCallback callback;
|
||||
|
||||
size_t totalUpload = 0;
|
||||
size_t totalDownload = 0;
|
||||
ConnectionState state = ConnectionState::INITIAL;
|
||||
|
||||
public:
|
||||
SocketUdpConnection(u64id_t id, SOCKET descriptor, sockaddr_in addr)
|
||||
: id(id), descriptor(descriptor), addr(std::move(addr)) {}
|
||||
|
||||
~SocketUdpConnection() override {
|
||||
SocketUdpConnection::close();
|
||||
}
|
||||
|
||||
static std::shared_ptr<SocketUdpConnection> connect(
|
||||
u64id_t id,
|
||||
const std::string& address,
|
||||
int port,
|
||||
ClientDatagramCallback handler,
|
||||
runnable callback
|
||||
) {
|
||||
SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (descriptor == -1) {
|
||||
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);
|
||||
|
||||
if (::connect(descriptor, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
|
||||
auto err = handle_socket_error("udp connect failed");
|
||||
closesocket(descriptor);
|
||||
throw err;
|
||||
}
|
||||
|
||||
auto socket = std::make_shared<SocketUdpConnection>(id, descriptor, serverAddr);
|
||||
socket->connect(std::move(handler));
|
||||
|
||||
callback();
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
void connect(ClientDatagramCallback handler) override {
|
||||
callback = std::move(handler);
|
||||
state = ConnectionState::CONNECTED;
|
||||
|
||||
thread = std::make_unique<std::thread>([this]() {
|
||||
util::Buffer<char> buffer(16'384);
|
||||
while (open) {
|
||||
int size = recv(descriptor, buffer.data(), buffer.size(), 0);
|
||||
if (size <= 0) {
|
||||
if (!open) break;
|
||||
closesocket(descriptor);
|
||||
state = ConnectionState::CLOSED;
|
||||
break;
|
||||
}
|
||||
totalDownload += size;
|
||||
if (callback) {
|
||||
callback(id, buffer.data(), size);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int send(const char* buffer, size_t length) override {
|
||||
int len = sendto(descriptor, buffer, length, 0,
|
||||
(sockaddr*)&addr, sizeof(addr));
|
||||
if (len < 0) {
|
||||
closesocket(descriptor);
|
||||
state = ConnectionState::CLOSED;
|
||||
} else totalUpload += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void close(bool discardAll=false) override {
|
||||
if (!open) return;
|
||||
open = false;
|
||||
|
||||
if (state != ConnectionState::CLOSED) {
|
||||
shutdown(descriptor, 2);
|
||||
closesocket(descriptor);
|
||||
}
|
||||
|
||||
if (thread) {
|
||||
thread->join();
|
||||
thread.reset();
|
||||
}
|
||||
state = ConnectionState::CLOSED;
|
||||
}
|
||||
|
||||
size_t pullUpload() override {
|
||||
size_t s = totalUpload;
|
||||
totalUpload = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t pullDownload() override {
|
||||
size_t s = totalDownload;
|
||||
totalDownload = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
[[nodiscard]] int getPort() const override {
|
||||
return ntohs(addr.sin_port);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string getAddress() const override {
|
||||
return to_string(addr, false);
|
||||
}
|
||||
|
||||
[[nodiscard]] ConnectionState getState() const override {
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
class SocketUdpServer : public UdpServer {
|
||||
u64id_t id;
|
||||
SOCKET descriptor;
|
||||
bool open = true;
|
||||
std::unique_ptr<std::thread> thread = nullptr;
|
||||
int port;
|
||||
ServerDatagramCallback callback;
|
||||
|
||||
public:
|
||||
SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
|
||||
: id(id), descriptor(descriptor), port(port) {}
|
||||
|
||||
~SocketUdpServer() override {
|
||||
SocketUdpServer::close();
|
||||
}
|
||||
|
||||
void startListen(ServerDatagramCallback handler) override {
|
||||
callback = std::move(handler);
|
||||
|
||||
thread = std::make_unique<std::thread>([this]() {
|
||||
util::Buffer<char> buffer(16384);
|
||||
sockaddr_in clientAddr{};
|
||||
socklen_t addrlen = sizeof(clientAddr);
|
||||
|
||||
while (open) {
|
||||
int size = recvfrom(descriptor, buffer.data(), buffer.size(), 0,
|
||||
reinterpret_cast<sockaddr*>(&clientAddr), &addrlen);
|
||||
if (size <= 0) {
|
||||
if (!open) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string addrStr = to_string(clientAddr, false);
|
||||
int port = ntohs(clientAddr.sin_port);
|
||||
|
||||
callback(id, addrStr, port, buffer.data(), size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void close() override {
|
||||
if (!open) return;
|
||||
open = false;
|
||||
shutdown(descriptor, 2);
|
||||
closesocket(descriptor);
|
||||
if (thread) {
|
||||
thread->join();
|
||||
thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool isOpen() override { return open; }
|
||||
int getPort() const override { return port; }
|
||||
|
||||
static std::shared_ptr<SocketUdpServer> openServer(
|
||||
u64id_t id, Network* network, int port, const ServerDatagramCallback& handler
|
||||
) {
|
||||
SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (descriptor == -1) throw std::runtime_error("could not create udp socket");
|
||||
|
||||
sockaddr_in address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(port);
|
||||
|
||||
if (bind(descriptor, (sockaddr*)&address, sizeof(address)) < 0) {
|
||||
closesocket(descriptor);
|
||||
throw std::runtime_error("could not bind udp port " + std::to_string(port));
|
||||
}
|
||||
|
||||
auto server = std::make_shared<SocketUdpServer>(id, network, descriptor, port);
|
||||
server->startListen(std::move(handler));
|
||||
return server;
|
||||
}
|
||||
@@ -602,9 +844,10 @@ void Network::get(
|
||||
const std::string& url,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject,
|
||||
std::vector<std::string> headers,
|
||||
long maxSize
|
||||
) {
|
||||
requests->get(url, onResponse, onReject, maxSize);
|
||||
requests->get(url, onResponse, onReject, std::move(headers), maxSize);
|
||||
}
|
||||
|
||||
void Network::post(
|
||||
@@ -612,9 +855,12 @@ void Network::post(
|
||||
const std::string& fieldsData,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject,
|
||||
std::vector<std::string> headers,
|
||||
long maxSize
|
||||
) {
|
||||
requests->post(url, fieldsData, onResponse, onReject, maxSize);
|
||||
requests->post(
|
||||
url, fieldsData, onResponse, onReject, std::move(headers), maxSize
|
||||
);
|
||||
}
|
||||
|
||||
Connection* Network::getConnection(u64id_t id) {
|
||||
@@ -627,7 +873,7 @@ Connection* Network::getConnection(u64id_t id) {
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
TcpServer* Network::getServer(u64id_t id) const {
|
||||
Server* Network::getServer(u64id_t id) const {
|
||||
const auto& found = servers.find(id);
|
||||
if (found == servers.end()) {
|
||||
return nullptr;
|
||||
@@ -635,20 +881,38 @@ TcpServer* Network::getServer(u64id_t id) const {
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
u64id_t Network::connect(const std::string& address, int port, consumer<u64id_t> callback) {
|
||||
u64id_t Network::connectTcp(const std::string& address, int port, consumer<u64id_t> callback) {
|
||||
std::lock_guard lock(connectionsMutex);
|
||||
|
||||
u64id_t id = nextConnection++;
|
||||
auto socket = SocketConnection::connect(address, port, [id, callback]() {
|
||||
auto socket = SocketTcpConnection::connect(address, port, [id, callback]() {
|
||||
callback(id);
|
||||
});
|
||||
connections[id] = std::move(socket);
|
||||
return id;
|
||||
}
|
||||
|
||||
u64id_t Network::openServer(int port, ConnectCallback handler) {
|
||||
u64id_t Network::openTcpServer(int port, ConnectCallback handler) {
|
||||
u64id_t id = nextServer++;
|
||||
auto server = SocketTcpSServer::openServer(id, this, port, handler);
|
||||
auto server = SocketTcpServer::openServer(id, this, port, handler);
|
||||
servers[id] = std::move(server);
|
||||
return id;
|
||||
}
|
||||
|
||||
u64id_t Network::connectUdp(const std::string& address, int port, const consumer<u64id_t>& callback, ClientDatagramCallback handler) {
|
||||
std::lock_guard lock(connectionsMutex);
|
||||
|
||||
u64id_t id = nextConnection++;
|
||||
auto socket = SocketUdpConnection::connect(id, address, port, std::move(handler), [id, callback]() {
|
||||
callback(id);
|
||||
});
|
||||
connections[id] = std::move(socket);
|
||||
return id;
|
||||
}
|
||||
|
||||
u64id_t Network::openUdpServer(int port, const ServerDatagramCallback& handler) {
|
||||
u64id_t id = nextServer++;
|
||||
auto server = SocketUdpServer::openServer(id, this, port, handler);
|
||||
servers[id] = std::move(server);
|
||||
return id;
|
||||
}
|
||||
@@ -679,7 +943,10 @@ void Network::update() {
|
||||
auto socket = socketiter->second.get();
|
||||
totalDownload += socket->pullDownload();
|
||||
totalUpload += socket->pullUpload();
|
||||
if (socket->available() == 0 &&
|
||||
if (
|
||||
( socket->getTransportType() == TransportType::UDP ||
|
||||
dynamic_cast<TcpConnection*>(socket)->available() == 0
|
||||
) &&
|
||||
socket->getState() == ConnectionState::CLOSED) {
|
||||
socketiter = connections.erase(socketiter);
|
||||
continue;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user