commit
d6e6e32ef2
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,6 +6,8 @@ Debug/voxel_engine
|
||||
/build
|
||||
/cmake-build-**
|
||||
/screenshots
|
||||
/export
|
||||
/config
|
||||
/out
|
||||
|
||||
/misc
|
||||
|
||||
1
res/config/defaults.toml
Normal file
1
res/config/defaults.toml
Normal file
@ -0,0 +1 @@
|
||||
generator = "core:default"
|
||||
3
res/content/base/blocks/coal_ore.json
Normal file
3
res/content/base/blocks/coal_ore.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"texture": "coal_ore"
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"texture": "dirt",
|
||||
"material": "base:ground"
|
||||
}
|
||||
"material": "base:ground",
|
||||
"surface-replacement": "base:grass_block"
|
||||
}
|
||||
|
||||
1
res/content/base/config/defaults.toml
Normal file
1
res/content/base/config/defaults.toml
Normal file
@ -0,0 +1 @@
|
||||
generator = "base:demo"
|
||||
@ -1,8 +1,6 @@
|
||||
{
|
||||
"entities": [
|
||||
"drop",
|
||||
"player",
|
||||
"falling_block"
|
||||
"items": [
|
||||
"bazalt_breaker"
|
||||
],
|
||||
"blocks": [
|
||||
"dirt",
|
||||
@ -28,9 +26,12 @@
|
||||
"pipe",
|
||||
"lightbulb",
|
||||
"torch",
|
||||
"wooden_door"
|
||||
"wooden_door",
|
||||
"coal_ore"
|
||||
],
|
||||
"items": [
|
||||
"bazalt_breaker"
|
||||
"entities": [
|
||||
"drop",
|
||||
"player",
|
||||
"falling_block"
|
||||
]
|
||||
}
|
||||
67
res/content/base/generators/demo.files/biomes.toml
Normal file
67
res/content/base/generators/demo.files/biomes.toml
Normal file
@ -0,0 +1,67 @@
|
||||
[forest]
|
||||
parameters = [
|
||||
{weight=1, value=1},
|
||||
{weight=0.5, value=0.2}
|
||||
]
|
||||
layers = [
|
||||
{below-sea-level=false, height=1, block="base:grass_block"},
|
||||
{below-sea-level=false, height=7, block="base:dirt"},
|
||||
{height=-1, block="base:stone"},
|
||||
{height=1, block="base:bazalt"}
|
||||
]
|
||||
sea-layers = [
|
||||
{height=-1, block="base:water"}
|
||||
]
|
||||
plant-chance = 0.4
|
||||
plants = [
|
||||
{weight=1, block="base:grass"},
|
||||
{weight=0.03, block="base:flower"}
|
||||
]
|
||||
structure-chance = 0.032
|
||||
structures = [
|
||||
{name="tree0", weight=1},
|
||||
{name="tree1", weight=1},
|
||||
{name="tree2", weight=1},
|
||||
{name="tower", weight=0.002}
|
||||
]
|
||||
|
||||
|
||||
[desert]
|
||||
parameters = [
|
||||
{weight=0.3, value=0},
|
||||
{weight=0.1, value=0}
|
||||
]
|
||||
layers = [
|
||||
{height=6, block="base:sand"},
|
||||
{height=-1, block="base:stone"},
|
||||
{height=1, block="base:bazalt"}
|
||||
]
|
||||
sea-layers = [
|
||||
{height=-1, block="base:water"}
|
||||
]
|
||||
|
||||
|
||||
[plains]
|
||||
parameters = [
|
||||
{weight=0.6, value=0.5},
|
||||
{weight=0.6, value=0.5}
|
||||
]
|
||||
layers = [
|
||||
{below-sea-level=false, height=1, block="base:grass_block"},
|
||||
{below-sea-level=false, height=5, block="base:dirt"},
|
||||
{height=-1, block="base:stone"},
|
||||
{height=1, block="base:bazalt"}
|
||||
]
|
||||
sea-layers = [
|
||||
{height=-1, block="base:water"}
|
||||
]
|
||||
plant-chance = 0.3
|
||||
plants = [
|
||||
{weight=1, block="base:grass"},
|
||||
{weight=0.03, block="base:flower"}
|
||||
]
|
||||
structure-chance=0.0001
|
||||
structures = [
|
||||
{name="tree0", weight=1},
|
||||
{name="tree1", weight=1}
|
||||
]
|
||||
BIN
res/content/base/generators/demo.files/fragments/coal_ore0.vox
Normal file
BIN
res/content/base/generators/demo.files/fragments/coal_ore0.vox
Normal file
Binary file not shown.
BIN
res/content/base/generators/demo.files/fragments/tower.vox
Normal file
BIN
res/content/base/generators/demo.files/fragments/tower.vox
Normal file
Binary file not shown.
BIN
res/content/base/generators/demo.files/fragments/tree0.vox
Normal file
BIN
res/content/base/generators/demo.files/fragments/tree0.vox
Normal file
Binary file not shown.
BIN
res/content/base/generators/demo.files/fragments/tree1.vox
Normal file
BIN
res/content/base/generators/demo.files/fragments/tree1.vox
Normal file
Binary file not shown.
BIN
res/content/base/generators/demo.files/fragments/tree2.vox
Normal file
BIN
res/content/base/generators/demo.files/fragments/tree2.vox
Normal file
Binary file not shown.
3
res/content/base/generators/demo.files/ores.json
Normal file
3
res/content/base/generators/demo.files/ores.json
Normal file
@ -0,0 +1,3 @@
|
||||
[
|
||||
{"struct": "coal_ore0", "rarity": 4400}
|
||||
]
|
||||
81
res/content/base/generators/demo.files/script.lua
Normal file
81
res/content/base/generators/demo.files/script.lua
Normal file
@ -0,0 +1,81 @@
|
||||
local _, dir = parse_path(__DIR__)
|
||||
local ores = require "base:generation/ores"
|
||||
ores.load(dir)
|
||||
|
||||
function place_structures(x, z, w, d, seed, hmap, chunk_height)
|
||||
local placements = {}
|
||||
ores.place(placements, x, z, w, d, seed, hmap, chunk_height)
|
||||
return placements
|
||||
end
|
||||
|
||||
function place_structures_wide(x, z, w, d, seed, chunk_height)
|
||||
local placements = {}
|
||||
if math.random() < 0.05 then -- generate caves
|
||||
local sx = x + math.random() * 10 - 5
|
||||
local sy = math.random() * (chunk_height / 4) + 10
|
||||
local sz = z + math.random() * 10 - 5
|
||||
|
||||
local dir = math.random() * math.pi * 2
|
||||
local dir_inertia = (math.random() - 0.5) * 2
|
||||
local elevation = -3
|
||||
local width = math.random() * 3 + 2
|
||||
|
||||
for i=1,18 do
|
||||
local dx = math.sin(dir) * 10
|
||||
local dz = -math.cos(dir) * 10
|
||||
|
||||
local ex = sx + dx
|
||||
local ey = sy + elevation
|
||||
local ez = sz + dz
|
||||
|
||||
table.insert(placements,
|
||||
{":line", 0, {sx, sy, sz}, {ex, ey, ez}, width})
|
||||
|
||||
sx = ex
|
||||
sy = ey
|
||||
sz = ez
|
||||
|
||||
dir_inertia = dir_inertia * 0.8 + (math.random() - 0.5) * math.pow(math.random(), 2) * 8
|
||||
elevation = elevation * 0.9 + (math.random() - 0.4) * (1.0-math.pow(math.random(), 4)) * 8
|
||||
dir = dir + dir_inertia
|
||||
end
|
||||
end
|
||||
return placements
|
||||
end
|
||||
|
||||
function generate_heightmap(x, y, w, h, seed, s)
|
||||
local umap = Heightmap(w, h)
|
||||
local vmap = Heightmap(w, h)
|
||||
umap.noiseSeed = seed
|
||||
vmap.noiseSeed = seed
|
||||
vmap:noise({x+521, y+70}, 0.1*s, 3, 25.8)
|
||||
vmap:noise({x+95, y+246}, 0.15*s, 3, 25.8)
|
||||
|
||||
local map = Heightmap(w, h)
|
||||
map.noiseSeed = seed
|
||||
map:noise({x, y}, 0.8*s, 4, 0.02)
|
||||
map:cellnoise({x, y}, 0.1*s, 3, 0.3, umap, vmap)
|
||||
map:add(0.7)
|
||||
|
||||
local rivermap = Heightmap(w, h)
|
||||
rivermap.noiseSeed = seed
|
||||
rivermap:noise({x+21, y+12}, 0.1*s, 4)
|
||||
rivermap:abs()
|
||||
rivermap:mul(2.0)
|
||||
rivermap:pow(0.15)
|
||||
rivermap:max(0.5)
|
||||
map:mul(rivermap)
|
||||
return map
|
||||
end
|
||||
|
||||
function generate_biome_parameters(x, y, w, h, seed, s)
|
||||
local tempmap = Heightmap(w, h)
|
||||
tempmap.noiseSeed = seed + 5324
|
||||
tempmap:noise({x, y}, 0.04*s, 6)
|
||||
local hummap = Heightmap(w, h)
|
||||
hummap.noiseSeed = seed + 953
|
||||
hummap:noise({x, y}, 0.04*s, 6)
|
||||
tempmap:pow(3)
|
||||
hummap:pow(3)
|
||||
return tempmap, hummap
|
||||
end
|
||||
5
res/content/base/generators/demo.files/structures.toml
Normal file
5
res/content/base/generators/demo.files/structures.toml
Normal file
@ -0,0 +1,5 @@
|
||||
tree0 = {}
|
||||
tree1 = {}
|
||||
tree2 = {}
|
||||
tower = {}
|
||||
coal_ore0 = {}
|
||||
4
res/content/base/generators/demo.toml
Normal file
4
res/content/base/generators/demo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
# 1 - temperature
|
||||
# 2 - humidity
|
||||
biome-parameters = 2
|
||||
sea-level = 64
|
||||
29
res/content/base/modules/generation/ores.lua
Normal file
29
res/content/base/modules/generation/ores.lua
Normal file
@ -0,0 +1,29 @@
|
||||
local ores = {}
|
||||
|
||||
function ores.load(directory)
|
||||
ores.ores = file.read_combined_list(directory.."/ores.json")
|
||||
end
|
||||
|
||||
function ores.place(placements, x, z, w, d, seed, hmap, chunk_height)
|
||||
local BLOCKS_PER_CHUNK = w * d * chunk_height
|
||||
for _, ore in ipairs(ores.ores) do
|
||||
local count = BLOCKS_PER_CHUNK / ore.rarity
|
||||
|
||||
-- average count is less than 1
|
||||
local addchance = math.fmod(count, 1.0)
|
||||
if math.random() < addchance then
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
for i=1,count do
|
||||
local sx = math.random() * w
|
||||
local sz = math.random() * d
|
||||
local sy = math.random() * (chunk_height * 0.5)
|
||||
if sy < hmap:at(sx, sz) * chunk_height - 6 then
|
||||
table.insert(placements, {ore.struct, {sx, sy, sz}, math.random()*4, -1})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ores
|
||||
@ -7,13 +7,6 @@
|
||||
"models": [
|
||||
"drop-item"
|
||||
],
|
||||
"shaders": [
|
||||
"ui3d",
|
||||
"entity",
|
||||
"screen",
|
||||
"background",
|
||||
"skybox_gen"
|
||||
],
|
||||
"textures": [
|
||||
"misc/moon",
|
||||
"misc/sun",
|
||||
|
||||
8
res/content/base/resource-aliases.json
Normal file
8
res/content/base/resource-aliases.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"camera": {
|
||||
"base:first-person": "core:first-person",
|
||||
"base:third-person-front": "core:third-person-front",
|
||||
"base:third-person-back": "core:third-person-back",
|
||||
"base:cinematic": "core:cinematic"
|
||||
}
|
||||
}
|
||||
BIN
res/content/base/textures/blocks/coal_ore.png
Normal file
BIN
res/content/base/textures/blocks/coal_ore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
8
res/generators/default.files/biomes.toml
Normal file
8
res/generators/default.files/biomes.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[flat]
|
||||
parameters = []
|
||||
layers = [
|
||||
{height=-1, block="core:obstacle"}
|
||||
]
|
||||
sea-layers = [
|
||||
{height=-1, block="core:obstacle"}
|
||||
]
|
||||
1
res/generators/default.toml
Normal file
1
res/generators/default.toml
Normal file
@ -0,0 +1 @@
|
||||
biome-parameters = 0
|
||||
@ -1,15 +1,20 @@
|
||||
settings = session.get_entry('new_world')
|
||||
|
||||
function on_open()
|
||||
local names = core.get_generators()
|
||||
table.sort(names)
|
||||
local names = generation.get_generators()
|
||||
local keys = {}
|
||||
for key in pairs(names) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
table.sort(keys)
|
||||
|
||||
local panel = document.root
|
||||
for _,k in ipairs(names) do
|
||||
for _, key in ipairs(keys) do
|
||||
local caption = names[key]
|
||||
panel:add(gui.template("generator", {
|
||||
callback=string.format("settings.generator=%q menu:back()", k),
|
||||
id=k,
|
||||
name=settings.generator_name(k)
|
||||
callback=string.format("settings.generator=%q menu:back()", key),
|
||||
id=key,
|
||||
name=settings.generator_name(caption)
|
||||
}))
|
||||
end
|
||||
panel:add("<button onclick='menu:back()'>@Back</button>")
|
||||
|
||||
@ -10,12 +10,7 @@ function save_state()
|
||||
end
|
||||
|
||||
function settings.generator_name(id)
|
||||
local prefix, name = parse_path(id)
|
||||
if prefix == "core" then
|
||||
return gui.str(name, "world.generators")
|
||||
else
|
||||
return id
|
||||
end
|
||||
return gui.str(id, "world.generators"):gsub("^%l", string.upper)
|
||||
end
|
||||
|
||||
function create_world()
|
||||
@ -34,12 +29,12 @@ function on_open()
|
||||
"%s [%s]", gui.str("Content", "menu"), #pack.get_installed()
|
||||
)
|
||||
if settings.generator == nil then
|
||||
settings.generator = core.get_default_generator()
|
||||
settings.generator = generation.get_default_generator()
|
||||
end
|
||||
document.generator_btn.text = string.format(
|
||||
"%s: %s",
|
||||
gui.str("World generator", "world"),
|
||||
settings.generator_name(settings.generator)
|
||||
settings.generator_name(generation.get_generators()[settings.generator])
|
||||
)
|
||||
document.name_box.text = settings.name or ''
|
||||
document.seed_box.text = settings.seed or ''
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
{
|
||||
"shaders": [
|
||||
"ui",
|
||||
"ui3d",
|
||||
"main",
|
||||
"lines"
|
||||
"lines",
|
||||
"entity",
|
||||
"screen",
|
||||
"background",
|
||||
"skybox_gen"
|
||||
],
|
||||
"textures": [
|
||||
"gui/menubg",
|
||||
|
||||
@ -149,3 +149,41 @@ console.add_command(
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
console.add_command(
|
||||
"fragment.save x:int y:int z:int w:int h:int d:int name:str='untitled' crop:bool=false",
|
||||
"Save fragment",
|
||||
function(args, kwargs)
|
||||
local x = args[1]
|
||||
local y = args[2]
|
||||
local z = args[3]
|
||||
|
||||
local w = args[4]
|
||||
local h = args[5]
|
||||
local d = args[6]
|
||||
|
||||
local name = args[7]
|
||||
local crop = args[8]
|
||||
|
||||
local fragment = generation.create_fragment(
|
||||
{x, y, z}, {x + w, y + h, z + d}, crop, false
|
||||
)
|
||||
local filename = 'export:'..name..'.vox'
|
||||
generation.save_fragment(fragment, filename, crop)
|
||||
console.log("fragment with size "..vec3.tostring(fragment.size)..
|
||||
" has been saved as "..file.resolve(filename))
|
||||
end
|
||||
)
|
||||
|
||||
console.add_command(
|
||||
"fragment.crop filename:str",
|
||||
"Crop fragment",
|
||||
function(args, kwargs)
|
||||
local filename = args[1]
|
||||
local fragment = generation.load_fragment(filename)
|
||||
fragment:crop()
|
||||
generation.save_fragment(fragment, filename, crop)
|
||||
console.log("fragment with size "..vec3.tostring(fragment.size)..
|
||||
" has been saved as "..file.resolve(filename))
|
||||
end
|
||||
)
|
||||
|
||||
@ -1,89 +1,6 @@
|
||||
-- kit of standard functions
|
||||
|
||||
-- Check if given table is an array
|
||||
function is_array(x)
|
||||
if #t > 0 then
|
||||
return true
|
||||
end
|
||||
for k, v in pairs(x) do
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Get entry-point and filename from `entry-point:filename` path
|
||||
function parse_path(path)
|
||||
local index = string.find(path, ':')
|
||||
if index == nil then
|
||||
error("invalid path syntax (':' missing)")
|
||||
end
|
||||
return string.sub(path, 1, index-1), string.sub(path, index+1, -1)
|
||||
end
|
||||
|
||||
package = {
|
||||
loaded={}
|
||||
}
|
||||
local __cached_scripts = {}
|
||||
local __warnings_hidden = {}
|
||||
|
||||
function on_deprecated_call(name, alternatives)
|
||||
if __warnings_hidden[name] then
|
||||
return
|
||||
end
|
||||
__warnings_hidden[name] = true
|
||||
if alternatives then
|
||||
debug.warning("deprecated function called ("..name.."), use "..
|
||||
alternatives.." instead\n"..debug.traceback())
|
||||
else
|
||||
debug.warning("deprecated function called ("..name..")\n"..debug.traceback())
|
||||
end
|
||||
end
|
||||
|
||||
-- Load script with caching
|
||||
--
|
||||
-- path - script path `contentpack:filename`.
|
||||
-- Example `base:scripts/tests.lua`
|
||||
--
|
||||
-- nocache - ignore cached script, load anyway
|
||||
local function __load_script(path, nocache)
|
||||
local packname, filename = parse_path(path)
|
||||
|
||||
-- __cached_scripts used in condition because cached result may be nil
|
||||
if not nocache and __cached_scripts[path] ~= nil then
|
||||
return package.loaded[path]
|
||||
end
|
||||
if not file.isfile(path) then
|
||||
error("script '"..filename.."' not found in '"..packname.."'")
|
||||
end
|
||||
|
||||
local script, err = load(file.read(path), path)
|
||||
if script == nil then
|
||||
error(err)
|
||||
end
|
||||
local result = script()
|
||||
if not nocache then
|
||||
__cached_scripts[path] = script
|
||||
package.loaded[path] = result
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function __scripts_cleanup()
|
||||
print("cleaning scripts cache")
|
||||
for k, v in pairs(__cached_scripts) do
|
||||
local packname, _ = parse_path(k)
|
||||
if packname ~= "core" then
|
||||
print("unloaded "..k)
|
||||
__cached_scripts[k] = nil
|
||||
package.loaded[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function require(path)
|
||||
local prefix, file = parse_path(path)
|
||||
return __load_script(prefix..":modules/"..file..".lua")
|
||||
end
|
||||
------------------------------------------------
|
||||
------ Extended kit of standard functions ------
|
||||
------------------------------------------------
|
||||
|
||||
function sleep(timesec)
|
||||
local start = time.uptime()
|
||||
@ -92,20 +9,6 @@ function sleep(timesec)
|
||||
end
|
||||
end
|
||||
|
||||
function pack.is_installed(packid)
|
||||
return file.isfile(packid..":package.json")
|
||||
end
|
||||
|
||||
function pack.data_file(packid, name)
|
||||
file.mkdirs("world:data/"..packid)
|
||||
return "world:data/"..packid.."/"..name
|
||||
end
|
||||
|
||||
function pack.shared_file(packid, name)
|
||||
file.mkdirs("config:"..packid)
|
||||
return "config:"..packid.."/"..name
|
||||
end
|
||||
|
||||
-- events
|
||||
events = {
|
||||
handlers = {}
|
||||
@ -233,59 +136,6 @@ function session.reset_entry(name)
|
||||
session.entries[name] = nil
|
||||
end
|
||||
|
||||
function timeit(iters, func, ...)
|
||||
local tm = time.uptime()
|
||||
for i=1,iters do
|
||||
func(...)
|
||||
end
|
||||
print("[time mcs]", (time.uptime()-tm) * 1000000)
|
||||
end
|
||||
|
||||
function table.has(t, x)
|
||||
for i,v in ipairs(t) do
|
||||
if v == x then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function table.index(t, x)
|
||||
for i,v in ipairs(t) do
|
||||
if v == x then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
function table.remove_value(t, x)
|
||||
local index = table.index(t, x)
|
||||
if index ~= -1 then
|
||||
table.remove(t, index)
|
||||
end
|
||||
end
|
||||
|
||||
function table.tostring(t)
|
||||
local s = '['
|
||||
for i,v in ipairs(t) do
|
||||
s = s..tostring(v)
|
||||
if i < #t then
|
||||
s = s..', '
|
||||
end
|
||||
end
|
||||
return s..']'
|
||||
end
|
||||
|
||||
function file.readlines(path)
|
||||
local str = file.read(path)
|
||||
local lines = {}
|
||||
for s in str:gmatch("[^\r\n]+") do
|
||||
table.insert(lines, s)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
stdcomp = require "core:internal/stdcomp"
|
||||
entities.get = stdcomp.get_Entity
|
||||
entities.get_all = function(uids)
|
||||
@ -302,145 +152,6 @@ end
|
||||
|
||||
math.randomseed(time.uptime() * 1536227939)
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
function math.clamp(_in, low, high)
|
||||
return math.min(math.max(_in, low), high)
|
||||
end
|
||||
|
||||
function math.rand(low, high)
|
||||
return low + (high - low) * math.random()
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
function table.copy(t)
|
||||
local copied = {}
|
||||
|
||||
for k, v in pairs(t) do
|
||||
copied[k] = v
|
||||
end
|
||||
|
||||
return copied
|
||||
end
|
||||
|
||||
function table.count_pairs(t)
|
||||
local count = 0
|
||||
|
||||
for k, v in pairs(t) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
function table.random(t)
|
||||
return t[math.random(1, #t)]
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
local pattern_escape_replacements = {
|
||||
["("] = "%(",
|
||||
[")"] = "%)",
|
||||
["."] = "%.",
|
||||
["%"] = "%%",
|
||||
["+"] = "%+",
|
||||
["-"] = "%-",
|
||||
["*"] = "%*",
|
||||
["?"] = "%?",
|
||||
["["] = "%[",
|
||||
["]"] = "%]",
|
||||
["^"] = "%^",
|
||||
["$"] = "%$",
|
||||
["\0"] = "%z"
|
||||
}
|
||||
|
||||
function string.pattern_safe(str)
|
||||
return string.gsub(str, ".", pattern_escape_replacements)
|
||||
end
|
||||
|
||||
--local totable = string.ToTable
|
||||
local string_sub = string.sub
|
||||
local string_find = string.find
|
||||
local string_len = string.len
|
||||
function string.explode(separator, str, withpattern)
|
||||
--if (separator == "") then return totable(str) end
|
||||
if (withpattern == nil) then withpattern = false end
|
||||
|
||||
local ret = {}
|
||||
local current_pos = 1
|
||||
|
||||
for i = 1, string_len(str) do
|
||||
local start_pos, end_pos = string_find(str, separator, current_pos, not withpattern)
|
||||
if (not start_pos) then break end
|
||||
ret[i] = string_sub(str, current_pos, start_pos - 1)
|
||||
current_pos = end_pos + 1
|
||||
end
|
||||
|
||||
ret[#ret + 1] = string_sub(str, current_pos)
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function string.split(str, delimiter)
|
||||
return string.explode(delimiter, str)
|
||||
end
|
||||
|
||||
function string.formatted_time(seconds, format)
|
||||
if (not seconds) then seconds = 0 end
|
||||
local hours = math.floor(seconds / 3600)
|
||||
local minutes = math.floor((seconds / 60) % 60)
|
||||
local millisecs = (seconds - math.floor(seconds)) * 1000
|
||||
seconds = math.floor(seconds % 60)
|
||||
|
||||
if (format) then
|
||||
return string.format(format, minutes, seconds, millisecs)
|
||||
else
|
||||
return { h = hours, m = minutes, s = seconds, ms = millisecs }
|
||||
end
|
||||
end
|
||||
|
||||
function string.replace(str, tofind, toreplace)
|
||||
local tbl = string.Explode(tofind, str)
|
||||
if (tbl[1]) then return table.concat(tbl, toreplace) end
|
||||
return str
|
||||
end
|
||||
|
||||
function string.trim(s, char)
|
||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
|
||||
end
|
||||
|
||||
function string.trim_right(s, char)
|
||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||
return string.match(s, "^(.-)" .. char .. "*$") or s
|
||||
end
|
||||
|
||||
function string.trim_left(s, char)
|
||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||
return string.match(s, "^" .. char .. "*(.+)$") or s
|
||||
end
|
||||
|
||||
local meta = getmetatable("")
|
||||
|
||||
function meta:__index(key)
|
||||
local val = string[key]
|
||||
if (val ~= nil) then
|
||||
return val
|
||||
elseif (tonumber(key)) then
|
||||
return string.sub(self, key, key)
|
||||
end
|
||||
end
|
||||
|
||||
function string.starts_with(str, start)
|
||||
return string.sub(str, 1, string.len(start)) == start
|
||||
end
|
||||
|
||||
function string.ends_with(str, endStr)
|
||||
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
|
||||
end
|
||||
|
||||
-- --------- Deprecated functions ------ --
|
||||
local function wrap_deprecated(func, name, alternatives)
|
||||
return function (...)
|
||||
|
||||
289
res/scripts/stdmin.lua
Normal file
289
res/scripts/stdmin.lua
Normal file
@ -0,0 +1,289 @@
|
||||
-- Check if given table is an array
|
||||
function is_array(x)
|
||||
if #t > 0 then
|
||||
return true
|
||||
end
|
||||
for k, v in pairs(x) do
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Get entry-point and filename from `entry-point:filename` path
|
||||
function parse_path(path)
|
||||
local index = string.find(path, ':')
|
||||
if index == nil then
|
||||
error("invalid path syntax (':' missing)")
|
||||
end
|
||||
return string.sub(path, 1, index-1), string.sub(path, index+1, -1)
|
||||
end
|
||||
|
||||
function pack.is_installed(packid)
|
||||
return file.isfile(packid..":package.json")
|
||||
end
|
||||
|
||||
function pack.data_file(packid, name)
|
||||
file.mkdirs("world:data/"..packid)
|
||||
return "world:data/"..packid.."/"..name
|
||||
end
|
||||
|
||||
function pack.shared_file(packid, name)
|
||||
file.mkdirs("config:"..packid)
|
||||
return "config:"..packid.."/"..name
|
||||
end
|
||||
|
||||
|
||||
function timeit(iters, func, ...)
|
||||
local tm = time.uptime()
|
||||
for i=1,iters do
|
||||
func(...)
|
||||
end
|
||||
print("[time mcs]", (time.uptime()-tm) * 1000000)
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
function math.clamp(_in, low, high)
|
||||
return math.min(math.max(_in, low), high)
|
||||
end
|
||||
|
||||
function math.rand(low, high)
|
||||
return low + (high - low) * math.random()
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
function table.copy(t)
|
||||
local copied = {}
|
||||
|
||||
for k, v in pairs(t) do
|
||||
copied[k] = v
|
||||
end
|
||||
|
||||
return copied
|
||||
end
|
||||
|
||||
function table.count_pairs(t)
|
||||
local count = 0
|
||||
|
||||
for k, v in pairs(t) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
function table.random(t)
|
||||
return t[math.random(1, #t)]
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
local pattern_escape_replacements = {
|
||||
["("] = "%(",
|
||||
[")"] = "%)",
|
||||
["."] = "%.",
|
||||
["%"] = "%%",
|
||||
["+"] = "%+",
|
||||
["-"] = "%-",
|
||||
["*"] = "%*",
|
||||
["?"] = "%?",
|
||||
["["] = "%[",
|
||||
["]"] = "%]",
|
||||
["^"] = "%^",
|
||||
["$"] = "%$",
|
||||
["\0"] = "%z"
|
||||
}
|
||||
|
||||
function string.pattern_safe(str)
|
||||
return string.gsub(str, ".", pattern_escape_replacements)
|
||||
end
|
||||
|
||||
local string_sub = string.sub
|
||||
local string_find = string.find
|
||||
local string_len = string.len
|
||||
function string.explode(separator, str, withpattern)
|
||||
if (withpattern == nil) then withpattern = false end
|
||||
|
||||
local ret = {}
|
||||
local current_pos = 1
|
||||
|
||||
for i = 1, string_len(str) do
|
||||
local start_pos, end_pos = string_find(str, separator, current_pos, not withpattern)
|
||||
if (not start_pos) then break end
|
||||
ret[i] = string_sub(str, current_pos, start_pos - 1)
|
||||
current_pos = end_pos + 1
|
||||
end
|
||||
|
||||
ret[#ret + 1] = string_sub(str, current_pos)
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function string.split(str, delimiter)
|
||||
return string.explode(delimiter, str)
|
||||
end
|
||||
|
||||
function string.formatted_time(seconds, format)
|
||||
if (not seconds) then seconds = 0 end
|
||||
local hours = math.floor(seconds / 3600)
|
||||
local minutes = math.floor((seconds / 60) % 60)
|
||||
local millisecs = (seconds - math.floor(seconds)) * 1000
|
||||
seconds = math.floor(seconds % 60)
|
||||
|
||||
if (format) then
|
||||
return string.format(format, minutes, seconds, millisecs)
|
||||
else
|
||||
return { h = hours, m = minutes, s = seconds, ms = millisecs }
|
||||
end
|
||||
end
|
||||
|
||||
function string.replace(str, tofind, toreplace)
|
||||
local tbl = string.Explode(tofind, str)
|
||||
if (tbl[1]) then return table.concat(tbl, toreplace) end
|
||||
return str
|
||||
end
|
||||
|
||||
function string.trim(s, char)
|
||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
|
||||
end
|
||||
|
||||
function string.trim_right(s, char)
|
||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||
return string.match(s, "^(.-)" .. char .. "*$") or s
|
||||
end
|
||||
|
||||
function string.trim_left(s, char)
|
||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||
return string.match(s, "^" .. char .. "*(.+)$") or s
|
||||
end
|
||||
|
||||
local meta = getmetatable("")
|
||||
|
||||
function meta:__index(key)
|
||||
local val = string[key]
|
||||
if (val ~= nil) then
|
||||
return val
|
||||
elseif (tonumber(key)) then
|
||||
return string.sub(self, key, key)
|
||||
end
|
||||
end
|
||||
|
||||
function string.starts_with(str, start)
|
||||
return string.sub(str, 1, string.len(start)) == start
|
||||
end
|
||||
|
||||
function string.ends_with(str, endStr)
|
||||
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
|
||||
end
|
||||
|
||||
function table.has(t, x)
|
||||
for i,v in ipairs(t) do
|
||||
if v == x then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function table.index(t, x)
|
||||
for i,v in ipairs(t) do
|
||||
if v == x then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
function table.remove_value(t, x)
|
||||
local index = table.index(t, x)
|
||||
if index ~= -1 then
|
||||
table.remove(t, index)
|
||||
end
|
||||
end
|
||||
|
||||
function table.tostring(t)
|
||||
local s = '['
|
||||
for i,v in ipairs(t) do
|
||||
s = s..tostring(v)
|
||||
if i < #t then
|
||||
s = s..', '
|
||||
end
|
||||
end
|
||||
return s..']'
|
||||
end
|
||||
|
||||
function file.readlines(path)
|
||||
local str = file.read(path)
|
||||
local lines = {}
|
||||
for s in str:gmatch("[^\r\n]+") do
|
||||
table.insert(lines, s)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
package = {
|
||||
loaded={}
|
||||
}
|
||||
local __cached_scripts = {}
|
||||
local __warnings_hidden = {}
|
||||
|
||||
function on_deprecated_call(name, alternatives)
|
||||
if __warnings_hidden[name] then
|
||||
return
|
||||
end
|
||||
__warnings_hidden[name] = true
|
||||
if alternatives then
|
||||
debug.warning("deprecated function called ("..name.."), use "..
|
||||
alternatives.." instead\n"..debug.traceback())
|
||||
else
|
||||
debug.warning("deprecated function called ("..name..")\n"..debug.traceback())
|
||||
end
|
||||
end
|
||||
|
||||
-- Load script with caching
|
||||
--
|
||||
-- path - script path `contentpack:filename`.
|
||||
-- Example `base:scripts/tests.lua`
|
||||
--
|
||||
-- nocache - ignore cached script, load anyway
|
||||
local function __load_script(path, nocache)
|
||||
local packname, filename = parse_path(path)
|
||||
|
||||
-- __cached_scripts used in condition because cached result may be nil
|
||||
if not nocache and __cached_scripts[path] ~= nil then
|
||||
return package.loaded[path]
|
||||
end
|
||||
if not file.isfile(path) then
|
||||
error("script '"..filename.."' not found in '"..packname.."'")
|
||||
end
|
||||
|
||||
local script, err = load(file.read(path), path)
|
||||
if script == nil then
|
||||
error(err)
|
||||
end
|
||||
local result = script()
|
||||
if not nocache then
|
||||
__cached_scripts[path] = script
|
||||
package.loaded[path] = result
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function require(path)
|
||||
local prefix, file = parse_path(path)
|
||||
return __load_script(prefix..":modules/"..file..".lua")
|
||||
end
|
||||
|
||||
function __scripts_cleanup()
|
||||
debug.log("cleaning scripts cache")
|
||||
for k, v in pairs(__cached_scripts) do
|
||||
local packname, _ = parse_path(k)
|
||||
if packname ~= "core" then
|
||||
debug.log("unloaded "..k)
|
||||
__cached_scripts[k] = nil
|
||||
package.loaded[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -42,7 +42,7 @@ menu.Contents Menu=Меню контентпаков
|
||||
world.Seed=Зерно
|
||||
world.Name=Название
|
||||
world.World generator=Генератор мира
|
||||
world.generators.default=Обычный
|
||||
world.generators.default=По-умолчанию
|
||||
world.generators.flat=Плоский
|
||||
world.Create World=Создать Мир
|
||||
world.convert-request=Есть изменения в индексах! Конвертировать мир?
|
||||
|
||||
BIN
res/textures/blocks/obstacle.png
Normal file
BIN
res/textures/blocks/obstacle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
BIN
res/textures/blocks/struct_air.png
Normal file
BIN
res/textures/blocks/struct_air.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@ -242,7 +242,7 @@ dv::value Parser::parseValue() {
|
||||
} else if (literal == "nan") {
|
||||
return NAN;
|
||||
}
|
||||
throw error("invalid literal ");
|
||||
throw error("invalid keyword " + literal);
|
||||
}
|
||||
if (next == '{') {
|
||||
return parseObject();
|
||||
|
||||
@ -23,6 +23,8 @@ inline constexpr uint REGION_FORMAT_VERSION = 3;
|
||||
inline constexpr uint MAX_OPEN_REGION_FILES = 32;
|
||||
|
||||
inline constexpr blockid_t BLOCK_AIR = 0;
|
||||
inline constexpr blockid_t BLOCK_OBSTACLE = 1;
|
||||
inline constexpr blockid_t BLOCK_STRUCT_AIR = 2;
|
||||
inline constexpr itemid_t ITEM_EMPTY = 0;
|
||||
inline constexpr entityid_t ENTITY_NONE = 0;
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/rigging.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "world/generator/VoxelFragment.hpp"
|
||||
#include "world/generator/GeneratorDef.hpp"
|
||||
#include "ContentPack.hpp"
|
||||
|
||||
ContentIndices::ContentIndices(
|
||||
@ -28,6 +30,7 @@ Content::Content(
|
||||
ContentUnitDefs<Block> blocks,
|
||||
ContentUnitDefs<ItemDef> items,
|
||||
ContentUnitDefs<EntityDef> entities,
|
||||
ContentUnitDefs<GeneratorDef> generators,
|
||||
UptrsMap<std::string, ContentPackRuntime> packs,
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
|
||||
@ -40,6 +43,7 @@ Content::Content(
|
||||
blocks(std::move(blocks)),
|
||||
items(std::move(items)),
|
||||
entities(std::move(entities)),
|
||||
generators(std::move(generators)),
|
||||
drawGroups(std::move(drawGroups)) {
|
||||
for (size_t i = 0; i < RESOURCE_TYPES_COUNT; i++) {
|
||||
this->resourceIndices[i] = std::move(resourceIndices[i]);
|
||||
|
||||
@ -19,12 +19,13 @@ class Block;
|
||||
struct BlockMaterial;
|
||||
struct ItemDef;
|
||||
struct EntityDef;
|
||||
struct GeneratorDef;
|
||||
|
||||
namespace rigging {
|
||||
class SkeletonConfig;
|
||||
}
|
||||
|
||||
constexpr const char* contenttype_name(ContentType type) {
|
||||
constexpr const char* ContentType_name(ContentType type) {
|
||||
switch (type) {
|
||||
case ContentType::NONE:
|
||||
return "none";
|
||||
@ -34,6 +35,8 @@ constexpr const char* contenttype_name(ContentType type) {
|
||||
return "item";
|
||||
case ContentType::ENTITY:
|
||||
return "entity";
|
||||
case ContentType::GENERATOR:
|
||||
return "generator";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
@ -117,6 +120,10 @@ public:
|
||||
}
|
||||
return *found->second;
|
||||
}
|
||||
|
||||
const auto& getDefs() const {
|
||||
return defs;
|
||||
}
|
||||
};
|
||||
|
||||
class ResourceIndices {
|
||||
@ -130,12 +137,21 @@ public:
|
||||
|
||||
static constexpr size_t MISSING = SIZE_MAX;
|
||||
|
||||
void add(std::string name, dv::value map) {
|
||||
void add(const std::string& name, dv::value map) {
|
||||
indices[name] = names.size();
|
||||
names.push_back(name);
|
||||
savedData->push_back(std::move(map));
|
||||
}
|
||||
|
||||
void addAlias(const std::string& name, const std::string& alias) {
|
||||
size_t index = indexOf(name);
|
||||
if (index == MISSING) {
|
||||
throw std::runtime_error(
|
||||
"resource does not exists: "+name);
|
||||
}
|
||||
indices[alias] = index;
|
||||
}
|
||||
|
||||
const std::string& getName(size_t index) const {
|
||||
return names.at(index);
|
||||
}
|
||||
@ -189,6 +205,7 @@ public:
|
||||
ContentUnitDefs<Block> blocks;
|
||||
ContentUnitDefs<ItemDef> items;
|
||||
ContentUnitDefs<EntityDef> entities;
|
||||
ContentUnitDefs<GeneratorDef> generators;
|
||||
std::unique_ptr<DrawGroups> const drawGroups;
|
||||
ResourceIndicesSet resourceIndices {};
|
||||
|
||||
@ -198,6 +215,7 @@ public:
|
||||
ContentUnitDefs<Block> blocks,
|
||||
ContentUnitDefs<ItemDef> items,
|
||||
ContentUnitDefs<EntityDef> entities,
|
||||
ContentUnitDefs<GeneratorDef> generators,
|
||||
UptrsMap<std::string, ContentPackRuntime> packs,
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
|
||||
|
||||
@ -72,6 +72,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
blocks.build(),
|
||||
items.build(),
|
||||
entities.build(),
|
||||
generators.build(),
|
||||
std::move(packs),
|
||||
std::move(blockMaterials),
|
||||
std::move(skeletons),
|
||||
@ -81,11 +82,16 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
// Now, it's time to resolve foreign keys
|
||||
for (Block* def : blockDefsIndices) {
|
||||
def->rt.pickingItem = content->items.require(def->pickingItem).rt.id;
|
||||
def->rt.surfaceReplacement = content->blocks.require(def->surfaceReplacement).rt.id;
|
||||
}
|
||||
|
||||
for (ItemDef* def : itemDefsIndices) {
|
||||
def->rt.placingBlock = content->blocks.require(def->placingBlock).rt.id;
|
||||
}
|
||||
|
||||
for (auto& [name, def] : content->generators.getDefs()) {
|
||||
def->prepare(content.get());
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
#include "ContentPack.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "world/generator/VoxelFragment.hpp"
|
||||
#include "world/generator/GeneratorDef.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
|
||||
template <class T>
|
||||
@ -67,6 +69,7 @@ public:
|
||||
ContentUnitBuilder<Block> blocks {allNames, ContentType::BLOCK};
|
||||
ContentUnitBuilder<ItemDef> items {allNames, ContentType::ITEM};
|
||||
ContentUnitBuilder<EntityDef> entities {allNames, ContentType::ENTITY};
|
||||
ContentUnitBuilder<GeneratorDef> generators {allNames, ContentType::GENERATOR};
|
||||
ResourceIndicesSet resourceIndices {};
|
||||
|
||||
~ContentBuilder();
|
||||
|
||||
@ -28,8 +28,10 @@ using namespace data;
|
||||
|
||||
static debug::Logger logger("content-loader");
|
||||
|
||||
ContentLoader::ContentLoader(ContentPack* pack, ContentBuilder& builder)
|
||||
: pack(pack), builder(builder) {
|
||||
ContentLoader::ContentLoader(
|
||||
ContentPack* pack, ContentBuilder& builder, const ResPaths& paths
|
||||
)
|
||||
: pack(pack), builder(builder), paths(paths) {
|
||||
auto runtime = std::make_unique<ContentPackRuntime>(
|
||||
*pack, scripting::create_pack_environment(*pack)
|
||||
);
|
||||
@ -51,15 +53,53 @@ static void detect_defs(
|
||||
if (name[0] == '_') {
|
||||
continue;
|
||||
}
|
||||
if (fs::is_regular_file(file) && file.extension() == ".json") {
|
||||
detected.push_back(prefix.empty() ? name : prefix + ":" + name);
|
||||
} else if (fs::is_directory(file)) {
|
||||
if (fs::is_regular_file(file) && files::is_data_file(file)) {
|
||||
auto map = files::read_object(file);
|
||||
std::string id = prefix.empty() ? name : prefix + ":" + name;
|
||||
detected.emplace_back(id);
|
||||
} else if (fs::is_directory(file) &&
|
||||
file.extension() != fs::u8path(".files")) {
|
||||
detect_defs(file, name, detected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void detect_defs_pairs(
|
||||
const fs::path& folder,
|
||||
const std::string& prefix,
|
||||
std::vector<std::tuple<std::string, std::string>>& detected
|
||||
) {
|
||||
if (fs::is_directory(folder)) {
|
||||
for (const auto& entry : fs::directory_iterator(folder)) {
|
||||
const fs::path& file = entry.path();
|
||||
std::string name = file.stem().string();
|
||||
if (name[0] == '_') {
|
||||
continue;
|
||||
}
|
||||
if (fs::is_regular_file(file) && files::is_data_file(file)) {
|
||||
auto map = files::read_object(file);
|
||||
std::string id = prefix.empty() ? name : prefix + ":" + name;
|
||||
std::string caption = util::id_to_caption(id);
|
||||
map.at("caption").get(caption);
|
||||
detected.emplace_back(id, name);
|
||||
} else if (fs::is_directory(file) &&
|
||||
file.extension() != fs::u8path(".files")) {
|
||||
detect_defs_pairs(file, name, detected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<std::string, std::string>> ContentLoader::scanContent(
|
||||
const ContentPack& pack, ContentType type
|
||||
) {
|
||||
std::vector<std::tuple<std::string, std::string>> detected;
|
||||
detect_defs_pairs(
|
||||
pack.folder / ContentPack::getFolderFor(type), pack.id, detected);
|
||||
return detected;
|
||||
}
|
||||
|
||||
bool ContentLoader::fixPackIndices(
|
||||
const fs::path& folder,
|
||||
dv::value& indicesRoot,
|
||||
@ -95,14 +135,14 @@ bool ContentLoader::fixPackIndices(
|
||||
|
||||
void ContentLoader::fixPackIndices() {
|
||||
auto folder = pack->folder;
|
||||
auto indexFile = pack->getContentFile();
|
||||
auto contentFile = pack->getContentFile();
|
||||
auto blocksFolder = folder / ContentPack::BLOCKS_FOLDER;
|
||||
auto itemsFolder = folder / ContentPack::ITEMS_FOLDER;
|
||||
auto entitiesFolder = folder / ContentPack::ENTITIES_FOLDER;
|
||||
|
||||
dv::value root;
|
||||
if (fs::is_regular_file(indexFile)) {
|
||||
root = files::read_json(indexFile);
|
||||
if (fs::is_regular_file(contentFile)) {
|
||||
root = files::read_json(contentFile);
|
||||
} else {
|
||||
root = dv::object();
|
||||
}
|
||||
@ -114,7 +154,7 @@ void ContentLoader::fixPackIndices() {
|
||||
|
||||
if (modified) {
|
||||
// rewrite modified json
|
||||
files::write_json(indexFile, root);
|
||||
files::write_json(contentFile, root);
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,6 +317,7 @@ void ContentLoader::loadBlock(
|
||||
root.at("hidden").get(def.hidden);
|
||||
root.at("draw-group").get(def.drawGroup);
|
||||
root.at("picking-item").get(def.pickingItem);
|
||||
root.at("surface-replacement").get(def.surfaceReplacement);
|
||||
root.at("script-name").get(def.scriptName);
|
||||
root.at("ui-layout").get(def.uiLayout);
|
||||
root.at("inventory-size").get(def.inventorySize);
|
||||
@ -511,6 +552,18 @@ void ContentLoader::loadItem(
|
||||
}
|
||||
}
|
||||
|
||||
static std::tuple<std::string, std::string, std::string> create_unit_id(
|
||||
const std::string& packid, const std::string& name
|
||||
) {
|
||||
size_t colon = name.find(':');
|
||||
if (colon == std::string::npos) {
|
||||
return {packid, packid + ":" + name, name};
|
||||
}
|
||||
auto otherPackid = name.substr(0, colon);
|
||||
auto full = otherPackid + ":" + name;
|
||||
return {otherPackid, full, otherPackid + "/" + name};
|
||||
}
|
||||
|
||||
void ContentLoader::loadBlockMaterial(
|
||||
BlockMaterial& def, const fs::path& file
|
||||
) {
|
||||
@ -520,23 +573,7 @@ void ContentLoader::loadBlockMaterial(
|
||||
root.at("break-sound").get(def.breakSound);
|
||||
}
|
||||
|
||||
void ContentLoader::load() {
|
||||
logger.info() << "loading pack [" << pack->id << "]";
|
||||
|
||||
fixPackIndices();
|
||||
|
||||
auto folder = pack->folder;
|
||||
|
||||
fs::path scriptFile = folder / fs::path("scripts/world.lua");
|
||||
if (fs::is_regular_file(scriptFile)) {
|
||||
scripting::load_world_script(
|
||||
env, pack->id, scriptFile, runtime->worldfuncsset
|
||||
);
|
||||
}
|
||||
|
||||
if (!fs::is_regular_file(pack->getContentFile())) return;
|
||||
|
||||
auto root = files::read_json(pack->getContentFile());
|
||||
void ContentLoader::loadContent(const dv::value& root) {
|
||||
std::vector<std::pair<std::string, std::string>> pendingDefs;
|
||||
auto getJsonParent = [this](const std::string& prefix, const std::string& name) {
|
||||
auto configFile = pack->folder / fs::path(prefix + "/" + name + ".json");
|
||||
@ -693,39 +730,53 @@ void ContentLoader::load() {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::path materialsDir = folder / fs::u8path("block_materials");
|
||||
if (fs::is_directory(materialsDir)) {
|
||||
for (const auto& entry : fs::directory_iterator(materialsDir)) {
|
||||
const fs::path& file = entry.path();
|
||||
std::string name = pack->id + ":" + file.stem().u8string();
|
||||
loadBlockMaterial(builder.createBlockMaterial(name), file);
|
||||
}
|
||||
}
|
||||
|
||||
fs::path skeletonsDir = folder / fs::u8path("skeletons");
|
||||
if (fs::is_directory(skeletonsDir)) {
|
||||
for (const auto& entry : fs::directory_iterator(skeletonsDir)) {
|
||||
const fs::path& file = entry.path();
|
||||
std::string name = pack->id + ":" + file.stem().u8string();
|
||||
std::string text = files::read_string(file);
|
||||
builder.add(
|
||||
rigging::SkeletonConfig::parse(text, file.u8string(), name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fs::path componentsDir = folder / fs::u8path("scripts/components");
|
||||
if (fs::is_directory(componentsDir)) {
|
||||
for (const auto& entry : fs::directory_iterator(componentsDir)) {
|
||||
fs::path scriptfile = entry.path();
|
||||
if (fs::is_regular_file(scriptfile)) {
|
||||
auto name = pack->id + ":" + scriptfile.stem().u8string();
|
||||
scripting::load_entity_component(name, scriptfile);
|
||||
static inline void foreach_file(
|
||||
const fs::path& dir, std::function<void(const fs::path&)> handler
|
||||
) {
|
||||
if (fs::is_directory(dir)) {
|
||||
for (const auto& entry : fs::directory_iterator(dir)) {
|
||||
const auto& path = entry.path();
|
||||
if (fs::is_directory(path)) {
|
||||
continue;
|
||||
}
|
||||
handler(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContentLoader::load() {
|
||||
logger.info() << "loading pack [" << pack->id << "]";
|
||||
|
||||
fixPackIndices();
|
||||
|
||||
auto folder = pack->folder;
|
||||
|
||||
// Load main world script
|
||||
fs::path scriptFile = folder / fs::path("scripts/world.lua");
|
||||
if (fs::is_regular_file(scriptFile)) {
|
||||
scripting::load_world_script(
|
||||
env, pack->id, scriptFile, runtime->worldfuncsset
|
||||
);
|
||||
}
|
||||
|
||||
// Load world generators
|
||||
fs::path generatorsDir = folder / fs::u8path("generators");
|
||||
foreach_file(generatorsDir, [this](const fs::path& file) {
|
||||
std::string name = file.stem().u8string();
|
||||
auto [packid, full, filename] =
|
||||
create_unit_id(pack->id, file.stem().u8string());
|
||||
|
||||
auto& def = builder.generators.create(full);
|
||||
try {
|
||||
loadGenerator(def, full, name);
|
||||
} catch (const std::runtime_error& err) {
|
||||
throw std::runtime_error("generator '"+full+"': "+err.what());
|
||||
}
|
||||
});
|
||||
|
||||
// Load pack resources.json
|
||||
fs::path resourcesFile = folder / fs::u8path("resources.json");
|
||||
if (fs::exists(resourcesFile)) {
|
||||
auto resRoot = files::read_json(resourcesFile);
|
||||
@ -733,10 +784,62 @@ void ContentLoader::load() {
|
||||
if (auto resType = ResourceType_from(key)) {
|
||||
loadResources(*resType, arr);
|
||||
} else {
|
||||
// Ignore unknown resources
|
||||
logger.warning() << "unknown resource type: " << key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load pack resources aliases
|
||||
fs::path aliasesFile = folder / fs::u8path("resource-aliases.json");
|
||||
if (fs::exists(aliasesFile)) {
|
||||
auto resRoot = files::read_json(aliasesFile);
|
||||
for (const auto& [key, arr] : resRoot.asObject()) {
|
||||
if (auto resType = ResourceType_from(key)) {
|
||||
loadResourceAliases(*resType, arr);
|
||||
} else {
|
||||
// Ignore unknown resources
|
||||
logger.warning() << "unknown resource type: " << key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load block materials
|
||||
fs::path materialsDir = folder / fs::u8path("block_materials");
|
||||
if (fs::is_directory(materialsDir)) {
|
||||
for (const auto& entry : fs::directory_iterator(materialsDir)) {
|
||||
const auto& file = entry.path();
|
||||
auto [packid, full, filename] =
|
||||
create_unit_id(pack->id, file.stem().u8string());
|
||||
loadBlockMaterial(
|
||||
builder.createBlockMaterial(full),
|
||||
materialsDir / fs::u8path(filename + ".json")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Load skeletons
|
||||
fs::path skeletonsDir = folder / fs::u8path("skeletons");
|
||||
foreach_file(skeletonsDir, [this](const fs::path& file) {
|
||||
std::string name = pack->id + ":" + file.stem().u8string();
|
||||
std::string text = files::read_string(file);
|
||||
builder.add(
|
||||
rigging::SkeletonConfig::parse(text, file.u8string(), name)
|
||||
);
|
||||
});
|
||||
|
||||
// Load entity components
|
||||
fs::path componentsDir = folder / fs::u8path("scripts/components");
|
||||
foreach_file(componentsDir, [this](const fs::path& file) {
|
||||
auto name = pack->id + ":" + file.stem().u8string();
|
||||
scripting::load_entity_component(name, file);
|
||||
});
|
||||
|
||||
// Process content.json and load defined content units
|
||||
auto contentFile = pack->getContentFile();
|
||||
if (fs::exists(contentFile)) {
|
||||
loadContent(files::read_json(contentFile));
|
||||
}
|
||||
}
|
||||
|
||||
void ContentLoader::loadResources(ResourceType type, const dv::value& list) {
|
||||
@ -746,3 +849,11 @@ void ContentLoader::loadResources(ResourceType type, const dv::value& list) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentLoader::loadResourceAliases(ResourceType type, const dv::value& aliases) {
|
||||
for (const auto& [alias, name] : aliases.asObject()) {
|
||||
builder.resourceIndices[static_cast<size_t>(type)].addAlias(
|
||||
name.asString(), alias
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,9 @@ struct BlockMaterial;
|
||||
struct ItemDef;
|
||||
struct EntityDef;
|
||||
struct ContentPack;
|
||||
struct GeneratorDef;
|
||||
|
||||
class ResPaths;
|
||||
class ContentBuilder;
|
||||
class ContentPackRuntime;
|
||||
struct ContentPackStats;
|
||||
@ -25,6 +27,7 @@ class ContentLoader {
|
||||
scriptenv env;
|
||||
ContentBuilder& builder;
|
||||
ContentPackStats* stats;
|
||||
const ResPaths& paths;
|
||||
|
||||
void loadBlock(
|
||||
Block& def, const std::string& full, const std::string& name
|
||||
@ -35,6 +38,9 @@ class ContentLoader {
|
||||
void loadEntity(
|
||||
EntityDef& def, const std::string& full, const std::string& name
|
||||
);
|
||||
void loadGenerator(
|
||||
GeneratorDef& def, const std::string& full, const std::string& name
|
||||
);
|
||||
|
||||
static void loadCustomBlockModel(Block& def, const dv::value& primitives);
|
||||
static void loadBlockMaterial(BlockMaterial& def, const fs::path& file);
|
||||
@ -48,14 +54,27 @@ class ContentLoader {
|
||||
EntityDef& def, const std::string& name, const fs::path& file
|
||||
);
|
||||
void loadResources(ResourceType type, const dv::value& list);
|
||||
public:
|
||||
ContentLoader(ContentPack* pack, ContentBuilder& builder);
|
||||
void loadResourceAliases(ResourceType type, const dv::value& aliases);
|
||||
|
||||
bool fixPackIndices(
|
||||
void loadContent(const dv::value& map);
|
||||
public:
|
||||
ContentLoader(
|
||||
ContentPack* pack,
|
||||
ContentBuilder& builder,
|
||||
const ResPaths& paths
|
||||
);
|
||||
|
||||
// Refresh pack content.json
|
||||
static bool fixPackIndices(
|
||||
const fs::path& folder,
|
||||
dv::value& indicesRoot,
|
||||
const std::string& contentSection
|
||||
);
|
||||
|
||||
static std::vector<std::tuple<std::string, std::string>> scanContent(
|
||||
const ContentPack& pack, ContentType type
|
||||
);
|
||||
|
||||
void fixPackIndices();
|
||||
void load();
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "content_fwd.hpp"
|
||||
|
||||
class EnginePaths;
|
||||
|
||||
@ -51,6 +52,7 @@ struct ContentPack {
|
||||
static inline const fs::path BLOCKS_FOLDER = "blocks";
|
||||
static inline const fs::path ITEMS_FOLDER = "items";
|
||||
static inline const fs::path ENTITIES_FOLDER = "entities";
|
||||
static inline const fs::path GENERATORS_FOLDER = "generators";
|
||||
static const std::vector<std::string> RESERVED_NAMES;
|
||||
|
||||
static bool is_pack(const fs::path& folder);
|
||||
@ -69,6 +71,16 @@ struct ContentPack {
|
||||
);
|
||||
|
||||
static ContentPack createCore(const EnginePaths*);
|
||||
|
||||
static inline fs::path getFolderFor(ContentType type) {
|
||||
switch (type) {
|
||||
case ContentType::BLOCK: return ContentPack::BLOCKS_FOLDER;
|
||||
case ContentType::ITEM: return ContentPack::ITEMS_FOLDER;
|
||||
case ContentType::ENTITY: return ContentPack::ENTITIES_FOLDER;
|
||||
case ContentType::GENERATOR: return ContentPack::GENERATORS_FOLDER;
|
||||
case ContentType::NONE: return fs::u8path("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ContentPackStats {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
class Content;
|
||||
class ContentPackRuntime;
|
||||
|
||||
enum class ContentType { NONE, BLOCK, ITEM, ENTITY };
|
||||
enum class ContentType { NONE, BLOCK, ITEM, ENTITY, GENERATOR };
|
||||
|
||||
enum class ResourceType : size_t { CAMERA, LAST = CAMERA };
|
||||
|
||||
|
||||
225
src/content/loading/GeneratorLoader.cpp
Normal file
225
src/content/loading/GeneratorLoader.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
#include "../ContentLoader.hpp"
|
||||
|
||||
#include "../ContentPack.hpp"
|
||||
|
||||
#include "files/files.hpp"
|
||||
#include "files/engine_paths.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "world/generator/GeneratorDef.hpp"
|
||||
#include "world/generator/VoxelFragment.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
static BlocksLayer load_layer(
|
||||
const dv::value& map, uint& lastLayersHeight, bool& hasResizeableLayer
|
||||
) {
|
||||
const auto& name = map["block"].asString();
|
||||
int height = map["height"].asInteger();
|
||||
bool belowSeaLevel = true;
|
||||
map.at("below-sea-level").get(belowSeaLevel);
|
||||
|
||||
if (hasResizeableLayer) {
|
||||
lastLayersHeight += height;
|
||||
}
|
||||
if (height == -1) {
|
||||
if (hasResizeableLayer) {
|
||||
throw std::runtime_error("only one resizeable layer allowed");
|
||||
}
|
||||
hasResizeableLayer = true;
|
||||
}
|
||||
return BlocksLayer {name, height, belowSeaLevel, {}};
|
||||
}
|
||||
|
||||
static inline BlocksLayers load_layers(
|
||||
const dv::value& layersArr, const std::string& fieldname
|
||||
) {
|
||||
uint lastLayersHeight = 0;
|
||||
bool hasResizeableLayer = false;
|
||||
std::vector<BlocksLayer> layers;
|
||||
|
||||
for (int i = 0; i < layersArr.size(); i++) {
|
||||
const auto& layerMap = layersArr[i];
|
||||
try {
|
||||
layers.push_back(
|
||||
load_layer(layerMap, lastLayersHeight, hasResizeableLayer));
|
||||
} catch (const std::runtime_error& err) {
|
||||
throw std::runtime_error(
|
||||
fieldname+" #"+std::to_string(i)+": "+err.what());
|
||||
}
|
||||
}
|
||||
return BlocksLayers {std::move(layers), lastLayersHeight};
|
||||
}
|
||||
|
||||
static inline BiomeElementList load_biome_element_list(
|
||||
const dv::value map,
|
||||
const std::string& chanceName,
|
||||
const std::string& arrName,
|
||||
const std::string& nameName
|
||||
) {
|
||||
float chance = 0.0f;
|
||||
map.at(chanceName).get(chance);
|
||||
std::vector<WeightedEntry> entries;
|
||||
if (map.has(arrName)) {
|
||||
const auto& arr = map[arrName];
|
||||
for (const auto& entry : arr) {
|
||||
const auto& name = entry[nameName].asString();
|
||||
float weight = entry["weight"].asNumber();
|
||||
if (weight <= 0.0f) {
|
||||
throw std::runtime_error("weight must be positive");
|
||||
}
|
||||
entries.push_back(WeightedEntry {name, weight, {}});
|
||||
}
|
||||
}
|
||||
std::sort(entries.begin(), entries.end(), std::greater<WeightedEntry>());
|
||||
return BiomeElementList(std::move(entries), chance);
|
||||
}
|
||||
|
||||
static inline BiomeElementList load_plants(const dv::value& biomeMap) {
|
||||
return load_biome_element_list(biomeMap, "plant-chance", "plants", "block");
|
||||
}
|
||||
|
||||
static inline BiomeElementList load_structures(const dv::value map) {
|
||||
return load_biome_element_list(map, "structure-chance", "structures", "name");
|
||||
}
|
||||
|
||||
static debug::Logger logger("generator-loader");
|
||||
|
||||
static inline Biome load_biome(
|
||||
const dv::value& biomeMap,
|
||||
const std::string& name,
|
||||
uint parametersCount
|
||||
) {
|
||||
std::vector<BiomeParameter> parameters;
|
||||
|
||||
const auto& paramsArr = biomeMap["parameters"];
|
||||
if (paramsArr.size() < parametersCount) {
|
||||
throw std::runtime_error(
|
||||
std::to_string(parametersCount)+" parameters expected");
|
||||
}
|
||||
for (size_t i = 0; i < parametersCount; i++) {
|
||||
const auto& paramMap = paramsArr[i];
|
||||
float value = paramMap["value"].asNumber();
|
||||
float weight = paramMap["weight"].asNumber();
|
||||
parameters.push_back(BiomeParameter {value, weight});
|
||||
}
|
||||
|
||||
auto plants = load_plants(biomeMap);
|
||||
auto groundLayers = load_layers(biomeMap["layers"], "layers");
|
||||
auto seaLayers = load_layers(biomeMap["sea-layers"], "sea-layers");
|
||||
|
||||
BiomeElementList structures;
|
||||
if (biomeMap.has("structures")) {
|
||||
structures = load_structures(biomeMap);
|
||||
}
|
||||
return Biome {
|
||||
name,
|
||||
std::move(parameters),
|
||||
std::move(plants),
|
||||
std::move(structures),
|
||||
std::move(groundLayers),
|
||||
std::move(seaLayers)};
|
||||
}
|
||||
|
||||
static VoxelStructureMeta load_structure_meta(
|
||||
const std::string& name, const dv::value& config
|
||||
) {
|
||||
VoxelStructureMeta meta;
|
||||
meta.name = name;
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
static std::vector<std::unique_ptr<VoxelStructure>> load_structures(
|
||||
const fs::path& structuresFile
|
||||
) {
|
||||
auto structuresDir = structuresFile.parent_path() / fs::path("fragments");
|
||||
auto map = files::read_object(structuresFile);
|
||||
|
||||
std::vector<std::unique_ptr<VoxelStructure>> structures;
|
||||
for (auto& [name, config] : map.asObject()) {
|
||||
auto structFile = structuresDir / fs::u8path(name + ".vox");
|
||||
logger.debug() << "loading voxel fragment " << structFile.u8string();
|
||||
if (!fs::exists(structFile)) {
|
||||
throw std::runtime_error("structure file does not exist (" +
|
||||
structFile.u8string());
|
||||
}
|
||||
auto fragment = std::make_unique<VoxelFragment>();
|
||||
fragment->deserialize(files::read_binary_json(structFile));
|
||||
logger.info() << "fragment " << name << " has size [" <<
|
||||
fragment->getSize().x << ", " << fragment->getSize().y << ", " <<
|
||||
fragment->getSize().z << "]";
|
||||
|
||||
structures.push_back(std::make_unique<VoxelStructure>(
|
||||
load_structure_meta(name, config),
|
||||
std::move(fragment)
|
||||
));
|
||||
}
|
||||
return structures;
|
||||
}
|
||||
|
||||
static void load_structures(GeneratorDef& def, const fs::path& structuresFile) {
|
||||
auto rawStructures = load_structures(structuresFile);
|
||||
def.structures.resize(rawStructures.size());
|
||||
|
||||
for (int i = 0; i < rawStructures.size(); i++) {
|
||||
def.structures[i] = std::move(rawStructures[i]);
|
||||
}
|
||||
// build indices map
|
||||
for (size_t i = 0; i < def.structures.size(); i++) {
|
||||
auto& structure = def.structures[i];
|
||||
def.structuresIndices[structure->meta.name] = i;
|
||||
}
|
||||
}
|
||||
|
||||
static inline const auto STRUCTURES_FILE = fs::u8path("structures.toml");
|
||||
static inline const auto BIOMES_FILE = fs::u8path("biomes.toml");
|
||||
static inline const auto GENERATORS_DIR = fs::u8path("generators");
|
||||
|
||||
static void load_biomes(GeneratorDef& def, const dv::value& root) {
|
||||
for (const auto& [biomeName, biomeMap] : root.asObject()) {
|
||||
try {
|
||||
def.biomes.push_back(
|
||||
load_biome(biomeMap, biomeName, def.biomeParameters));
|
||||
} catch (const std::runtime_error& err) {
|
||||
throw std::runtime_error("biome "+biomeName+": "+err.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContentLoader::loadGenerator(
|
||||
GeneratorDef& def, const std::string& full, const std::string& name
|
||||
) {
|
||||
auto packDir = pack->folder;
|
||||
auto generatorsDir = packDir / GENERATORS_DIR;
|
||||
auto generatorFile = generatorsDir / fs::u8path(name + ".toml");
|
||||
if (!fs::exists(generatorFile)) {
|
||||
return;
|
||||
}
|
||||
auto map = files::read_toml(generatorsDir / fs::u8path(name + ".toml"));
|
||||
map.at("caption").get(def.caption);
|
||||
map.at("biome-parameters").get(def.biomeParameters);
|
||||
map.at("biome-bpd").get(def.biomesBPD);
|
||||
map.at("heights-bpd").get(def.heightsBPD);
|
||||
map.at("sea-level").get(def.seaLevel);
|
||||
map.at("wide-structs-chunks-radius").get(def.wideStructsChunksRadius);
|
||||
|
||||
auto folder = generatorsDir / fs::u8path(name + ".files");
|
||||
auto scriptFile = folder / fs::u8path("script.lua");
|
||||
|
||||
auto structuresFile = folder / STRUCTURES_FILE;
|
||||
if (fs::exists(structuresFile)) {
|
||||
load_structures(def, structuresFile);
|
||||
}
|
||||
|
||||
auto biomesFile = GENERATORS_DIR / fs::u8path(name + ".files") / BIOMES_FILE;
|
||||
auto biomesMap = paths.readCombinedObject(biomesFile.u8string());
|
||||
if (biomesMap.empty()) {
|
||||
throw std::runtime_error(
|
||||
"generator " + util::quote(def.name) +
|
||||
": at least one biome required"
|
||||
);
|
||||
}
|
||||
load_biomes(def, biomesMap);
|
||||
def.script = scripting::load_generator(
|
||||
def, scriptFile, pack->id+":generators/"+name+".files");
|
||||
}
|
||||
@ -12,18 +12,21 @@
|
||||
|
||||
// All in-game definitions (blocks, items, etc..)
|
||||
void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
Block& block = builder->blocks.create("core:air");
|
||||
block.replaceable = true;
|
||||
block.drawGroup = 1;
|
||||
block.lightPassing = true;
|
||||
block.skyLightPassing = true;
|
||||
block.obstacle = false;
|
||||
block.selectable = false;
|
||||
block.model = BlockModel::none;
|
||||
block.pickingItem = "core:empty";
|
||||
|
||||
ItemDef& item = builder->items.create("core:empty");
|
||||
item.iconType = item_icon_type::none;
|
||||
{
|
||||
Block& block = builder->blocks.create(CORE_AIR);
|
||||
block.replaceable = true;
|
||||
block.drawGroup = 1;
|
||||
block.lightPassing = true;
|
||||
block.skyLightPassing = true;
|
||||
block.obstacle = false;
|
||||
block.selectable = false;
|
||||
block.model = BlockModel::none;
|
||||
block.pickingItem = CORE_EMPTY;
|
||||
}
|
||||
{
|
||||
ItemDef& item = builder->items.create(CORE_EMPTY);
|
||||
item.iconType = item_icon_type::none;
|
||||
}
|
||||
|
||||
auto bindsFile = paths->getResourcesFolder()/fs::path("bindings.toml");
|
||||
if (fs::is_regular_file(bindsFile)) {
|
||||
@ -31,4 +34,34 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
bindsFile.u8string(), files::read_string(bindsFile)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
Block& block = builder->blocks.create(CORE_OBSTACLE);
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
block.textureFaces[i] = "obstacle";
|
||||
}
|
||||
block.hitboxes = {AABB()};
|
||||
block.breakable = false;
|
||||
ItemDef& item = builder->items.create(CORE_OBSTACLE+".item");
|
||||
item.iconType = item_icon_type::block;
|
||||
item.icon = CORE_OBSTACLE;
|
||||
item.placingBlock = CORE_OBSTACLE;
|
||||
item.caption = block.caption;
|
||||
}
|
||||
{
|
||||
Block& block = builder->blocks.create(CORE_STRUCT_AIR);
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
block.textureFaces[i] = "struct_air";
|
||||
}
|
||||
block.drawGroup = -1;
|
||||
block.skyLightPassing = true;
|
||||
block.lightPassing = true;
|
||||
block.hitboxes = {AABB()};
|
||||
block.obstacle = false;
|
||||
ItemDef& item = builder->items.create(CORE_STRUCT_AIR+".item");
|
||||
item.iconType = item_icon_type::block;
|
||||
item.icon = CORE_STRUCT_AIR;
|
||||
item.placingBlock = CORE_STRUCT_AIR;
|
||||
item.caption = block.caption;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
inline const std::string CORE_EMPTY = "core:empty";
|
||||
inline const std::string CORE_AIR = "core:air";
|
||||
inline const std::string CORE_OBSTACLE = "core:obstacle";
|
||||
inline const std::string CORE_STRUCT_AIR = "core:struct_air";
|
||||
|
||||
inline const std::string TEXTURE_NOTFOUND = "notfound";
|
||||
|
||||
|
||||
@ -505,6 +505,9 @@ namespace dv {
|
||||
inline bool isNumber() const noexcept {
|
||||
return type == value_type::number;
|
||||
}
|
||||
inline bool isBoolean() const noexcept {
|
||||
return type == value_type::boolean;
|
||||
}
|
||||
};
|
||||
|
||||
inline bool is_numeric(const value& val) {
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace dv {
|
||||
template <int n>
|
||||
inline dv::value to_value(glm::vec<n, float> vec) {
|
||||
template <int n, typename T>
|
||||
inline dv::value to_value(glm::vec<n, T> vec) {
|
||||
auto list = dv::list();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
list.add(vec[i]);
|
||||
@ -14,8 +14,8 @@ namespace dv {
|
||||
return list;
|
||||
}
|
||||
|
||||
template <int n, int m>
|
||||
inline dv::value to_value(glm::mat<n, m, float> mat) {
|
||||
template <int n, int m, typename T>
|
||||
inline dv::value to_value(glm::mat<n, m, T> mat) {
|
||||
auto list = dv::list();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
for (size_t j = 0; j < m; j++) {
|
||||
@ -32,14 +32,18 @@ namespace dv {
|
||||
}
|
||||
}
|
||||
|
||||
template <int n>
|
||||
void get_vec(const dv::value& map, const std::string& key, glm::vec<n, float>& vec) {
|
||||
template <int n, typename T>
|
||||
void get_vec(const dv::value& map, const std::string& key, glm::vec<n, T>& vec) {
|
||||
if (!map.has(key)) {
|
||||
return;
|
||||
}
|
||||
auto& list = map[key];
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
vec[i] = list[i].asNumber();
|
||||
if constexpr (std::is_floating_point<T>()) {
|
||||
vec[i] = list[i].asNumber();
|
||||
} else {
|
||||
vec[i] = list[i].asInteger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,13 +31,10 @@
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include "voxels/DefaultWorldGenerator.hpp"
|
||||
#include "voxels/FlatWorldGenerator.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "window/Events.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/WorldGenerators.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <iostream>
|
||||
@ -51,11 +48,6 @@ static debug::Logger logger("engine");
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static void add_world_generators() {
|
||||
WorldGenerators::addGenerator<DefaultWorldGenerator>("core:default");
|
||||
WorldGenerators::addGenerator<FlatWorldGenerator>("core:flat");
|
||||
}
|
||||
|
||||
static void create_channel(Engine* engine, std::string name, NumberSetting& setting) {
|
||||
if (name != "master") {
|
||||
audio::create_channel(name);
|
||||
@ -115,7 +107,6 @@ Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, Engin
|
||||
keepAlive(settings.ui.language.observe([=](auto lang) {
|
||||
setLanguage(lang);
|
||||
}, true));
|
||||
add_world_generators();
|
||||
|
||||
scripting::initialize(this);
|
||||
basePacks = files::read_list(resdir/fs::path("config/builtins.list"));
|
||||
@ -318,21 +309,28 @@ void Engine::loadContent() {
|
||||
names = manager.assembly(names);
|
||||
contentPacks = manager.getAll(names);
|
||||
|
||||
std::vector<PathsRoot> resRoots;
|
||||
{
|
||||
auto pack = ContentPack::createCore(paths);
|
||||
resRoots.push_back({"core", pack.folder});
|
||||
ContentLoader(&pack, contentBuilder).load();
|
||||
load_configs(pack.folder);
|
||||
}
|
||||
auto corePack = ContentPack::createCore(paths);
|
||||
|
||||
// Setup filesystem entry points
|
||||
std::vector<PathsRoot> resRoots {
|
||||
{"core", corePack.folder}
|
||||
};
|
||||
for (auto& pack : contentPacks) {
|
||||
resRoots.push_back({pack.id, pack.folder});
|
||||
ContentLoader(&pack, contentBuilder).load();
|
||||
}
|
||||
resPaths = std::make_unique<ResPaths>(resdir, resRoots);
|
||||
|
||||
// Load content
|
||||
{
|
||||
ContentLoader(&corePack, contentBuilder, *resPaths).load();
|
||||
load_configs(corePack.folder);
|
||||
}
|
||||
for (auto& pack : contentPacks) {
|
||||
ContentLoader(&pack, contentBuilder, *resPaths).load();
|
||||
load_configs(pack.folder);
|
||||
}
|
||||
|
||||
content = contentBuilder.build();
|
||||
resPaths = std::make_unique<ResPaths>(resdir, resRoots);
|
||||
|
||||
langs::setup(resdir, langs::current->getId(), contentPacks);
|
||||
loadAssets();
|
||||
@ -347,6 +345,11 @@ void Engine::resetContent() {
|
||||
resRoots.push_back({"core", pack.folder});
|
||||
load_configs(pack.folder);
|
||||
}
|
||||
auto manager = createPacksManager(fs::path());
|
||||
manager.scan();
|
||||
for (const auto& pack : manager.getAll(basePacks)) {
|
||||
resRoots.push_back({pack.id, pack.folder});
|
||||
}
|
||||
resPaths = std::make_unique<ResPaths>(resdir, resRoots);
|
||||
contentPacks.clear();
|
||||
content.reset();
|
||||
@ -355,8 +358,6 @@ void Engine::resetContent() {
|
||||
loadAssets();
|
||||
onAssetsLoaded();
|
||||
|
||||
auto manager = createPacksManager(fs::path());
|
||||
manager.scan();
|
||||
contentPacks = manager.getAll(basePacks);
|
||||
}
|
||||
|
||||
@ -413,6 +414,12 @@ const Content* Engine::getContent() const {
|
||||
return content.get();
|
||||
}
|
||||
|
||||
std::vector<ContentPack> Engine::getAllContentPacks() {
|
||||
auto packs = getContentPacks();
|
||||
packs.insert(packs.begin(), ContentPack::createCore(paths));
|
||||
return packs;
|
||||
}
|
||||
|
||||
std::vector<ContentPack>& Engine::getContentPacks() {
|
||||
return contentPacks;
|
||||
}
|
||||
|
||||
@ -130,6 +130,8 @@ public:
|
||||
/// @brief Get selected content packs
|
||||
std::vector<ContentPack>& getContentPacks();
|
||||
|
||||
std::vector<ContentPack> getAllContentPacks();
|
||||
|
||||
std::vector<std::string>& getBasePacks();
|
||||
|
||||
/// @brief Get current screen
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "coders/json.hpp"
|
||||
#include "coders/byte_utils.hpp"
|
||||
#include "coders/rle.hpp"
|
||||
#include "coders/binary_json.hpp"
|
||||
|
||||
@ -10,11 +10,15 @@
|
||||
#include <utility>
|
||||
|
||||
#include "WorldFiles.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
static debug::Logger logger("engine-paths");
|
||||
|
||||
static inline auto SCREENSHOTS_FOLDER = std::filesystem::u8path("screenshots");
|
||||
static inline auto CONTENT_FOLDER = std::filesystem::u8path("content");
|
||||
static inline auto WORLDS_FOLDER = std::filesystem::u8path("worlds");
|
||||
static inline auto CONFIG_FOLDER = std::filesystem::u8path("config");
|
||||
static inline auto EXPORT_FOLDER = std::filesystem::u8path("export");
|
||||
static inline auto CONTROLS_FILE = std::filesystem::u8path("controls.toml");
|
||||
static inline auto SETTINGS_FILE = std::filesystem::u8path("settings.toml");
|
||||
|
||||
@ -48,6 +52,10 @@ void EnginePaths::prepare() {
|
||||
if (!fs::is_directory(contentFolder)) {
|
||||
fs::create_directories(contentFolder);
|
||||
}
|
||||
auto exportFolder = userFilesFolder / EXPORT_FOLDER;
|
||||
if (!fs::is_directory(exportFolder)) {
|
||||
fs::create_directories(exportFolder);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path EnginePaths::getUserFilesFolder() const {
|
||||
@ -153,15 +161,23 @@ void EnginePaths::setContentPacks(std::vector<ContentPack>* contentPacks) {
|
||||
this->contentPacks = contentPacks;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> EnginePaths::parsePath(std::string_view path) {
|
||||
size_t separator = path.find(':');
|
||||
if (separator == std::string::npos) {
|
||||
return {"", std::string(path)};
|
||||
}
|
||||
auto prefix = std::string(path.substr(0, separator));
|
||||
auto filename = std::string(path.substr(separator + 1));
|
||||
return {prefix, filename};
|
||||
}
|
||||
|
||||
std::filesystem::path EnginePaths::resolve(
|
||||
const std::string& path, bool throwErr
|
||||
) {
|
||||
size_t separator = path.find(':');
|
||||
if (separator == std::string::npos) {
|
||||
auto [prefix, filename] = EnginePaths::parsePath(path);
|
||||
if (prefix.empty()) {
|
||||
throw files_access_error("no entry point specified");
|
||||
}
|
||||
std::string prefix = path.substr(0, separator);
|
||||
std::string filename = path.substr(separator + 1);
|
||||
filename = toCanonic(fs::u8path(filename)).u8string();
|
||||
|
||||
if (prefix == "res" || prefix == "core") {
|
||||
@ -176,6 +192,9 @@ std::filesystem::path EnginePaths::resolve(
|
||||
if (prefix == "world") {
|
||||
return currentWorldFolder / fs::u8path(filename);
|
||||
}
|
||||
if (prefix == "export") {
|
||||
return userFilesFolder / EXPORT_FOLDER / fs::u8path(filename);
|
||||
}
|
||||
|
||||
if (contentPacks) {
|
||||
for (auto& pack : *contentPacks) {
|
||||
@ -244,6 +263,56 @@ std::vector<std::filesystem::path> ResPaths::listdir(
|
||||
return entries;
|
||||
}
|
||||
|
||||
dv::value ResPaths::readCombinedList(const std::string& filename) const {
|
||||
dv::value list = dv::list();
|
||||
for (const auto& root : roots) {
|
||||
auto path = root.path / fs::u8path(filename);
|
||||
if (!fs::exists(path)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
auto value = files::read_object(path);
|
||||
if (!value.isList()) {
|
||||
logger.warning() << "reading combined list " << root.name << ":"
|
||||
<< filename << " is not a list (skipped)";
|
||||
continue;
|
||||
}
|
||||
for (const auto& elem : value) {
|
||||
list.add(elem);
|
||||
}
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.warning() << "reading combined list " << root.name << ":"
|
||||
<< filename << ": " << err.what();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
dv::value ResPaths::readCombinedObject(const std::string& filename) const {
|
||||
dv::value object = dv::object();
|
||||
for (const auto& root : roots) {
|
||||
auto path = root.path / fs::u8path(filename);
|
||||
if (!fs::exists(path)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
auto value = files::read_object(path);
|
||||
if (!value.isObject()) {
|
||||
logger.warning()
|
||||
<< "reading combined object " << root.name << ": "
|
||||
<< filename << " is not an object (skipped)";
|
||||
}
|
||||
for (const auto& [key, element] : value.asObject()) {
|
||||
object[key] = element;
|
||||
}
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.warning() << "reading combined object " << root.name << ":"
|
||||
<< filename << ": " << err.what();
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
const std::filesystem::path& ResPaths::getMainRoot() const {
|
||||
return mainRoot;
|
||||
}
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include "data/dv.hpp"
|
||||
#include "content/ContentPack.hpp"
|
||||
|
||||
|
||||
@ -41,6 +43,10 @@ public:
|
||||
|
||||
std::filesystem::path resolve(const std::string& path, bool throwErr = true);
|
||||
|
||||
static std::tuple<std::string, std::string> parsePath(std::string_view view);
|
||||
|
||||
static inline auto CONFIG_DEFAULTS =
|
||||
std::filesystem::u8path("config/defaults.toml");
|
||||
private:
|
||||
std::filesystem::path userFilesFolder {"."};
|
||||
std::filesystem::path resourcesFolder {"res"};
|
||||
@ -62,6 +68,13 @@ public:
|
||||
std::vector<std::filesystem::path> listdir(const std::string& folder) const;
|
||||
std::vector<std::string> listdirRaw(const std::string& folder) const;
|
||||
|
||||
/// @brief Read all found list versions from all packs and combine into a
|
||||
/// single list. Invalid versions will be skipped with logging a warning
|
||||
/// @param file *.json file path relative to entry point
|
||||
dv::value readCombinedList(const std::string& file) const;
|
||||
|
||||
dv::value readCombinedObject(const std::string& file) const;
|
||||
|
||||
const std::filesystem::path& getMainRoot() const;
|
||||
|
||||
private:
|
||||
|
||||
@ -165,3 +165,36 @@ std::vector<std::string> files::read_list(const fs::path& filename) {
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "coders/json.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
|
||||
using DecodeFunc = dv::value(*)(std::string_view, std::string_view);
|
||||
|
||||
static std::map<fs::path, DecodeFunc> data_decoders {
|
||||
{fs::u8path(".json"), json::parse},
|
||||
{fs::u8path(".toml"), toml::parse},
|
||||
};
|
||||
|
||||
bool files::is_data_file(const fs::path& file) {
|
||||
return is_data_interchange_format(file.extension());
|
||||
}
|
||||
|
||||
bool files::is_data_interchange_format(const fs::path& ext) {
|
||||
return data_decoders.find(ext) != data_decoders.end();
|
||||
}
|
||||
|
||||
dv::value files::read_object(const fs::path& file) {
|
||||
const auto& found = data_decoders.find(file.extension());
|
||||
if (found == data_decoders.end()) {
|
||||
throw std::runtime_error("unknown file format");
|
||||
}
|
||||
auto text = read_string(file);
|
||||
try {
|
||||
return found->second(file.u8string(), text);
|
||||
} catch (const parsing_error& err) {
|
||||
throw std::runtime_error(err.errorLog());
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +65,16 @@ namespace files {
|
||||
/// @brief Read JSON or BJSON file
|
||||
/// @param file *.json or *.bjson file
|
||||
dv::value read_json(const fs::path& file);
|
||||
|
||||
dv::value read_binary_json(const fs::path& file);
|
||||
|
||||
/// @brief Read TOML file
|
||||
/// @param file *.toml file
|
||||
dv::value read_toml(const fs::path& file);
|
||||
|
||||
std::vector<std::string> read_list(const fs::path& file);
|
||||
|
||||
bool is_data_file(const fs::path& file);
|
||||
bool is_data_interchange_format(const fs::path& ext);
|
||||
dv::value read_object(const fs::path& file);
|
||||
}
|
||||
|
||||
16
src/files/util.hpp
Normal file
16
src/files/util.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace files {
|
||||
inline bool is_valid_name(std::string_view name) {
|
||||
static std::string illegalChars = "\\/%?!<>:; ";
|
||||
for (char c : illegalChars) {
|
||||
if (name.find(c) != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !name.empty();
|
||||
}
|
||||
}
|
||||
@ -84,7 +84,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
return L"frustum-culling: "+std::wstring(culling ? L"on" : L"off");
|
||||
}));
|
||||
panel->add(create_label([=]() {
|
||||
return L"chunks: "+std::to_wstring(level->chunks->chunksCount)+
|
||||
return L"chunks: "+std::to_wstring(level->chunks->getChunksCount())+
|
||||
L" visible: "+std::to_wstring(level->chunks->visible);
|
||||
}));
|
||||
panel->add(create_label([=]() {
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "graphics/core/Mesh.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
#include "graphics/render/WorldRenderer.hpp"
|
||||
#include "graphics/ui/elements/InventoryView.hpp"
|
||||
#include "graphics/ui/elements/Menu.hpp"
|
||||
@ -29,6 +30,8 @@
|
||||
#include "items/Inventory.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "world/generator/WorldGenerator.hpp"
|
||||
#include "maths/voxmaths.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
@ -37,6 +40,7 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/ChunksStorage.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "window/Events.hpp"
|
||||
#include "window/input.hpp"
|
||||
@ -140,12 +144,16 @@ std::shared_ptr<InventoryView> Hud::createHotbar() {
|
||||
return view;
|
||||
}
|
||||
|
||||
Hud::Hud(Engine* engine, LevelFrontend* frontend, Player* player)
|
||||
: assets(engine->getAssets()),
|
||||
gui(engine->getGUI()),
|
||||
frontend(frontend),
|
||||
player(player)
|
||||
{
|
||||
static constexpr uint WORLDGEN_IMG_SIZE = 128U;
|
||||
|
||||
Hud::Hud(Engine* engine, LevelFrontend* frontend, Player* player)
|
||||
: assets(engine->getAssets()),
|
||||
gui(engine->getGUI()),
|
||||
frontend(frontend),
|
||||
player(player),
|
||||
debugImgWorldGen(std::make_unique<ImageData>(
|
||||
ImageFormat::rgba8888, WORLDGEN_IMG_SIZE, WORLDGEN_IMG_SIZE
|
||||
)) {
|
||||
contentAccess = createContentAccess();
|
||||
contentAccess->setId("hud.content-access");
|
||||
contentAccessPanel = std::make_shared<Panel>(
|
||||
@ -177,6 +185,14 @@ Hud::Hud(Engine* engine, LevelFrontend* frontend, Player* player)
|
||||
dplotter->setGravity(Gravity::bottom_right);
|
||||
dplotter->setInteractive(false);
|
||||
add(HudElement(hud_element_mode::permanent, nullptr, dplotter, true));
|
||||
|
||||
assets->store(Texture::from(debugImgWorldGen.get()), DEBUG_WORLDGEN_IMAGE);
|
||||
|
||||
add(HudElement(hud_element_mode::permanent, nullptr,
|
||||
guiutil::create(
|
||||
"<image src='"+DEBUG_WORLDGEN_IMAGE+
|
||||
"' pos='0' size='256' gravity='top-right' margin='0,20,0,0'/>"
|
||||
), true));
|
||||
}
|
||||
|
||||
Hud::~Hud() {
|
||||
@ -250,6 +266,53 @@ void Hud::updateHotbarControl() {
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::updateWorldGenDebugVisualization() {
|
||||
auto level = frontend->getLevel();
|
||||
auto generator =
|
||||
frontend->getController()->getChunksController()->getGenerator();
|
||||
auto debugInfo = generator->createDebugInfo();
|
||||
|
||||
int width = debugImgWorldGen->getWidth();
|
||||
int height = debugImgWorldGen->getHeight();
|
||||
ubyte* data = debugImgWorldGen->getData();
|
||||
|
||||
int ox = debugInfo.areaOffsetX;
|
||||
int oz = debugInfo.areaOffsetY;
|
||||
|
||||
int areaWidth = debugInfo.areaWidth;
|
||||
int areaHeight = debugInfo.areaHeight;
|
||||
|
||||
for (int z = 0; z < height; z++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int cx = x + ox;
|
||||
int cz = z + oz;
|
||||
|
||||
int ax = x - (width - areaWidth) / 2;
|
||||
int az = z - (height - areaHeight) / 2;
|
||||
|
||||
data[(z * width + x) * 4 + 1] =
|
||||
level->chunks->getChunk(ax + ox, az + oz) ? 255 : 0;
|
||||
data[(z * width + x) * 4 + 0] =
|
||||
level->chunksStorage->get(ax + ox, az + oz) ? 255 : 0;
|
||||
|
||||
if (ax < 0 || az < 0 ||
|
||||
ax >= areaWidth || az >= areaHeight) {
|
||||
data[(z * width + x) * 4 + 2] = 0;
|
||||
data[(z * width + x) * 4 + 3] = 0;
|
||||
data[(z * width + x) * 4 + 3] = 100;
|
||||
continue;
|
||||
}
|
||||
auto value = debugInfo.areaLevels[az * areaWidth + ax] * 25;
|
||||
|
||||
// Chunk is already generated
|
||||
data[(z * width + x) * 4 + 2] = value;
|
||||
data[(z * width + x) * 4 + 3] = 150;
|
||||
}
|
||||
}
|
||||
auto texture = assets->get<Texture>(DEBUG_WORLDGEN_IMAGE);
|
||||
texture->reload(*debugImgWorldGen);
|
||||
}
|
||||
|
||||
void Hud::update(bool visible) {
|
||||
auto level = frontend->getLevel();
|
||||
auto menu = gui->getMenu();
|
||||
@ -296,6 +359,10 @@ void Hud::update(bool visible) {
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
|
||||
if (player->debug) {
|
||||
updateWorldGenDebugVisualization();
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Show inventory on the screen and turn on inventory mode blocking movement
|
||||
|
||||
@ -18,6 +18,7 @@ class LevelFrontend;
|
||||
class UiDocument;
|
||||
class DrawContext;
|
||||
class Viewport;
|
||||
class ImageData;
|
||||
|
||||
namespace gui {
|
||||
class GUI;
|
||||
@ -107,6 +108,8 @@ class Hud : public util::ObjectsKeeper {
|
||||
|
||||
/// @brief UI element will be dynamicly positioned near to inventory or in screen center
|
||||
std::shared_ptr<gui::UINode> secondUI = nullptr;
|
||||
|
||||
std::unique_ptr<ImageData> debugImgWorldGen;
|
||||
|
||||
std::shared_ptr<gui::InventoryView> createContentAccess();
|
||||
std::shared_ptr<gui::InventoryView> createHotbar();
|
||||
@ -117,6 +120,7 @@ class Hud : public util::ObjectsKeeper {
|
||||
void cleanup();
|
||||
|
||||
void showExchangeSlot();
|
||||
void updateWorldGenDebugVisualization();
|
||||
public:
|
||||
Hud(Engine* engine, LevelFrontend* frontend, Player* player);
|
||||
~Hud();
|
||||
@ -167,4 +171,7 @@ public:
|
||||
Player* getPlayer() const;
|
||||
|
||||
std::shared_ptr<Inventory> getBlockInventory();
|
||||
|
||||
/// @brief Runtime updating debug visualization texture
|
||||
inline static std::string DEBUG_WORLDGEN_IMAGE = "#debug.img.worldgen";
|
||||
};
|
||||
|
||||
@ -41,7 +41,13 @@ void GLTexture::unbind() {
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void GLTexture::reload(const ubyte* data){
|
||||
void GLTexture::reload(const ImageData& image) {
|
||||
width = image.getWidth();
|
||||
height = image.getHeight();
|
||||
reload(image.getData());
|
||||
}
|
||||
|
||||
void GLTexture::reload(const ubyte* data) {
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
|
||||
|
||||
@ -16,6 +16,8 @@ public:
|
||||
|
||||
void setNearestFilter();
|
||||
|
||||
virtual void reload(const ImageData& image) override;
|
||||
|
||||
virtual std::unique_ptr<ImageData> readData() override;
|
||||
virtual uint getId() const override;
|
||||
|
||||
|
||||
@ -20,6 +20,8 @@ public:
|
||||
virtual void bind() = 0;
|
||||
virtual void unbind() = 0;
|
||||
|
||||
virtual void reload(const ImageData& image) = 0;
|
||||
|
||||
virtual std::unique_ptr<ImageData> readData() = 0;
|
||||
|
||||
virtual uint getWidth() const {
|
||||
|
||||
@ -80,7 +80,7 @@ WorldRenderer::~WorldRenderer() = default;
|
||||
bool WorldRenderer::drawChunk(
|
||||
size_t index, Camera* camera, Shader* shader, bool culling
|
||||
) {
|
||||
auto chunk = level->chunks->chunks[index];
|
||||
auto chunk = level->chunks->getChunks()[index];
|
||||
if (!chunk->flags.lighted) {
|
||||
return false;
|
||||
}
|
||||
@ -123,15 +123,16 @@ void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) {
|
||||
// [warning] this whole method is not thread-safe for chunks
|
||||
|
||||
std::vector<size_t> indices;
|
||||
for (size_t i = 0; i < chunks->volume; i++) {
|
||||
if (chunks->chunks[i] == nullptr) continue;
|
||||
for (size_t i = 0; i < chunks->getVolume(); i++) {
|
||||
if (chunks->getChunks()[i] == nullptr) continue;
|
||||
indices.emplace_back(i);
|
||||
}
|
||||
float px = camera->position.x / static_cast<float>(CHUNK_W) - 0.5f;
|
||||
float pz = camera->position.z / static_cast<float>(CHUNK_D) - 0.5f;
|
||||
std::sort(indices.begin(), indices.end(), [chunks, px, pz](auto i, auto j) {
|
||||
const auto a = chunks->chunks[i].get();
|
||||
const auto b = chunks->chunks[j].get();
|
||||
const auto& chunksBuffer = chunks->getChunks();
|
||||
const auto a = chunksBuffer[i].get();
|
||||
const auto b = chunksBuffer[j].get();
|
||||
auto adx = (a->x - px);
|
||||
auto adz = (a->z - pz);
|
||||
auto bdx = (b->x - px);
|
||||
|
||||
@ -15,7 +15,7 @@ using inventories_map = std::unordered_map<int64_t, std::shared_ptr<Inventory>>;
|
||||
class Inventories {
|
||||
Level& level;
|
||||
inventories_map map;
|
||||
PseudoRandom random;
|
||||
util::PseudoRandom random;
|
||||
public:
|
||||
Inventories(Level& level);
|
||||
~Inventories();
|
||||
|
||||
@ -91,17 +91,11 @@ void Inventory::convert(const ContentReport* report) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
void Inventory::convert(dv::value& data, const ContentReport* report) {
|
||||
auto& slotsarr = data["slots"];
|
||||
for (auto& item : data["slots"]) {
|
||||
itemid_t id = item["id"].asInteger(ITEM_EMPTY);
|
||||
itemid_t replacement = report->items.getId(id);
|
||||
item["id"] = replacement;
|
||||
if (replacement == 0 && item.has("count")) {
|
||||
item.erase("count");
|
||||
}
|
||||
}
|
||||
Inventory inventory;
|
||||
inventory.deserialize(data);
|
||||
inventory.convert(report);
|
||||
data = inventory.serialize();
|
||||
}
|
||||
|
||||
const size_t Inventory::npos = -1;
|
||||
|
||||
@ -14,6 +14,8 @@ class Inventory : public Serializable {
|
||||
int64_t id;
|
||||
std::vector<ItemStack> slots;
|
||||
public:
|
||||
Inventory() = default;
|
||||
|
||||
Inventory(int64_t id, size_t size);
|
||||
|
||||
Inventory(const Inventory& orig);
|
||||
|
||||
@ -23,8 +23,9 @@ Lighting::Lighting(const Content* content, Chunks* chunks)
|
||||
Lighting::~Lighting() = default;
|
||||
|
||||
void Lighting::clear(){
|
||||
for (size_t index = 0; index < chunks->volume; index++){
|
||||
auto chunk = chunks->chunks[index];
|
||||
const auto& chunks = this->chunks->getChunks();
|
||||
for (size_t index = 0; index < chunks.size(); index++){
|
||||
auto chunk = chunks[index];
|
||||
if (chunk == nullptr)
|
||||
continue;
|
||||
Lightmap& lightmap = chunk->lightmap;
|
||||
|
||||
@ -124,17 +124,17 @@ void BlocksController::randomTick(
|
||||
|
||||
void BlocksController::randomTick(int tickid, int parts) {
|
||||
auto indices = level->content->getIndices();
|
||||
const int w = chunks->w;
|
||||
const int d = chunks->d;
|
||||
int width = chunks->getWidth();
|
||||
int height = chunks->getHeight();
|
||||
int segments = 4;
|
||||
|
||||
for (uint z = padding; z < d - padding; z++) {
|
||||
for (uint x = padding; x < w - padding; x++) {
|
||||
int index = z * w + x;
|
||||
for (uint z = padding; z < height - padding; z++) {
|
||||
for (uint x = padding; x < width - padding; x++) {
|
||||
int index = z * width + x;
|
||||
if ((index + tickid) % parts != 0) {
|
||||
continue;
|
||||
}
|
||||
auto& chunk = chunks->chunks[index];
|
||||
auto& chunk = chunks->getChunks()[index];
|
||||
if (chunk == nullptr || !chunk->flags.lighted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -15,10 +15,9 @@
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/ChunksStorage.hpp"
|
||||
#include "voxels/WorldGenerator.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "world/WorldGenerators.hpp"
|
||||
#include "world/generator/WorldGenerator.hpp"
|
||||
|
||||
const uint MAX_WORK_PER_FRAME = 128;
|
||||
const uint MIN_SURROUNDING = 9;
|
||||
@ -28,14 +27,19 @@ ChunksController::ChunksController(Level* level, uint padding)
|
||||
chunks(level->chunks.get()),
|
||||
lighting(level->lighting.get()),
|
||||
padding(padding),
|
||||
generator(WorldGenerators::createGenerator(
|
||||
level->getWorld()->getGenerator(), level->content
|
||||
)) {
|
||||
}
|
||||
generator(std::make_unique<WorldGenerator>(
|
||||
level->content->generators.require(level->getWorld()->getGenerator()),
|
||||
level->content,
|
||||
level->getWorld()->getSeed()
|
||||
)) {}
|
||||
|
||||
ChunksController::~ChunksController() = default;
|
||||
|
||||
void ChunksController::update(int64_t maxDuration) {
|
||||
void ChunksController::update(
|
||||
int64_t maxDuration, int loadDistance, int centerX, int centerY
|
||||
) {
|
||||
generator->update(centerX, centerY, loadDistance);
|
||||
|
||||
int64_t mcstotal = 0;
|
||||
|
||||
for (uint i = 0; i < MAX_WORK_PER_FRAME; i++) {
|
||||
@ -52,16 +56,17 @@ void ChunksController::update(int64_t maxDuration) {
|
||||
}
|
||||
|
||||
bool ChunksController::loadVisible() {
|
||||
const int w = chunks->w;
|
||||
const int d = chunks->d;
|
||||
int sizeX = chunks->getWidth();
|
||||
int sizeY = chunks->getHeight();
|
||||
|
||||
int nearX = 0;
|
||||
int nearZ = 0;
|
||||
int minDistance = ((w - padding * 2) / 2) * ((w - padding * 2) / 2);
|
||||
for (uint z = padding; z < d - padding; z++) {
|
||||
for (uint x = padding; x < w - padding; x++) {
|
||||
int index = z * w + x;
|
||||
auto& chunk = chunks->chunks[index];
|
||||
bool assigned = false;
|
||||
int minDistance = ((sizeX - padding * 2) / 2) * ((sizeY - padding * 2) / 2);
|
||||
for (uint z = padding; z < sizeY - padding; z++) {
|
||||
for (uint x = padding; x < sizeX - padding; x++) {
|
||||
int index = z * sizeX + x;
|
||||
auto& chunk = chunks->getChunks()[index];
|
||||
if (chunk != nullptr) {
|
||||
if (chunk->flags.loaded && !chunk->flags.lighted) {
|
||||
if (buildLights(chunk)) {
|
||||
@ -70,25 +75,25 @@ bool ChunksController::loadVisible() {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int lx = x - w / 2;
|
||||
int lz = z - d / 2;
|
||||
int lx = x - sizeX / 2;
|
||||
int lz = z - sizeY / 2;
|
||||
int distance = (lx * lx + lz * lz);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearX = x;
|
||||
nearZ = z;
|
||||
assigned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& chunk = chunks->chunks[nearZ * w + nearX];
|
||||
if (chunk != nullptr) {
|
||||
const auto& chunk = chunks->getChunks()[nearZ * sizeX + nearX];
|
||||
if (chunk != nullptr || !assigned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int ox = chunks->ox;
|
||||
const int oz = chunks->oz;
|
||||
createChunk(nearX + ox, nearZ + oz);
|
||||
int offsetX = chunks->getOffsetX();
|
||||
int offsetY = chunks->getOffsetY();
|
||||
createChunk(nearX + offsetX, nearZ + offsetY);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -117,7 +122,7 @@ void ChunksController::createChunk(int x, int z) {
|
||||
auto& chunkFlags = chunk->flags;
|
||||
|
||||
if (!chunkFlags.loaded) {
|
||||
generator->generate(chunk->voxels, x, z, level->getWorld()->getSeed());
|
||||
generator->generate(chunk->voxels, x, z);
|
||||
chunkFlags.unsaved = true;
|
||||
}
|
||||
chunk->updateHeights();
|
||||
|
||||
@ -28,5 +28,13 @@ public:
|
||||
~ChunksController();
|
||||
|
||||
/// @param maxDuration milliseconds reserved for chunks loading
|
||||
void update(int64_t maxDuration);
|
||||
void update(
|
||||
int64_t maxDuration,
|
||||
int loadDistance,
|
||||
int centerX,
|
||||
int centerY);
|
||||
|
||||
const WorldGenerator* getGenerator() const {
|
||||
return generator.get();
|
||||
}
|
||||
};
|
||||
|
||||
@ -39,6 +39,7 @@ class CommandParser : BasicParser {
|
||||
{"int", ArgType::integer},
|
||||
{"str", ArgType::string},
|
||||
{"sel", ArgType::selector},
|
||||
{"bool", ArgType::boolean},
|
||||
{"enum", ArgType::enumvalue},
|
||||
};
|
||||
public:
|
||||
@ -250,6 +251,11 @@ public:
|
||||
return selectorCheck(arg, value);
|
||||
case ArgType::integer:
|
||||
return typeCheck(arg, dv::value_type::integer, value, "integer");
|
||||
case ArgType::boolean:
|
||||
if (!arg->optional) {
|
||||
throw typeError(arg->name, "boolean", value);
|
||||
}
|
||||
return value.isBoolean();
|
||||
case ArgType::string:
|
||||
if (!value.isString()) {
|
||||
return !arg->optional;
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
#include "data/dv.hpp"
|
||||
|
||||
namespace cmd {
|
||||
enum class ArgType { number, integer, enumvalue, selector, string };
|
||||
enum class ArgType { number, integer, enumvalue, selector, boolean, string };
|
||||
|
||||
inline std::string argtype_name(ArgType type) {
|
||||
switch (type) {
|
||||
@ -20,6 +20,8 @@ namespace cmd {
|
||||
return "enumeration";
|
||||
case ArgType::selector:
|
||||
return "selector";
|
||||
case ArgType::boolean:
|
||||
return "boolean";
|
||||
case ArgType::string:
|
||||
return "string";
|
||||
default:
|
||||
|
||||
@ -124,7 +124,7 @@ static void show_content_missing(
|
||||
auto root = dv::object();
|
||||
auto& contentEntries = root.list("content");
|
||||
for (auto& entry : report->getMissingContent()) {
|
||||
std::string contentName = contenttype_name(entry.type);
|
||||
std::string contentName = ContentType_name(entry.type);
|
||||
auto& contentEntry = contentEntries.object();
|
||||
contentEntry["type"] = contentName;
|
||||
contentEntry["name"] = entry.name;
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "settings.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "maths/voxmaths.hpp"
|
||||
#include "scripting/scripting.hpp"
|
||||
|
||||
static debug::Logger logger("level-control");
|
||||
@ -38,7 +39,10 @@ void LevelController::update(float delta, bool input, bool pause) {
|
||||
position.z,
|
||||
settings.chunks.loadDistance.get() + settings.chunks.padding.get() * 2
|
||||
);
|
||||
chunks->update(settings.chunks.loadSpeed.get());
|
||||
chunks->update(
|
||||
settings.chunks.loadSpeed.get(), settings.chunks.loadDistance.get(),
|
||||
floordiv(position.x, CHUNK_W), floordiv(position.z, CHUNK_D)
|
||||
);
|
||||
|
||||
if (!pause) {
|
||||
// update all objects that needed
|
||||
@ -91,6 +95,10 @@ BlocksController* LevelController::getBlocksController() {
|
||||
return blocks.get();
|
||||
}
|
||||
|
||||
ChunksController* LevelController::getChunksController() {
|
||||
return chunks.get();
|
||||
}
|
||||
|
||||
PlayerController* LevelController::getPlayerController() {
|
||||
return player.get();
|
||||
}
|
||||
|
||||
@ -34,5 +34,6 @@ public:
|
||||
Player* getPlayer();
|
||||
|
||||
BlocksController* getBlocksController();
|
||||
ChunksController* getChunksController();
|
||||
PlayerController* getPlayerController();
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
#include "lua_util.hpp"
|
||||
#include "../lua_util.hpp"
|
||||
|
||||
/// Definitions can be found in local .cpp files
|
||||
/// having same names as declarations
|
||||
@ -30,6 +30,7 @@ extern const luaL_Reg jsonlib[];
|
||||
extern const luaL_Reg mat4lib[];
|
||||
extern const luaL_Reg packlib[];
|
||||
extern const luaL_Reg playerlib[];
|
||||
extern const luaL_Reg generationlib[];
|
||||
extern const luaL_Reg quatlib[]; // quat.cpp
|
||||
extern const luaL_Reg timelib[];
|
||||
extern const luaL_Reg tomllib[];
|
||||
@ -281,7 +281,13 @@ static int l_get_model(lua::State* L) {
|
||||
|
||||
static int l_get_hitbox(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
auto& hitbox = def->rt.hitboxes[lua::tointeger(L, 2)].at(0);
|
||||
size_t rotation = lua::tointeger(L, 2);
|
||||
if (def->rotatable) {
|
||||
rotation %= def->rotations.MAX_COUNT;
|
||||
} else {
|
||||
rotation = 0;
|
||||
}
|
||||
auto& hitbox = def->rt.hitboxes[rotation].at(0);
|
||||
lua::createtable(L, 2, 0);
|
||||
|
||||
lua::pushvec3(L, hitbox.min());
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "engine.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "files/engine_paths.hpp"
|
||||
#include "files/settings_io.hpp"
|
||||
#include "frontend/menu.hpp"
|
||||
@ -11,8 +12,10 @@
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "window/Events.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/generator/WorldGenerator.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/WorldGenerators.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
|
||||
#include "api_lua.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
@ -170,27 +173,6 @@ static int l_quit(lua::State*) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// @brief Get the default world generator
|
||||
/// @return The ID of the default world generator
|
||||
static int l_get_default_generator(lua::State* L) {
|
||||
return lua::pushstring(L, WorldGenerators::getDefaultGeneratorID());
|
||||
}
|
||||
|
||||
/// @brief Get a list of all world generators
|
||||
/// @return A table with the IDs of all world generators
|
||||
static int l_get_generators(lua::State* L) {
|
||||
const auto& generators = WorldGenerators::getGeneratorsIDs();
|
||||
lua::createtable(L, generators.size(), 0);
|
||||
|
||||
int i = 0;
|
||||
for (auto& id : generators) {
|
||||
lua::pushstring(L, id);
|
||||
lua::rawseti(L, i + 1);
|
||||
i++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg corelib[] = {
|
||||
{"new_world", lua::wrap<l_new_world>},
|
||||
{"open_world", lua::wrap<l_open_world>},
|
||||
@ -203,6 +185,4 @@ const luaL_Reg corelib[] = {
|
||||
{"str_setting", lua::wrap<l_str_setting>},
|
||||
{"get_setting_info", lua::wrap<l_get_setting_info>},
|
||||
{"quit", lua::wrap<l_quit>},
|
||||
{"get_default_generator", lua::wrap<l_get_default_generator>},
|
||||
{"get_generators", lua::wrap<l_get_generators>},
|
||||
{NULL, NULL}};
|
||||
@ -158,7 +158,7 @@ static int l_file_write_bytes(lua::State* L) {
|
||||
|
||||
fs::path path = resolve_path(lua::require_string(L, pathIndex));
|
||||
|
||||
if (auto bytearray = lua::touserdata<lua::Bytearray>(L, -1)) {
|
||||
if (auto bytearray = lua::touserdata<lua::LuaBytearray>(L, -1)) {
|
||||
auto& bytes = bytearray->data();
|
||||
return lua::pushboolean(
|
||||
L, files::write_bytes(path, bytes.data(), bytes.size())
|
||||
@ -247,6 +247,14 @@ static int l_file_gzip_decompress(lua::State* L) {
|
||||
}
|
||||
}
|
||||
|
||||
static int l_file_read_combined_list(lua::State* L) {
|
||||
std::string path = lua::require_string(L, 1);
|
||||
if (path.find(':') != std::string::npos) {
|
||||
throw std::runtime_error("entry point must not be specified");
|
||||
}
|
||||
return lua::pushvalue(L, engine->getResPaths()->readCombinedList(path));
|
||||
}
|
||||
|
||||
const luaL_Reg filelib[] = {
|
||||
{"exists", lua::wrap<l_file_exists>},
|
||||
{"find", lua::wrap<l_file_find>},
|
||||
@ -265,4 +273,5 @@ const luaL_Reg filelib[] = {
|
||||
{"write", lua::wrap<l_file_write>},
|
||||
{"gzip_compress", lua::wrap<l_file_gzip_compress>},
|
||||
{"gzip_decompress", lua::wrap<l_file_gzip_decompress>},
|
||||
{"read_combined_list", lua::wrap<l_file_read_combined_list>},
|
||||
{NULL, NULL}};
|
||||
85
src/logic/scripting/lua/libs/libgeneration.cpp
Normal file
85
src/logic/scripting/lua/libs/libgeneration.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "api_lua.hpp"
|
||||
|
||||
#include "files/files.hpp"
|
||||
#include "files/util.hpp"
|
||||
#include "coders/binary_json.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/generator/VoxelFragment.hpp"
|
||||
#include "content/ContentLoader.hpp"
|
||||
#include "engine.hpp"
|
||||
#include "../lua_custom_types.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static int l_save_fragment(lua::State* L) {
|
||||
auto paths = engine->getPaths();
|
||||
auto fragment = lua::touserdata<lua::LuaVoxelFragment>(L, 1);
|
||||
auto file = paths->resolve(lua::require_string(L, 2), true);
|
||||
auto map = fragment->getFragment()->serialize();
|
||||
auto bytes = json::to_binary(map, true);
|
||||
files::write_bytes(file, bytes.data(), bytes.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_create_fragment(lua::State* L) {
|
||||
auto pointA = lua::tovec<3>(L, 1);
|
||||
auto pointB = lua::tovec<3>(L, 2);
|
||||
bool crop = lua::toboolean(L, 3);
|
||||
bool saveEntities = lua::toboolean(L, 4);
|
||||
|
||||
auto fragment =
|
||||
VoxelFragment::create(level, pointA, pointB, crop, saveEntities);
|
||||
return lua::newuserdata<lua::LuaVoxelFragment>(
|
||||
L, std::shared_ptr<VoxelFragment>(std::move(fragment))
|
||||
);
|
||||
}
|
||||
|
||||
static int l_load_fragment(lua::State* L) {
|
||||
auto paths = engine->getPaths();
|
||||
auto filename = lua::require_string(L, 1);
|
||||
auto path = paths->resolve(filename);
|
||||
if (!std::filesystem::exists(path)) {
|
||||
throw std::runtime_error("file "+path.u8string()+" does not exist");
|
||||
}
|
||||
auto map = files::read_binary_json(path);
|
||||
|
||||
auto fragment = std::make_shared<VoxelFragment>();
|
||||
fragment->deserialize(map);
|
||||
return lua::newuserdata<lua::LuaVoxelFragment>(L, std::move(fragment));
|
||||
}
|
||||
|
||||
/// @brief Get a list of all world generators
|
||||
/// @return A table with the IDs of all world generators
|
||||
static int l_get_generators(lua::State* L) {
|
||||
auto packs = engine->getAllContentPacks();
|
||||
|
||||
lua::createtable(L, 0, 0);
|
||||
|
||||
int i = 1;
|
||||
for (const auto& pack : packs) {
|
||||
auto pairs = ContentLoader::scanContent(pack, ContentType::GENERATOR);
|
||||
for (const auto& [name, caption] : pairs) {
|
||||
lua::pushstring(L, caption);
|
||||
lua::setfield(L, name);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// @brief Get the default world generator
|
||||
/// @return The ID of the default world generator
|
||||
static int l_get_default_generator(lua::State* L) {
|
||||
auto combined = engine->getResPaths()->readCombinedObject(
|
||||
EnginePaths::CONFIG_DEFAULTS.u8string()
|
||||
);
|
||||
return lua::pushstring(L, combined["generator"].asString());
|
||||
}
|
||||
|
||||
const luaL_Reg generationlib[] = {
|
||||
{"create_fragment", lua::wrap<l_create_fragment>},
|
||||
{"save_fragment", lua::wrap<l_save_fragment>},
|
||||
{"load_fragment", lua::wrap<l_load_fragment>},
|
||||
{"get_generators", lua::wrap<l_get_generators>},
|
||||
{"get_default_generator", lua::wrap<l_get_default_generator>},
|
||||
{NULL, NULL}};
|
||||
@ -77,7 +77,7 @@ static int l_mul(lua::State* L) {
|
||||
/// transformed copy of matrix mat4.<func>(matrix: float[16], vec: float[3],
|
||||
/// dst: float[16]) -> sets dst to transformed version of matrix
|
||||
template <glm::mat4 (*func)(const glm::mat4&, const glm::vec3&)>
|
||||
inline int l_transform_func(lua::State* L) {
|
||||
inline int l_binop_func(lua::State* L) {
|
||||
uint argc = lua::gettop(L);
|
||||
switch (argc) {
|
||||
case 1: {
|
||||
@ -272,9 +272,9 @@ static int l_tostring(lua::State* L) {
|
||||
const luaL_Reg mat4lib[] = {
|
||||
{"idt", lua::wrap<l_idt>},
|
||||
{"mul", lua::wrap<l_mul>},
|
||||
{"scale", lua::wrap<l_transform_func<glm::scale>>},
|
||||
{"scale", lua::wrap<l_binop_func<glm::scale>>},
|
||||
{"rotate", lua::wrap<l_rotate>},
|
||||
{"translate", lua::wrap<l_transform_func<glm::translate>>},
|
||||
{"translate", lua::wrap<l_binop_func<glm::translate>>},
|
||||
{"inverse", lua::wrap<l_inverse>},
|
||||
{"transpose", lua::wrap<l_transpose>},
|
||||
{"determinant", lua::wrap<l_determinant>},
|
||||
@ -16,11 +16,9 @@ using namespace scripting;
|
||||
|
||||
static int l_pack_get_folder(lua::State* L) {
|
||||
std::string packName = lua::tostring(L, 1);
|
||||
if (packName == "core") {
|
||||
auto folder = engine->getPaths()->getResourcesFolder().u8string() + "/";
|
||||
return lua::pushstring(L, folder);
|
||||
}
|
||||
for (auto& pack : engine->getContentPacks()) {
|
||||
auto packs = engine->getAllContentPacks();
|
||||
|
||||
for (auto& pack : packs) {
|
||||
if (pack.id == packName) {
|
||||
return lua::pushstring(L, pack.folder.u8string() + "/");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user