Mods update
|
@ -1,3 +1,8 @@
|
|||
-------------------------------------------------------------------------------
|
||||
Work in Progress 0.9 [03.04.2020]
|
||||
|
||||
Mods update
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Work in Progress 0.8 [19.03.2020]
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
origin https://github.com/minetest/minetest_game.git (fetch)
|
||||
* master 0a90bd8 [origin/master] Fix wield image for coral and kelp nodes (#2850)
|
||||
* master 42baede [origin/master] Add specific groups to different types of grass (#2852)
|
||||
Mod: minetest_game/mods/beds
|
||||
Mod: minetest_game/mods/binoculars
|
||||
Mod: minetest_game/mods/boats
|
||||
|
@ -73,11 +73,11 @@ origin https://github.com/minetest-mods/dynamic_liquid (fetch)
|
|||
Mod: environment/dynamic_liquid
|
||||
|
||||
origin https://notabug.org/tenplus1/farming (fetch)
|
||||
* master 2c75834 [origin/master] cornstarch added to gelatin group
|
||||
* master 113fac2 [origin/master] added soy sauce
|
||||
Mod: flora/farming
|
||||
|
||||
origin https://github.com/minetest-mods/i3.git (fetch)
|
||||
* main 756d4e2 [origin/main] Change listring behavior
|
||||
* main e910c20 [origin/main] Code cleaning
|
||||
Mod: gui/i3
|
||||
|
||||
origin https://repo.or.cz/minetest_hbarmor.git (fetch)
|
||||
|
@ -113,11 +113,11 @@ origin https://github.com/TheTermos/mobkit (fetch)
|
|||
Mod: lib_api/mobkit
|
||||
|
||||
origin https://notabug.org/tenplus1/mobs_redo (fetch)
|
||||
* master 774ce66 [origin/master] add 'ignore_invisibility' setting to mob definition
|
||||
* master 34b06df [origin/master] add ability to follow group: items
|
||||
Mod: lib_api/mobs_redo
|
||||
|
||||
origin https://github.com/appgurueu/modlib (fetch)
|
||||
* master 2005754 [origin/master] Add binary module to reduce code duplication
|
||||
* master fc683ce [origin/master] Bump version
|
||||
Mod: lib_api/modlib
|
||||
|
||||
origin git@github.com:runsy/rcbows.git (fetch)
|
||||
|
@ -129,7 +129,7 @@ origin git@github.com:minetest-mods/ts_workshop.git (fetch)
|
|||
Mod: lib_api/ts_workshop
|
||||
|
||||
origin https://github.com/Skandarella/Animal-World.git (fetch)
|
||||
* main af0555a [origin/main] Add files via upload
|
||||
* main a5bb0b5 [origin/main] Add files via upload
|
||||
Mod: mobs/mobs_mobs/Animal-World
|
||||
|
||||
origin https://github.com/hkzorman/advanced_npc.git (fetch)
|
||||
|
@ -137,7 +137,7 @@ origin https://github.com/hkzorman/advanced_npc.git (fetch)
|
|||
Mod: mobs/mobs_mobs/advanced_npc
|
||||
|
||||
origin https://github.com/FreeLikeGNU/goblins.git (fetch)
|
||||
* master a680780 [origin/master] fix issue #14
|
||||
* master eb9055b [origin/master] fixes for issue #15
|
||||
Mod: mobs/mobs_mobs/goblins
|
||||
|
||||
origin https://github.com/hkzorman/mg_villages_npc.git (fetch)
|
||||
|
@ -157,19 +157,19 @@ origin https://github.com/berengma/aerotest (fetch)
|
|||
Mod: mobs/mobs_mobkit/aerotest
|
||||
|
||||
origin https://github.com/runsy/petz (fetch)
|
||||
* master c3ae3ae [origin/master] Merge pull request #64 from NathanielFreeman/jackolantern
|
||||
* master 8fd8bd4 [origin/master] fixes
|
||||
Mod: mobs/mobs_mobkit/petz
|
||||
|
||||
origin https://github.com/berengma/water_life (fetch)
|
||||
* master 4166b7f [origin/master] Update README.md
|
||||
* master caf92f0 [origin/master] add more nil checks
|
||||
Mod: mobs/mobs_mobkit/water_life
|
||||
|
||||
origin https://github.com/minetest-mods/3d_armor (fetch)
|
||||
* master 583dfe7 [origin/master] Update to work with Unified Inventory since formspec v4 update (#45)
|
||||
* master e75af55 [origin/master] Fix translation nonsense in minetest.log
|
||||
Mod: player/3d_armor
|
||||
|
||||
origin https://github.com/appgurueu/character_anim (fetch)
|
||||
* master 34751e6 [origin/master] Fix global warnings if strictness is enabled
|
||||
* master 3117bfb [origin/master] Fix rotations for changing body pitch
|
||||
Mod: player/character_anim
|
||||
|
||||
origin https://github.com/minetest-mods/emote (fetch)
|
||||
|
@ -189,7 +189,7 @@ origin https://gitlab.com/4w/hunger_ng.git (fetch)
|
|||
Mod: player/hunger_ng
|
||||
|
||||
origin https://github.com/minetest-mods/skinsdb.git (fetch)
|
||||
* master da6905f [origin/master] Mention i3 support (#55)
|
||||
* master c53158d [origin/master] Update to work with Unified Inventory v2 i.e. the formspec v4 rewrite Requires UI "version-2" tag or commit a7556c50 or later and and Minetest v5.4.0 or later
|
||||
Mod: player/skinsdb
|
||||
|
||||
origin https://github.com/stujones11/wield3d.git (fetch)
|
||||
|
@ -201,7 +201,7 @@ origin https://github.com/minetest-mods/wielded_light.git (fetch)
|
|||
Mod: player/wielded_light
|
||||
|
||||
origin https://notabug.org/TenPlus1/bonemeal.git (fetch)
|
||||
* master 807388d [origin/master] add parsley and artichoke
|
||||
* master 05e211a [origin/master] can grow cactus and papyrus
|
||||
Mod: tools/bonemeal
|
||||
|
||||
origin git@gitlab.com:daretmavi/bucket-lite.git (fetch)
|
||||
|
|
|
@ -33,9 +33,9 @@ else
|
|||
minetest.register_node(":ethereal:banana", {
|
||||
description = S("Banana"),
|
||||
drawtype = "torchlike",
|
||||
tiles = {"banana_single.png"},
|
||||
inventory_image = "banana_single.png",
|
||||
wield_image = "banana_single.png",
|
||||
tiles = {"farming_banana_single.png"},
|
||||
inventory_image = "farming_banana_single.png",
|
||||
wield_image = "farming_banana_single.png",
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
walkable = false,
|
||||
|
@ -50,9 +50,9 @@ else
|
|||
|
||||
minetest.register_node(":ethereal:bananaleaves", {
|
||||
description = S("Banana Leaves"),
|
||||
tiles = {"banana_leaf.png"},
|
||||
inventory_image = "banana_leaf.png",
|
||||
wield_image = "banana_leaf.png",
|
||||
tiles = {"farming_banana_leaf.png"},
|
||||
inventory_image = "farming_banana_leaf.png",
|
||||
wield_image = "farming_banana_leaf.png",
|
||||
paramtype = "light",
|
||||
waving = 1,
|
||||
groups = {snappy = 3, leafdecay = 3, leaves = 1, flammable = 2},
|
||||
|
@ -148,8 +148,8 @@ if eth then
|
|||
else
|
||||
minetest.register_craftitem(":ethereal:strawberry", {
|
||||
description = S("Strawberry"),
|
||||
inventory_image = "strawberry.png",
|
||||
wield_image = "strawberry.png",
|
||||
inventory_image = "farming_strawberry.png",
|
||||
wield_image = "farming_strawberry.png",
|
||||
groups = {food_strawberry = 1, flammable = 2},
|
||||
on_use = minetest.item_eat(1)
|
||||
})
|
||||
|
|
|
@ -46,6 +46,7 @@ minetest.register_node("farming:garlic_braid", {
|
|||
inventory_image = "crops_garlic_braid.png",
|
||||
wield_image = "crops_garlic_braid.png",
|
||||
drawtype = "nodebox",
|
||||
use_texture_alpha = "clip",
|
||||
paramtype = "light",
|
||||
paramtype2 = "facedir",
|
||||
tiles = {
|
||||
|
|
|
@ -24,6 +24,38 @@ minetest.register_craft({
|
|||
recipe = {"farming:soy_pod"}
|
||||
})
|
||||
|
||||
-- soy sauce
|
||||
minetest.register_node("farming:soy_sauce", {
|
||||
description = S("Soy Sauce"),
|
||||
drawtype = "plantlike",
|
||||
tiles = {"farming_soy_sauce.png"},
|
||||
inventory_image = "farming_soy_sauce.png",
|
||||
wield_image = "farming_soy_sauce.png",
|
||||
paramtype = "light",
|
||||
walkable = false,
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-0.3, -0.5, -0.3, 0.3, 0.5, 0.3}
|
||||
},
|
||||
groups = {
|
||||
vessel = 1, food_soy_sauce = 1, dig_immediate = 3, attached_node = 1
|
||||
},
|
||||
sounds = default.node_sound_glass_defaults()
|
||||
})
|
||||
|
||||
minetest.register_craft( {
|
||||
type = "shapeless",
|
||||
output = "farming:soy_sauce",
|
||||
recipe = {
|
||||
"group:food_soy", "group:food_soy", "group:food_juicer",
|
||||
"bucket:bucket_river_water", "vessels:glass_bottle", "group:food_salt"
|
||||
},
|
||||
replacements = {
|
||||
{"bucket:bucket_river_water", "bucket:bucket_empty"},
|
||||
{"group:food_juicer", "farming:juicer"}
|
||||
}
|
||||
})
|
||||
|
||||
-- soy milk
|
||||
minetest.register_node("farming:soy_milk", {
|
||||
description = S("Soy Milk"),
|
||||
|
|
|
@ -556,6 +556,7 @@ minetest.register_craft({
|
|||
"group:food_egg", "group:food_egg", "farming:vanilla_extract"
|
||||
},
|
||||
replacements = {
|
||||
{"cucina_vegana:soy_milk", "vessels:drinking_glass"},
|
||||
{"group:food_milk", "bucket:bucket_empty"},
|
||||
{"farming:vanilla_extract", "vessels:glass_bottle"}
|
||||
}
|
||||
|
@ -599,3 +600,42 @@ minetest.register_craft({
|
|||
{"farming:pot", "farming:pot"},
|
||||
}
|
||||
})
|
||||
|
||||
-- Onigiri
|
||||
|
||||
minetest.register_craftitem("farming:onigiri", {
|
||||
description = S("Onirigi"),
|
||||
inventory_image = "farming_onigiri.png",
|
||||
on_use = minetest.item_eat(2),
|
||||
groups = {flammable = 2},
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "farming:onigiri",
|
||||
recipe = {
|
||||
"group:food_rice", "group:food_rice", "group:food_seaweed", "group:food_salt"
|
||||
}
|
||||
})
|
||||
|
||||
-- Gyoza
|
||||
|
||||
minetest.register_craftitem("farming:gyoza", {
|
||||
description = S("Gyoza"),
|
||||
inventory_image = "farming_gyoza.png",
|
||||
on_use = minetest.item_eat(4),
|
||||
groups = {flammable = 2},
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "farming:gyoza 4",
|
||||
recipe = {
|
||||
"group:food_cabbage", "group:food_garlic_clove", "group:food_onion",
|
||||
"group:food_meat_raw", "group:food_salt", "group:food_skillet",
|
||||
"group:food_flour"
|
||||
},
|
||||
replacements = {
|
||||
{"group:food_skillet", "farming:skillet"}
|
||||
}
|
||||
})
|
||||
|
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 421 B |
Before Width: | Height: | Size: 200 B After Width: | Height: | Size: 200 B |
After Width: | Height: | Size: 603 B |
After Width: | Height: | Size: 204 B |
After Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 382 B |
|
@ -68,8 +68,8 @@ local sprintf, find, gmatch, match, sub, split, upper, lower =
|
|||
|
||||
local min, max, floor, ceil, abs = math.min, math.max, math.floor, math.ceil, math.abs
|
||||
|
||||
local pairs, ipairs, next, type, setmetatable, tonum, unpack =
|
||||
pairs, ipairs, next, type, setmetatable, tonumber, unpack
|
||||
local pairs, ipairs, next, type, setmetatable, tonum, unpack, select =
|
||||
pairs, ipairs, next, type, setmetatable, tonumber, unpack, select
|
||||
|
||||
local vec_add, vec_mul = vector.add, vector.multiply
|
||||
|
||||
|
@ -99,6 +99,7 @@ local BAG_SIZES = {
|
|||
local PNG = {
|
||||
bg = "i3_bg.png",
|
||||
bg_full = "i3_bg_full.png",
|
||||
bar = "i3_bar.png",
|
||||
hotbar = "i3_hotbar.png",
|
||||
search = "i3_search.png",
|
||||
heart = "i3_heart.png",
|
||||
|
@ -315,7 +316,7 @@ local function fmt(elem, ...)
|
|||
end
|
||||
|
||||
local function clean_name(item)
|
||||
if sub(item, 1, 1) == ":" then
|
||||
if sub(item, 1, 1) == ":" or sub(item, 1, 1) == " " then
|
||||
item = sub(item, 2)
|
||||
end
|
||||
|
||||
|
@ -453,10 +454,6 @@ function i3.register_craft_type(name, def)
|
|||
def.description = ""
|
||||
end
|
||||
|
||||
if not is_str(def.icon) then
|
||||
def.icon = ""
|
||||
end
|
||||
|
||||
craft_types[name] = def
|
||||
end
|
||||
|
||||
|
@ -537,7 +534,6 @@ function i3.register_craft(def)
|
|||
def.items = {}
|
||||
|
||||
for i = 1, len do
|
||||
items[i] = items[i]:gsub(",", ", ")
|
||||
local rlen = #split(items[i], ",")
|
||||
|
||||
if rlen > width then
|
||||
|
@ -547,13 +543,13 @@ function i3.register_craft(def)
|
|||
|
||||
for i = 1, len do
|
||||
while #split(items[i], ",") < width do
|
||||
items[i] = items[i] .. ", "
|
||||
items[i] = fmt("%s,", items[i])
|
||||
end
|
||||
end
|
||||
|
||||
for name in gmatch(concat(items, ","), "[%s%w_:]+") do
|
||||
c = c + 1
|
||||
def.items[c] = match(name, "%S+")
|
||||
def.items[c] = clean_name(name)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1356,25 +1352,25 @@ local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_siz
|
|||
local pos_y = data.yoffset + 0.9
|
||||
|
||||
if sub(icon, 1, 10) == "i3_furnace" then
|
||||
fs(fmt("animated_image", pos_x, pos_y, 0.5, 0.5, PNG.furnace_anim, 8, 180))
|
||||
fs("animated_image", pos_x, pos_y, 0.5, 0.5, PNG.furnace_anim, 8, 180)
|
||||
else
|
||||
fs(fmt("image", pos_x, pos_y, 0.5, 0.5, icon))
|
||||
fs("image", pos_x, pos_y, 0.5, 0.5, icon)
|
||||
end
|
||||
|
||||
local tooltip = custom_recipe and custom_recipe.description or
|
||||
shapeless and S"Shapeless" or S"Cooking"
|
||||
|
||||
fs(fmt("tooltip", pos_x, pos_y, 0.5, 0.5, ESC(tooltip)))
|
||||
fs("tooltip", pos_x, pos_y, 0.5, 0.5, ESC(tooltip))
|
||||
end
|
||||
|
||||
local arrow_X = right + 0.2 + (_btn_size or ITEM_BTN_SIZE)
|
||||
local X = arrow_X + 1.2
|
||||
local Y = data.yoffset + 1.4
|
||||
|
||||
fs(fmt("image", arrow_X, Y + 0.06, 1, 1, PNG.arrow))
|
||||
fs("image", arrow_X, Y + 0.06, 1, 1, PNG.arrow)
|
||||
|
||||
if rcp.type == "fuel" then
|
||||
fs(fmt("animated_image", X + 0.05, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE, PNG.fire_anim, 8, 180))
|
||||
fs("animated_image", X + 0.05, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE, PNG.fire_anim, 8, 180)
|
||||
else
|
||||
local item = rcp.output
|
||||
item = ItemStack(clean_name(item))
|
||||
|
@ -1382,14 +1378,14 @@ local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_siz
|
|||
local count = item:get_count()
|
||||
local bt_s = ITEM_BTN_SIZE * 1.2
|
||||
|
||||
fs(fmt("image", X, Y - 0.11, bt_s, bt_s, PNG.slot))
|
||||
fs("image", X, Y - 0.11, bt_s, bt_s, PNG.slot)
|
||||
|
||||
local _name = fmt("_%s", name)
|
||||
|
||||
fs(fmt("item_image_button",
|
||||
fs("item_image_button",
|
||||
X + 0.11, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE,
|
||||
fmt("%s %u", name, count * (is_recipe and data.scrbar_rcp or data.scrbar_usg or 1)),
|
||||
_name, ""))
|
||||
_name, "")
|
||||
|
||||
local def = reg_items[name]
|
||||
local unknown = not def or nil
|
||||
|
@ -1490,14 +1486,14 @@ local function get_grid_fs(fs, data, rcp, is_recipe)
|
|||
end
|
||||
|
||||
if not large_recipe then
|
||||
fs(fmt("image", X, Y, btn_size, btn_size, PNG.slot))
|
||||
fs("image", X, Y, btn_size, btn_size, PNG.slot)
|
||||
end
|
||||
|
||||
local btn_name = groups and fmt("group|%s|%s", groups[1], item) or item
|
||||
|
||||
fs(fmt("item_image_button", X, Y, btn_size, btn_size,
|
||||
fs("item_image_button", X, Y, btn_size, btn_size,
|
||||
fmt("%s %u", item, is_recipe and data.scrbar_rcp or data.scrbar_usg or 1),
|
||||
btn_name, label))
|
||||
btn_name, label)
|
||||
|
||||
local def = reg_items[name]
|
||||
local unknown = not def or nil
|
||||
|
@ -1538,7 +1534,7 @@ local function get_rcp_lbl(fs, data, panel, rn, is_recipe)
|
|||
local lbl_len = #_lbl:gsub("[\128-\191]", "") -- Count chars, not bytes in UTF-8 strings
|
||||
local shift = min(0.9, abs(12 - max(12, lbl_len)) * 0.15)
|
||||
|
||||
fs(fmt("label", data.xoffset + 5.65 - shift, data.yoffset + 3.37, lbl))
|
||||
fs("label", data.xoffset + 5.65 - shift, data.yoffset + 3.37, lbl)
|
||||
|
||||
if rn > 1 then
|
||||
local btn_suffix = is_recipe and "recipe" or "usage"
|
||||
|
@ -1547,8 +1543,8 @@ local function get_rcp_lbl(fs, data, panel, rn, is_recipe)
|
|||
local x_arrow = data.xoffset + 5.09
|
||||
local y_arrow = data.yoffset + 3.2
|
||||
|
||||
fs(fmt("image_button", x_arrow - shift, y_arrow, 0.3, 0.3, "", prev_name, ""),
|
||||
fmt("image_button", x_arrow + 2.3, y_arrow, 0.3, 0.3, "", next_name, ""))
|
||||
fs("image_button", x_arrow - shift, y_arrow, 0.3, 0.3, "", prev_name, "")
|
||||
fs("image_button", x_arrow + 2.3, y_arrow, 0.3, 0.3, "", next_name, "")
|
||||
end
|
||||
|
||||
local rcp = is_recipe and panel.rcp[data.rnum] or panel.rcp[data.unum]
|
||||
|
@ -1597,10 +1593,9 @@ local function get_model_fs(fs, data, def, model_alias)
|
|||
t[#t + 1] = t[#t]
|
||||
end
|
||||
|
||||
fs(fmt("model",
|
||||
data.xoffset + 6.6, data.yoffset + 0.05, 1.3, 1.3, "",
|
||||
fs("model", data.xoffset + 6.6, data.yoffset + 0.05, 1.3, 1.3, "",
|
||||
def.mesh, concat(t, ","), "0,0", "true", "true",
|
||||
model_alias and model_alias.frames or ""))
|
||||
model_alias and model_alias.frames or "")
|
||||
end
|
||||
|
||||
local function get_header(fs, data)
|
||||
|
@ -1612,14 +1607,14 @@ local function get_header(fs, data)
|
|||
local fav_marked = fmt("i3_fav%s.png", fav and "_off" or "")
|
||||
|
||||
fs(fmt("style[fav;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]",
|
||||
fmt("i3_fav%s.png", fav and "" or "_off"), fav_marked, fav_marked),
|
||||
fmt("image_button", star_x, star_y, star_size, star_size, "", "fav", ""),
|
||||
fmt("tooltip[fav;%s]", fav and ES"Unmark this item" or ES"Mark this item"))
|
||||
fmt("i3_fav%s.png", fav and "" or "_off"), fav_marked, fav_marked))
|
||||
fs("image_button", star_x, star_y, star_size, star_size, "", "fav", "")
|
||||
fs(fmt("tooltip[fav;%s]", fav and ES"Unmark this item" or ES"Mark this item"))
|
||||
else
|
||||
fs(fmt("style[nofav;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]",
|
||||
"i3_fav_off.png", PNG.cancel, PNG.cancel),
|
||||
fmt("image_button", star_x, star_y, star_size, star_size, "", "nofav", ""),
|
||||
fmt("tooltip[nofav;%s]", ES"Cannot mark this item. Bookmark limit reached."))
|
||||
"i3_fav_off.png", PNG.cancel, PNG.cancel))
|
||||
fs("image_button", star_x, star_y, star_size, star_size, "", "nofav", "")
|
||||
fs(fmt("tooltip[nofav;%s]", ES"Cannot mark this item. Bookmark limit reached."))
|
||||
end
|
||||
|
||||
local desc_lim, name_lim = 32, 34
|
||||
|
@ -1631,18 +1626,20 @@ local function get_header(fs, data)
|
|||
local Y2 = Y1 + 0.5
|
||||
|
||||
if #desc > desc_lim then
|
||||
fs(fmt("tooltip", X, Y1 - 0.1, 5.7, 0.24, desc))
|
||||
fs("tooltip", X, Y1 - 0.1, 5.7, 0.24, desc)
|
||||
desc = snip(desc, desc_lim)
|
||||
end
|
||||
|
||||
if #tech_name > name_lim then
|
||||
fs(fmt("tooltip", X, Y2 - 0.1, 5.7, 0.24, tech_name))
|
||||
fs("tooltip", X, Y2 - 0.1, 5.7, 0.24, tech_name)
|
||||
tech_name = snip(tech_name, name_lim)
|
||||
end
|
||||
|
||||
fs("style_type[label;font=bold;font_size=22]",
|
||||
fmt("label", X, Y1, desc), "style_type[label;font=mono;font_size=16]",
|
||||
fmt("label", X, Y2, clr("#7bf", tech_name)), "style_type[label;font=normal;font_size=16]")
|
||||
fs("style_type[label;font=bold;font_size=22]")
|
||||
fs("label", X, Y1, desc)
|
||||
fs("style_type[label;font=mono;font_size=16]")
|
||||
fs("label", X, Y2, clr("#7bf", tech_name))
|
||||
fs("style_type[label;font=normal;font_size=16]")
|
||||
|
||||
local def = reg_items[data.query_item]
|
||||
local model_alias = i3.model_alias[data.query_item]
|
||||
|
@ -1650,7 +1647,7 @@ local function get_header(fs, data)
|
|||
if def.drawtype == "mesh" or model_alias then
|
||||
get_model_fs(fs, data, def, model_alias)
|
||||
else
|
||||
fs(fmt("item_image", data.xoffset + 6.8, data.yoffset + 0.17, 1.1, 1.1, data.query_item))
|
||||
fs("item_image", data.xoffset + 6.8, data.yoffset + 0.17, 1.1, 1.1, data.query_item)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1659,10 +1656,9 @@ local function get_export_fs(fs, data, is_recipe, is_usage, max_stacks_rcp, max_
|
|||
local show_export = (is_recipe and data.export_rcp) or (is_usage and data.export_usg)
|
||||
|
||||
fs(fmt("style[export_%s;fgimg=%s;fgimg_hovered=%s]",
|
||||
name, fmt("%s", show_export and PNG.export_hover or PNG.export), PNG.export_hover),
|
||||
fmt("image_button",
|
||||
data.xoffset + 7.35, data.yoffset + 0.2, 0.45, 0.45, "", fmt("export_%s", name), ""),
|
||||
fmt("tooltip[export_%s;%s]", name, ES"Quick crafting"))
|
||||
name, fmt("%s", show_export and PNG.export_hover or PNG.export), PNG.export_hover))
|
||||
fs("image_button", data.xoffset + 7.35, data.yoffset + 0.2, 0.45, 0.45, "", fmt("export_%s", name), "")
|
||||
fs(fmt("tooltip[export_%s;%s]", name, ES"Quick crafting"))
|
||||
|
||||
if not show_export then return end
|
||||
|
||||
|
@ -1680,11 +1676,10 @@ local function get_export_fs(fs, data, is_recipe, is_usage, max_stacks_rcp, max_
|
|||
end
|
||||
|
||||
fs(fmt("style[scrbar_%s;noclip=true]", name),
|
||||
fmt("scrollbaroptions[min=1;max=%u;smallstep=1]", craft_max),
|
||||
fmt("scrollbar", data.xoffset + 8.1, data.yoffset, 3, 0.35,
|
||||
"horizontal", fmt("scrbar_%s", name), stack_fs),
|
||||
fmt("button", data.xoffset + 8.1, data.yoffset + 0.4, 3, 0.7, fmt("craft_%s", name),
|
||||
ES("Craft (x@1)", stack_fs)))
|
||||
fmt("scrollbaroptions[min=1;max=%u;smallstep=1]", craft_max))
|
||||
fs("scrollbar", data.xoffset + 8.1, data.yoffset, 3, 0.35, "horizontal", fmt("scrbar_%s", name), stack_fs)
|
||||
fs("button", data.xoffset + 8.1, data.yoffset + 0.4, 3, 0.7, fmt("craft_%s", name),
|
||||
ES("Craft (x@1)", stack_fs))
|
||||
end
|
||||
|
||||
local function get_rcp_extra(player, data, fs, panel, is_recipe, is_usage)
|
||||
|
@ -1719,13 +1714,13 @@ local function get_rcp_extra(player, data, fs, panel, is_recipe, is_usage)
|
|||
get_rcp_lbl(fs, data, panel, rn, is_recipe)
|
||||
else
|
||||
local lbl = is_recipe and ES"No recipes" or ES"No usages"
|
||||
fs(fmt("button", data.xoffset + 0.1, data.yoffset + (panel.height / 2) - 0.5,
|
||||
7.8, 1, "no_rcp", lbl))
|
||||
fs("button", data.xoffset + 0.1, data.yoffset + (panel.height / 2) - 0.5,
|
||||
7.8, 1, "no_rcp", lbl)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_favs(fs, data)
|
||||
fs(fmt("label", data.xoffset + 0.4, data.yoffset + 0.4, ES"Bookmarks"))
|
||||
fs("label", data.xoffset + 0.4, data.yoffset + 0.4, ES"Bookmarks")
|
||||
|
||||
for i = 1, #data.favs do
|
||||
local item = data.favs[i]
|
||||
|
@ -1733,10 +1728,10 @@ local function get_favs(fs, data)
|
|||
local Y = data.yoffset + 0.8
|
||||
|
||||
if data.query_item == item then
|
||||
fs(fmt("image", X, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE, PNG.slot))
|
||||
fs("image", X, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE, PNG.slot)
|
||||
end
|
||||
|
||||
fs(fmt("item_image_button", X, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE, item, item, ""))
|
||||
fs("item_image_button", X, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE, item, item, "")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1757,7 +1752,7 @@ local function get_panels(player, data, fs)
|
|||
end
|
||||
end
|
||||
|
||||
fs(fmt("bg9", data.xoffset + 0.1, data.yoffset, 7.9, panel.height, PNG.bg_full, 10))
|
||||
fs("bg9", data.xoffset + 0.1, data.yoffset, 7.9, panel.height, PNG.bg_full, 10)
|
||||
|
||||
local is_recipe, is_usage = panel.name == "recipes", panel.name == "usages"
|
||||
|
||||
|
@ -1771,24 +1766,21 @@ local function get_panels(player, data, fs)
|
|||
end
|
||||
end
|
||||
|
||||
local function add_subtitle(fs, title, x, y, ctn_len, font_size)
|
||||
font_size = font_size or 18
|
||||
|
||||
fs(fmt("style_type[label;font=bold;font_size=%u]", font_size), fmt("label", x, y, title),
|
||||
"style_type[label;font=normal;font_size=16]",
|
||||
"style_type[box;colors=#bababa,#bababa30,#bababa30,#bababa]",
|
||||
fmt("box", x, y + 0.3, ctn_len, 0.045, "#"))
|
||||
local function add_subtitle(fs, name, y, ctn_len, font_size, label)
|
||||
fs(fmt("style[%s;font=bold;font_size=%u;border=false;content_offset=0]", name, font_size))
|
||||
fs("button", 0, y, ctn_len, 0.5, name, ESC(label))
|
||||
fs("image", 0, y + 0.55, ctn_len, 0.035, PNG.bar)
|
||||
end
|
||||
|
||||
local function get_award_list(data, fs, ctn_len, yextra, award_list, awards_unlocked, award_list_nb)
|
||||
local percent = fmt("%.1f%%", (awards_unlocked * 100) / award_list_nb):gsub(".0", "")
|
||||
|
||||
add_subtitle(fs, ES("Achievements: @1 of @2 (@3)", awards_unlocked, award_list_nb, percent),
|
||||
0, yextra, ctn_len)
|
||||
add_subtitle(fs, "awards", yextra, ctn_len, 18,
|
||||
ES("Achievements: @1 of @2 (@3)", awards_unlocked, award_list_nb, percent))
|
||||
|
||||
for i = 1, award_list_nb do
|
||||
local award = award_list[i]
|
||||
local y = yextra - 0.7 + i + (i * 0.3)
|
||||
local y = yextra - 0.5 + i + (i * 0.3)
|
||||
|
||||
local def, progress = award.def, award.progress
|
||||
local title, desc = def.title, def.description
|
||||
|
@ -1848,38 +1840,37 @@ local function get_award_list(data, fs, ctn_len, yextra, award_list, awards_unlo
|
|||
end
|
||||
end
|
||||
|
||||
local function get_ctn_content(fs, data, player, xoffset, yoffset, ctn_len, award_list, awards_unlocked,
|
||||
award_list_nb)
|
||||
local function get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, awards_unlocked, award_list_nb)
|
||||
local name = player:get_player_name()
|
||||
add_subtitle(fs, ESC(name), xoffset, yoffset + 0.2, ctn_len, 22)
|
||||
add_subtitle(fs, "player_name", 0, ctn_len, 22, ESC(name))
|
||||
|
||||
local hp = damage_enabled and (data.hp or player:get_hp()) or 20
|
||||
local half = ceil((hp / 2) % 1)
|
||||
local hearts = (hp / 2) + half
|
||||
local heart_size = 0.35
|
||||
local heart_hgt = yoffset + 0.7
|
||||
local heart_x, heart_h = 0.65, yoffset + 0.75
|
||||
|
||||
for i = 1, 10 do
|
||||
fs(fmt("image", xoffset + ((i - 1) * (heart_size + 0.1)), heart_hgt,
|
||||
heart_size, heart_size, PNG.heart_grey))
|
||||
fs("image", heart_x + ((i - 1) * (heart_size + 0.1)), heart_h,
|
||||
heart_size, heart_size, PNG.heart_grey)
|
||||
end
|
||||
|
||||
if damage_enabled then
|
||||
for i = 1, hearts do
|
||||
fs(fmt("image", xoffset + ((i - 1) * (heart_size + 0.1)), heart_hgt,
|
||||
fs("image", heart_x + ((i - 1) * (heart_size + 0.1)), heart_h,
|
||||
heart_size, heart_size,
|
||||
(half == 1 and i == floor(hearts)) and PNG.heart_half or PNG.heart))
|
||||
(half == 1 and i == floor(hearts)) and PNG.heart_half or PNG.heart)
|
||||
end
|
||||
end
|
||||
|
||||
fs(fmt("list[current_player;craft;%f,%f;3,3;]", xoffset, yoffset + 1.45),
|
||||
fmt("image", xoffset + 3.47, yoffset + 2.69, 0.85, 0.85, PNG.arrow),
|
||||
fmt("list[current_player;craftpreview;%f,%f;1,1;]", xoffset + 4.45, yoffset + 2.6))--,
|
||||
--fmt("list[detached:i3_trash;main;%f,%f;1,1;]", xoffset + 4.45, yoffset + 3.75),
|
||||
--fmt("image", xoffset + 4.45, yoffset + 3.75, 1, 1, PNG.trash))
|
||||
fs(fmt("list[current_player;craft;%f,%f;3,3;]", 0, yoffset + 1.45))
|
||||
fs("image", 3.47, yoffset + 2.69, 0.85, 0.85, PNG.arrow)
|
||||
fs(fmt("list[current_player;craftpreview;%f,%f;1,1;]", 4.45, yoffset + 2.6))--,
|
||||
-- fmt("list[detached:i3_trash;main;%f,%f;1,1;]", 4.45, yoffset + 3.75))
|
||||
--fs("image", 4.45, yoffset + 3.75, 1, 1, PNG.trash)
|
||||
if core.is_creative_enabled(name) then
|
||||
fs(fmt("list[detached:i3_trash;main;%f,%f;1,1;]", xoffset + 4.45, yoffset + 3.75),
|
||||
fmt("image", xoffset + 4.45, yoffset + 3.75, 1, 1, PNG.trash))
|
||||
fs(fmt("list[detached:i3_trash;main;%f,%f;1,1;]", 4.45, yoffset + 3.75))
|
||||
fs("image", 4.45, yoffset + 3.75, 1, 1, PNG.trash)
|
||||
end
|
||||
|
||||
local yextra = 5.4
|
||||
|
@ -1890,21 +1881,21 @@ local function get_ctn_content(fs, data, player, xoffset, yoffset, ctn_len, awar
|
|||
fs(fmt("style[btn_bag;textcolor=%s]", bag_equip and "#fff" or "#aaa"),
|
||||
fmt("style[btn_armor;textcolor=%s]", armor_equip and "#fff" or "#aaa"),
|
||||
fmt("style[btn_skins;textcolor=%s]", skins_equip and "#fff" or "#aaa"),
|
||||
"style_type[button:hovered;textcolor=#fff]",
|
||||
fmt("button", -0.14, yextra - 0.2, 2, 0.6, "btn_bag", ES"Bag"),
|
||||
fmt("button", 1.87, yextra - 0.2, 2, 0.6, "btn_armor", ES"Armor"),
|
||||
fmt("button", 3.87, yextra - 0.2, 2, 0.6, "btn_skins", ES"Skins"))
|
||||
"style_type[button:hovered;textcolor=#fff]")
|
||||
fs("button", -0.14, yextra - 0.2, 2, 0.6, "btn_bag", ES"Bag")
|
||||
fs("button", 1.87, yextra - 0.2, 2, 0.6, "btn_armor", ES"Armor")
|
||||
fs("button", 3.87, yextra - 0.2, 2, 0.6, "btn_skins", ES"Skins")
|
||||
|
||||
fs(fmt("box", 0, yextra + 0.4, ctn_len, 0.045, "#bababa50"),
|
||||
fmt("box", (bag_equip and 0) or (armor_equip and 2) or (skins_equip and 4),
|
||||
yextra + 0.4, 1.7, 0.045, "#f9826c"))
|
||||
fs("box", 0, yextra + 0.4, ctn_len, 0.045, "#bababa50")
|
||||
fs("box", (bag_equip and 0) or (armor_equip and 2) or (skins_equip and 4),
|
||||
yextra + 0.4, 1.7, 0.045, "#f9826c")
|
||||
|
||||
if bag_equip then
|
||||
fs(fmt("list[detached:%s_backpack;main;0,%f;1,1;]", ESC(name), yextra + 0.7))
|
||||
|
||||
if not data.bag:get_stack("main", 1):is_empty() then
|
||||
fs(fmt("hypertext", 1.2, yextra + 0.89, ctn_len - 1.9, 0.8, "",
|
||||
ES("The inventory is extended by @1 slots", BAG_SIZES[data.bag_size] - INV_SIZE)))
|
||||
fs("hypertext", 1.2, yextra + 0.89, ctn_len - 1.9, 0.8, "",
|
||||
ES("The inventory is extended by @1 slots", BAG_SIZES[data.bag_size] - INV_SIZE))
|
||||
end
|
||||
|
||||
elseif armor_equip then
|
||||
|
@ -1913,11 +1904,11 @@ local function get_ctn_content(fs, data, player, xoffset, yoffset, ctn_len, awar
|
|||
|
||||
local armor_def = armor.def[name]
|
||||
|
||||
fs(fmt("label", 3.65, yextra + 1.55, fmt("%s: %s", ES"Level", armor_def.level)),
|
||||
fmt("label", 3.65, yextra + 2.05, fmt("%s: %s", ES"Heal", armor_def.heal)))
|
||||
fs("label", 3.65, yextra + 1.55, fmt("%s: %s", ES"Level", armor_def.level))
|
||||
fs("label", 3.65, yextra + 2.05, fmt("%s: %s", ES"Heal", armor_def.heal))
|
||||
else
|
||||
fs(fmt("hypertext", 0, yextra + 0.9, ctn_len, 0.6, "",
|
||||
"<center><style color=#7bf font=mono>3d_armor</style> not installed</center>"))
|
||||
fs("hypertext", 0, yextra + 0.9, ctn_len, 0.6, "",
|
||||
"<center><style color=#7bf font=mono>3d_armor</style> not installed</center>")
|
||||
end
|
||||
|
||||
elseif skins_equip then
|
||||
|
@ -1933,18 +1924,18 @@ local function get_ctn_content(fs, data, player, xoffset, yoffset, ctn_len, awar
|
|||
|
||||
fs(fmt("dropdown[0,%f;4,0.6;skins;%s;%u;true]", yextra + 0.7, sks, data.skin_id or 1))
|
||||
else
|
||||
fs(fmt("hypertext", 0, yextra + 0.9, ctn_len, 0.6, "",
|
||||
"<center><style color=#7bf font=mono>skinsdb</style> not installed</center>"))
|
||||
fs("hypertext", 0, yextra + 0.9, ctn_len, 0.6, "",
|
||||
"<center><style color=#7bf font=mono>skinsdb</style> not installed</center>")
|
||||
end
|
||||
end
|
||||
|
||||
if __awards then
|
||||
if bag_equip then
|
||||
yextra = yextra + 2.4
|
||||
yextra = yextra + 2.2
|
||||
elseif armor_equip then
|
||||
yextra = yextra + (__3darmor and 3.6 or 1.9)
|
||||
yextra = yextra + (__3darmor and 3.4 or 1.7)
|
||||
elseif skins_equip then
|
||||
yextra = yextra + 1.9
|
||||
yextra = yextra + 1.7
|
||||
end
|
||||
|
||||
get_award_list(data, fs, ctn_len, yextra, award_list, awards_unlocked, award_list_nb)
|
||||
|
@ -1986,14 +1977,15 @@ local function get_tabs_fs(player, data, fs, full_height)
|
|||
local Y = btm and full_height or -tab_hgh
|
||||
|
||||
fs("style_type[image_button:hovered;textcolor=#fff]")
|
||||
fs(fmt("image_button", X, Y, tab_len, tab_hgh, "", fmt("tab_%s", def.name),
|
||||
ESC(def.description)))
|
||||
fs("image_button", X, Y, tab_len, tab_hgh, "", fmt("tab_%s", def.name),
|
||||
ESC(def.description))
|
||||
|
||||
if def.image and def.image ~= "" then
|
||||
fs("style_type[image;noclip=true]")
|
||||
local desc = translate(data.lang_code, def.description)
|
||||
fs(fmt("image", X + (tab_len / 2) - ((#desc * 0.1) / 2) - 0.55,
|
||||
Y + 0.05, 0.35, 0.35, fmt("%s^\\[resize:16x16", def.image)))
|
||||
|
||||
fs("style_type[image;noclip=true]")
|
||||
fs("image", X + (tab_len / 2) - ((#desc * 0.1) / 2) - 0.55,
|
||||
Y + 0.05, 0.35, 0.35, fmt("%s^\\[resize:16x16", def.image))
|
||||
end
|
||||
|
||||
c = c + 1
|
||||
|
@ -2004,15 +1996,15 @@ local function get_debug_grid(data, fs, full_height)
|
|||
local spacing = 0.2
|
||||
|
||||
for x = 0, data.xoffset, spacing do
|
||||
fs(fmt("box", x, 0, 0.01, full_height, "#ff0"))
|
||||
fs("box", x, 0, 0.01, full_height, "#ff0")
|
||||
end
|
||||
|
||||
for y = 0, full_height, spacing do
|
||||
fs(fmt("box", 0, y, data.xoffset, 0.01, "#ff0"))
|
||||
fs("box", 0, y, data.xoffset, 0.01, "#ff0")
|
||||
end
|
||||
|
||||
fs(fmt("box", data.xoffset / 2, 0, 0.01, full_height, "#f00"))
|
||||
fs(fmt("box", 0, full_height / 2, data.xoffset, 0.01, "#f00"))
|
||||
fs("box", data.xoffset / 2, 0, 0.01, full_height, "#f00")
|
||||
fs("box", 0, full_height / 2, data.xoffset, 0.01, "#f00")
|
||||
end
|
||||
|
||||
local function make_fs(player, data)
|
||||
|
@ -2020,7 +2012,14 @@ local function make_fs(player, data)
|
|||
|
||||
local fs = setmetatable({}, {
|
||||
__call = function(t, ...)
|
||||
t[#t + 1] = concat({...})
|
||||
local args = {...}
|
||||
local elem = fs_elements[args[1]]
|
||||
|
||||
if elem then
|
||||
t[#t + 1] = fmt(elem, select(2, ...))
|
||||
else
|
||||
t[#t + 1] = concat(args)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
|
@ -2034,7 +2033,7 @@ local function make_fs(player, data)
|
|||
fs(fmt("formspec_version[%u]size[%f,%f]no_prepend[]bgcolor[#0000]",
|
||||
MIN_FORMSPEC_VERSION, data.xoffset + (show_panels and 8 or 0), full_height), styles)
|
||||
|
||||
fs(fmt("bg9", 0, 0, data.xoffset, full_height, PNG.bg_full, 10))
|
||||
fs("bg9", 0, 0, data.xoffset, full_height, PNG.bg_full, 10)
|
||||
|
||||
if tab then
|
||||
tab.formspec(player, data, fs)
|
||||
|
@ -2246,7 +2245,7 @@ local function get_inv_slots(data, fs)
|
|||
fs("style_type[box;colors=#77777710,#77777710,#777,#777]")
|
||||
|
||||
for i = 0, HOTBAR_COUNT - 1 do
|
||||
fs(fmt("box", i * size + inv_x + (i * spacing), inv_y, size, size, ""))
|
||||
fs("box", i * size + inv_x + (i * spacing), inv_y, size, size, "")
|
||||
end
|
||||
|
||||
fs(fmt("style_type[list;size=%f;spacing=%f]", size, spacing),
|
||||
|
@ -2279,20 +2278,20 @@ local function get_inventory_fs(player, data, fs)
|
|||
local name = player:get_player_name()
|
||||
|
||||
local ctn_len, ctn_hgt = 5.7, 6
|
||||
local xoffset, yoffset = 0, 0
|
||||
local yoffset = 0
|
||||
|
||||
if props.mesh ~= "" then
|
||||
local anim = player:get_local_animation()
|
||||
--fs("style[player_model;bgcolor=black]")
|
||||
local armor_skin = __3darmor or __skinsdb
|
||||
|
||||
fs(fmt("model", 0.2, 0.2, armor_skin and 4 or 3.4, armor_skin and ctn_hgt or 5.8,
|
||||
fs("model", 0.2, 0.2, armor_skin and 4 or 3.4, armor_skin and ctn_hgt or 5.8,
|
||||
"player_model",
|
||||
props.mesh, concat(props.textures, ","), "0,-150", "false", "false",
|
||||
fmt("%u,%u", anim.x, anim.y)))
|
||||
fmt("%u,%u", anim.x, anim.y))
|
||||
else
|
||||
local size = 2.5
|
||||
fs(fmt("image", 0.7, 0.2, size, size * props.visual_size.y, props.textures[1]))
|
||||
fs("image", 0.7, 0.2, size, size * props.visual_size.y, props.textures[1])
|
||||
end
|
||||
|
||||
local award_list, award_list_nb
|
||||
|
@ -2332,8 +2331,7 @@ local function get_inventory_fs(player, data, fs)
|
|||
|
||||
fs(fmt("scroll_container[3.9,0.2;%f,%f;scrbar_inv;vertical]", ctn_len, ctn_hgt))
|
||||
|
||||
get_ctn_content(fs, data, player, xoffset, yoffset, ctn_len, award_list, awards_unlocked,
|
||||
award_list_nb)
|
||||
get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, awards_unlocked, award_list_nb)
|
||||
|
||||
fs("scroll_container_end[]")
|
||||
|
||||
|
@ -2351,7 +2349,7 @@ local function get_inventory_fs(player, data, fs)
|
|||
fs(fmt("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]",
|
||||
btn_name, PNG[btn_name], PNG[fmt("%s_hover", btn_name)]))
|
||||
|
||||
fs(fmt("image_button", i + 3.447 - (i * 0.4), 11.13, 0.35, 0.35, "", btn_name, ""))
|
||||
fs("image_button", i + 3.447 - (i * 0.4), 11.13, 0.35, 0.35, "", btn_name, "")
|
||||
fs(fmt("tooltip[%s;%s]", btn_name, tooltip))
|
||||
end
|
||||
end
|
||||
|
@ -2361,15 +2359,15 @@ local function get_items_fs(_, data, fs)
|
|||
fmt("field[0.3,0.2;3.45,0.6;filter;;%s]", ESC(data.filter)),
|
||||
"field_close_on_enter[filter;false]")
|
||||
|
||||
fs(fmt("image_button", 3.75, 0.35, 0.3, 0.3, "", "cancel", ""),
|
||||
fmt("image_button", 4.25, 0.32, 0.35, 0.35, "", "search", ""),
|
||||
fmt("image_button", data.xoffset - 2.73, 0.3, 0.35, 0.35, "", "prev_page", ""),
|
||||
fmt("image_button", data.xoffset - 0.55, 0.3, 0.35, 0.35, "", "next_page", ""))
|
||||
fs("image_button", 3.75, 0.35, 0.3, 0.3, "", "cancel", "")
|
||||
fs("image_button", 4.25, 0.32, 0.35, 0.35, "", "search", "")
|
||||
fs("image_button", data.xoffset - 2.73, 0.3, 0.35, 0.35, "", "prev_page", "")
|
||||
fs("image_button", data.xoffset - 0.55, 0.3, 0.35, 0.35, "", "next_page", "")
|
||||
|
||||
data.pagemax = max(1, ceil(#data.items / IPP))
|
||||
|
||||
fs(fmt("button", data.xoffset - 2.4, 0.14, 1.88, 0.7, "pagenum",
|
||||
fmt("%s / %u", clr("#ff0", data.pagenum), data.pagemax)))
|
||||
fs("button", data.xoffset - 2.4, 0.14, 1.88, 0.7, "pagenum",
|
||||
fmt("%s / %u", clr("#ff0", data.pagenum), data.pagemax))
|
||||
|
||||
if #data.items == 0 then
|
||||
local lbl = ES"No item to show"
|
||||
|
@ -2378,7 +2376,7 @@ local function get_items_fs(_, data, fs)
|
|||
lbl = ES"Collect items to reveal more recipes"
|
||||
end
|
||||
|
||||
fs(fmt("button", 0, 3, data.xoffset, 1, "no_item", lbl))
|
||||
fs("button", 0, 3, data.xoffset, 1, "no_item", lbl)
|
||||
end
|
||||
|
||||
local first_item = (data.pagenum - 1) * IPP
|
||||
|
@ -2394,7 +2392,7 @@ local function get_items_fs(_, data, fs)
|
|||
Y = Y + (Y * 0.06) + 1
|
||||
|
||||
if data.query_item == item then
|
||||
fs(fmt("image", X, Y, 1, 1, PNG.slot))
|
||||
fs("image", X, Y, 1, 1, PNG.slot)
|
||||
end
|
||||
|
||||
fs[#fs + 1] = fmt("item_image_button", X, Y, 1, 1, item, fmt("%s_inv", item), "")
|
||||
|
@ -2422,10 +2420,15 @@ i3.new_tab {
|
|||
|
||||
if fields.trash then
|
||||
local inv = player:get_inventory()
|
||||
|
||||
if not inv:is_empty("main") then
|
||||
inv:set_list("main", {})
|
||||
end
|
||||
|
||||
if not inv:is_empty("craft") then
|
||||
inv:set_list("craft", {})
|
||||
end
|
||||
|
||||
elseif fields.compress then
|
||||
compress_items(player)
|
||||
|
||||
|
@ -2819,6 +2822,7 @@ end)
|
|||
core.register_on_dieplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
local data = pdata[name]
|
||||
if not data then return end
|
||||
|
||||
data.bag_size = nil
|
||||
data.bag:set_list("main", {})
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
i3.register_craft({
|
||||
result = "default:ladder_wood",
|
||||
items = {"default:copper_ingot 7, default:tin_ingot", "default:steel_ingot 2"},
|
||||
})
|
||||
|
||||
i3.register_craft({
|
||||
grid = {
|
||||
"X #",
|
||||
|
@ -7,7 +12,7 @@ i3.register_craft({
|
|||
},
|
||||
key = {
|
||||
['#'] = "default:wood",
|
||||
['X'] = "default:glass",
|
||||
['X'] = "default:glass 2",
|
||||
},
|
||||
result = "default:mese 3",
|
||||
})
|
||||
|
|
After Width: | Height: | Size: 705 B |
After Width: | Height: | Size: 100 KiB |
|
@ -8,7 +8,7 @@ local use_cmi = minetest.global_exists("cmi")
|
|||
|
||||
mobs = {
|
||||
mod = "redo",
|
||||
version = "20210310",
|
||||
version = "20210323",
|
||||
intllib = S,
|
||||
invis = minetest.global_exists("invisibility") and invisibility or {}
|
||||
}
|
||||
|
@ -251,9 +251,17 @@ local check_for = function(look_for, look_inside)
|
|||
for _, str in pairs(look_inside) do
|
||||
|
||||
if str == look_for then
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if str:find("group:") then
|
||||
|
||||
local group = str:split(":")[2]
|
||||
|
||||
if minetest.get_item_group(look_for, group) ~= 0 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -2697,34 +2705,17 @@ function mob_class:falling(pos)
|
|||
-- sanity check
|
||||
if not v then return end
|
||||
|
||||
local fall_speed = -10 -- gravity
|
||||
|
||||
-- don't exceed mob fall speed
|
||||
if v.y < self.fall_speed then
|
||||
fall_speed = self.fall_speed
|
||||
end
|
||||
local fall_speed = self.fall_speed
|
||||
|
||||
-- in water then use liquid viscosity for float/sink speed
|
||||
if (self.standing_in
|
||||
and minetest.registered_nodes[self.standing_in].groups.liquid)
|
||||
or (self.standing_on
|
||||
and minetest.registered_nodes[self.standing_in].groups.liquid) then
|
||||
if self.floats == 1 and self.standing_in
|
||||
and minetest.registered_nodes[self.standing_in].groups.liquid then
|
||||
|
||||
local visc = min(
|
||||
minetest.registered_nodes[self.standing_in].liquid_viscosity, 7)
|
||||
minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
|
||||
|
||||
if self.floats == 1 then
|
||||
|
||||
-- floating up
|
||||
if visc > 0 then
|
||||
fall_speed = max(1, v.y) / (visc + 1)
|
||||
end
|
||||
else
|
||||
-- sinking down
|
||||
if visc > 0 then
|
||||
fall_speed = -(max(1, v.y) / (visc + 1))
|
||||
end
|
||||
end
|
||||
self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
|
||||
fall_speed = -1.2 / visc
|
||||
else
|
||||
|
||||
-- fall damage onto solid ground
|
||||
|
|
|
@ -76,7 +76,7 @@ functions needed for the mob to work properly which contains the following:
|
|||
'floats' when set to 1 mob will float in water, 0 has them sink.
|
||||
'follow' mobs follow player when holding any of the items which appear
|
||||
on this table, the same items can be fed to a mob to tame or
|
||||
breed e.g. {"farming:wheat", "default:apple"}
|
||||
breed e.g. {"farming:wheat", "default:apple", "group:fish"}
|
||||
|
||||
'reach' is how far the mob can attack player when standing
|
||||
nearby, default is 3 nodes.
|
||||
|
|
|
@ -125,6 +125,54 @@ custom_bluon:write(userdata(), rope)
|
|||
assert(rope:to_text() == "\100userdata")
|
||||
```
|
||||
|
||||
### Schema
|
||||
|
||||
Place a file `schema.lua` in your mod, returning a schema table.
|
||||
|
||||
#### Non-string entries and `minetest.conf`
|
||||
|
||||
Suppose you have the following schema:
|
||||
|
||||
```lua
|
||||
return {
|
||||
type = "table",
|
||||
entries = {
|
||||
[42] = {
|
||||
type = "boolean",
|
||||
description = "The Answer"
|
||||
default = true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And a user sets the following config:
|
||||
|
||||
```conf
|
||||
mod.42 = false
|
||||
```
|
||||
|
||||
It won't work, as the resulting table will be `{["42"] = false}` instead of `{[42] = false}`. In order to make this work, you have to convert the keys yourself:
|
||||
|
||||
```lua
|
||||
return {
|
||||
type = "table",
|
||||
keys = {
|
||||
-- this will convert all keys to numbers
|
||||
type = "number"
|
||||
},
|
||||
entries = {
|
||||
[42] = {
|
||||
type = "boolean",
|
||||
description = "The Answer"
|
||||
default = true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is best left explicit. First, you shouldn't be using numbered field keys if you want decent `minetest.conf` support, and second, `modlib`'s schema module could only guess in this case, attempting conversion to number / boolean. What if both number and string field were set as possible entries? Should the string field be deleted? And so on.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Legacy
|
||||
|
@ -155,4 +203,38 @@ assert(rope:to_text() == "\100userdata")
|
|||
3. [`conf`](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt)
|
||||
* Minetest-like configuration files
|
||||
4. [`json`](https://json.org)
|
||||
* Not recommended
|
||||
* Not recommended
|
||||
|
||||
## Versions
|
||||
|
||||
### `rolling-62`
|
||||
|
||||
* Fix `modlib.func.curry_tail`
|
||||
* Change `b3d:get_animated_bone_properties` to return a list according to hierarchy
|
||||
|
||||
### `rolling-61`
|
||||
|
||||
* Fix `quaternion.to_euler_rotation`
|
||||
|
||||
### `rolling-60`
|
||||
|
||||
* Fix `vector.interpolate`
|
||||
|
||||
### `rolling-59`
|
||||
|
||||
* Schema failing check handling fixes
|
||||
|
||||
### `rolling-58`
|
||||
|
||||
* Schema improvements & docs
|
||||
|
||||
### `rolling-57`
|
||||
|
||||
* Uses `minetest.safe_file_write` for `file.write`
|
||||
|
||||
### `rolling-56`
|
||||
|
||||
* Fixes `math.fround`
|
||||
* Other minor fixes
|
||||
* Switch to lazy loading
|
||||
* Do `_ = modlib.<module>` to avoid lag spikes at run time
|
|
@ -6,350 +6,350 @@ local metatable = {__index = getfenv(1)}
|
|||
--+ See `b3d_specification.txt` as well as https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp
|
||||
--> B3D model
|
||||
function read(stream)
|
||||
local left = 8
|
||||
local left = 8
|
||||
|
||||
local function byte()
|
||||
left = left - 1
|
||||
return assert(stream:read(1):byte())
|
||||
end
|
||||
local function byte()
|
||||
left = left - 1
|
||||
return assert(stream:read(1):byte())
|
||||
end
|
||||
|
||||
local function int()
|
||||
local value = byte() + byte() * 0x100 + byte() * 0x10000 + byte() * 0x1000000
|
||||
if value >= 2^31 then
|
||||
return value - 2^32
|
||||
end
|
||||
return value
|
||||
end
|
||||
local function int()
|
||||
local value = byte() + byte() * 0x100 + byte() * 0x10000 + byte() * 0x1000000
|
||||
if value >= 2^31 then
|
||||
return value - 2^32
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function id()
|
||||
return int() + 1
|
||||
end
|
||||
local function id()
|
||||
return int() + 1
|
||||
end
|
||||
|
||||
local function optional_id()
|
||||
local id = int()
|
||||
if id == -1 then
|
||||
return
|
||||
end
|
||||
return id + 1
|
||||
end
|
||||
local function optional_id()
|
||||
local id = int()
|
||||
if id == -1 then
|
||||
return
|
||||
end
|
||||
return id + 1
|
||||
end
|
||||
|
||||
local function string()
|
||||
local rope = {}
|
||||
while true do
|
||||
left = left - 1
|
||||
local char = assert(stream:read(1))
|
||||
if char == "\0" then
|
||||
return table.concat(rope)
|
||||
end
|
||||
table.insert(rope, char)
|
||||
end
|
||||
end
|
||||
local function string()
|
||||
local rope = {}
|
||||
while true do
|
||||
left = left - 1
|
||||
local char = assert(stream:read(1))
|
||||
if char == "\0" then
|
||||
return table.concat(rope)
|
||||
end
|
||||
table.insert(rope, char)
|
||||
end
|
||||
end
|
||||
|
||||
local read_single = modlib.binary.read_single
|
||||
local function float()
|
||||
return read_single(byte)
|
||||
end
|
||||
local read_single = modlib.binary.read_single
|
||||
local function float()
|
||||
return read_single(byte)
|
||||
end
|
||||
|
||||
local function float_array(length)
|
||||
local list = {}
|
||||
for index = 1, length do
|
||||
list[index] = float()
|
||||
end
|
||||
return list
|
||||
end
|
||||
local function float_array(length)
|
||||
local list = {}
|
||||
for index = 1, length do
|
||||
list[index] = float()
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
local function color()
|
||||
return {
|
||||
r = float(),
|
||||
g = float(),
|
||||
b = float(),
|
||||
a = float()
|
||||
}
|
||||
end
|
||||
local function color()
|
||||
return {
|
||||
r = float(),
|
||||
g = float(),
|
||||
b = float(),
|
||||
a = float()
|
||||
}
|
||||
end
|
||||
|
||||
local function vector3()
|
||||
return float_array(3)
|
||||
end
|
||||
local function vector3()
|
||||
return float_array(3)
|
||||
end
|
||||
|
||||
local function quaternion()
|
||||
return {[4] = float(), [1] = float(), [2] = float(), [3] = float()}
|
||||
end
|
||||
local function quaternion()
|
||||
return {[4] = float(), [1] = float(), [2] = float(), [3] = float()}
|
||||
end
|
||||
|
||||
local function content()
|
||||
assert(left >= 0, stream:seek())
|
||||
return left ~= 0
|
||||
end
|
||||
local function content()
|
||||
assert(left >= 0, stream:seek())
|
||||
return left ~= 0
|
||||
end
|
||||
|
||||
local chunk
|
||||
local chunks = {
|
||||
TEXS = function()
|
||||
local textures = {}
|
||||
while content() do
|
||||
table.insert(textures, {
|
||||
file = string(),
|
||||
flags = int(),
|
||||
blend = int(),
|
||||
pos = float_array(2),
|
||||
scale = float_array(2),
|
||||
rotation = float()
|
||||
})
|
||||
end
|
||||
return textures
|
||||
end,
|
||||
BRUS = function()
|
||||
local brushes = {}
|
||||
brushes.n_texs = int()
|
||||
assert(brushes.n_texs <= 8)
|
||||
while content() do
|
||||
local brush = {
|
||||
name = string(),
|
||||
color = color(),
|
||||
shininess = float(),
|
||||
blend = float(),
|
||||
fx = float(),
|
||||
texture_id = {}
|
||||
}
|
||||
for index = 1, brushes.n_texs do
|
||||
brush.texture_id[index] = optional_id()
|
||||
end
|
||||
table.insert(brushes, brush)
|
||||
end
|
||||
return brushes
|
||||
end,
|
||||
VRTS = function()
|
||||
local vertices = {
|
||||
flags = int(),
|
||||
tex_coord_sets = int(),
|
||||
tex_coord_set_size = int()
|
||||
}
|
||||
assert(vertices.tex_coord_sets <= 8 and vertices.tex_coord_set_size <= 4)
|
||||
local has_normal = (vertices.flags % 2 == 1) or nil
|
||||
local has_color = (math.floor(vertices.flags / 2) % 2 == 1) or nil
|
||||
while content() do
|
||||
local vertex = {
|
||||
pos = vector3(),
|
||||
normal = has_normal and vector3(),
|
||||
color = has_color and color(),
|
||||
tex_coords = {}
|
||||
}
|
||||
for tex_coord_set = 1, vertices.tex_coord_sets do
|
||||
local tex_coords = {}
|
||||
for tex_coord = 1, vertices.tex_coord_set_size do
|
||||
tex_coords[tex_coord] = float()
|
||||
end
|
||||
vertex.tex_coords[tex_coord_set] = tex_coords
|
||||
end
|
||||
table.insert(vertices, vertex)
|
||||
end
|
||||
return vertices
|
||||
end,
|
||||
TRIS = function()
|
||||
local tris = {
|
||||
brush_id = id(),
|
||||
vertex_ids = {}
|
||||
}
|
||||
while content() do
|
||||
table.insert(tris.vertex_ids, {id(), id(), id()})
|
||||
end
|
||||
return tris
|
||||
end,
|
||||
MESH = function()
|
||||
local mesh = {
|
||||
brush_id = optional_id(),
|
||||
vertices = chunk{VRTS = true}
|
||||
}
|
||||
mesh.triangle_sets = {}
|
||||
repeat
|
||||
local tris = chunk{TRIS = true}
|
||||
table.insert(mesh.triangle_sets, tris)
|
||||
until not content()
|
||||
return mesh
|
||||
end,
|
||||
BONE = function()
|
||||
local bone = {}
|
||||
while content() do
|
||||
local vertex_id = id()
|
||||
assert(not bone[vertex_id], "duplicate vertex weight")
|
||||
local weight = float()
|
||||
if weight > 0 then
|
||||
-- Many exporters include unneeded zero weights
|
||||
bone[vertex_id] = weight
|
||||
end
|
||||
end
|
||||
return bone
|
||||
end,
|
||||
KEYS = function()
|
||||
local flags = int()
|
||||
local _flags = flags % 8
|
||||
local rotation, scale, position
|
||||
if _flags >= 4 then
|
||||
rotation = true
|
||||
_flags = _flags - 4
|
||||
end
|
||||
if _flags >= 2 then
|
||||
scale = true
|
||||
_flags = _flags - 2
|
||||
end
|
||||
position = _flags >= 1
|
||||
local bone = {
|
||||
flags = flags
|
||||
}
|
||||
while content() do
|
||||
table.insert(bone, {
|
||||
frame = int(),
|
||||
position = position and vector3() or nil,
|
||||
scale = scale and vector3() or nil,
|
||||
rotation = rotation and quaternion() or nil
|
||||
})
|
||||
end
|
||||
-- Ensure frames are sorted ascending
|
||||
table.sort(bone, function(a, b) return a.frame < b.frame end)
|
||||
return bone
|
||||
end,
|
||||
ANIM = function()
|
||||
return {
|
||||
-- flags are unused
|
||||
flags = int(),
|
||||
frames = int(),
|
||||
fps = float()
|
||||
}
|
||||
end,
|
||||
NODE = function()
|
||||
local node = {
|
||||
name = string(),
|
||||
position = vector3(),
|
||||
scale = vector3(),
|
||||
keys = {},
|
||||
rotation = quaternion(),
|
||||
children = {}
|
||||
}
|
||||
local node_type
|
||||
-- See https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp#L263
|
||||
-- Order is not validated; double occurences of mutually exclusive node def are
|
||||
while content() do
|
||||
local elem, type = chunk()
|
||||
if type == "MESH" then
|
||||
assert(not node_type)
|
||||
node_type = "mesh"
|
||||
node.mesh = elem
|
||||
elseif type == "BONE" then
|
||||
assert(not node_type)
|
||||
node_type = "bone"
|
||||
node.bone = elem
|
||||
elseif type == "KEYS" then
|
||||
assert((node.keys[#node.keys] or {}).frame ~= (elem[1] or {}).frame, "duplicate frame")
|
||||
modlib.table.append(node.keys, elem)
|
||||
elseif type == "NODE" then
|
||||
table.insert(node.children, elem)
|
||||
elseif type == "ANIM" then
|
||||
node.animation = elem
|
||||
else
|
||||
assert(not node_type)
|
||||
node_type = "pivot"
|
||||
end
|
||||
end
|
||||
-- TODO somehow merge keys
|
||||
return node
|
||||
end,
|
||||
BB3D = function()
|
||||
local version = int()
|
||||
local self = {
|
||||
version = {
|
||||
major = math.floor(version / 100),
|
||||
minor = version % 100,
|
||||
raw = version
|
||||
},
|
||||
textures = {},
|
||||
brushes = {}
|
||||
}
|
||||
assert(self.version.major <= 2, "unsupported version: " .. self.version.major)
|
||||
while content() do
|
||||
local field, type = chunk{TEXS = true, BRUS = true, NODE = true}
|
||||
if type == "TEXS" then
|
||||
modlib.table.append(self.textures, field)
|
||||
elseif type == "BRUS" then
|
||||
modlib.table.append(self.brushes, field)
|
||||
else
|
||||
self.node = field
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
}
|
||||
local chunk
|
||||
local chunks = {
|
||||
TEXS = function()
|
||||
local textures = {}
|
||||
while content() do
|
||||
table.insert(textures, {
|
||||
file = string(),
|
||||
flags = int(),
|
||||
blend = int(),
|
||||
pos = float_array(2),
|
||||
scale = float_array(2),
|
||||
rotation = float()
|
||||
})
|
||||
end
|
||||
return textures
|
||||
end,
|
||||
BRUS = function()
|
||||
local brushes = {}
|
||||
brushes.n_texs = int()
|
||||
assert(brushes.n_texs <= 8)
|
||||
while content() do
|
||||
local brush = {
|
||||
name = string(),
|
||||
color = color(),
|
||||
shininess = float(),
|
||||
blend = float(),
|
||||
fx = float(),
|
||||
texture_id = {}
|
||||
}
|
||||
for index = 1, brushes.n_texs do
|
||||
brush.texture_id[index] = optional_id()
|
||||
end
|
||||
table.insert(brushes, brush)
|
||||
end
|
||||
return brushes
|
||||
end,
|
||||
VRTS = function()
|
||||
local vertices = {
|
||||
flags = int(),
|
||||
tex_coord_sets = int(),
|
||||
tex_coord_set_size = int()
|
||||
}
|
||||
assert(vertices.tex_coord_sets <= 8 and vertices.tex_coord_set_size <= 4)
|
||||
local has_normal = (vertices.flags % 2 == 1) or nil
|
||||
local has_color = (math.floor(vertices.flags / 2) % 2 == 1) or nil
|
||||
while content() do
|
||||
local vertex = {
|
||||
pos = vector3(),
|
||||
normal = has_normal and vector3(),
|
||||
color = has_color and color(),
|
||||
tex_coords = {}
|
||||
}
|
||||
for tex_coord_set = 1, vertices.tex_coord_sets do
|
||||
local tex_coords = {}
|
||||
for tex_coord = 1, vertices.tex_coord_set_size do
|
||||
tex_coords[tex_coord] = float()
|
||||
end
|
||||
vertex.tex_coords[tex_coord_set] = tex_coords
|
||||
end
|
||||
table.insert(vertices, vertex)
|
||||
end
|
||||
return vertices
|
||||
end,
|
||||
TRIS = function()
|
||||
local tris = {
|
||||
brush_id = id(),
|
||||
vertex_ids = {}
|
||||
}
|
||||
while content() do
|
||||
table.insert(tris.vertex_ids, {id(), id(), id()})
|
||||
end
|
||||
return tris
|
||||
end,
|
||||
MESH = function()
|
||||
local mesh = {
|
||||
brush_id = optional_id(),
|
||||
vertices = chunk{VRTS = true}
|
||||
}
|
||||
mesh.triangle_sets = {}
|
||||
repeat
|
||||
local tris = chunk{TRIS = true}
|
||||
table.insert(mesh.triangle_sets, tris)
|
||||
until not content()
|
||||
return mesh
|
||||
end,
|
||||
BONE = function()
|
||||
local bone = {}
|
||||
while content() do
|
||||
local vertex_id = id()
|
||||
assert(not bone[vertex_id], "duplicate vertex weight")
|
||||
local weight = float()
|
||||
if weight > 0 then
|
||||
-- Many exporters include unneeded zero weights
|
||||
bone[vertex_id] = weight
|
||||
end
|
||||
end
|
||||
return bone
|
||||
end,
|
||||
KEYS = function()
|
||||
local flags = int()
|
||||
local _flags = flags % 8
|
||||
local rotation, scale, position
|
||||
if _flags >= 4 then
|
||||
rotation = true
|
||||
_flags = _flags - 4
|
||||
end
|
||||
if _flags >= 2 then
|
||||
scale = true
|
||||
_flags = _flags - 2
|
||||
end
|
||||
position = _flags >= 1
|
||||
local bone = {
|
||||
flags = flags
|
||||
}
|
||||
while content() do
|
||||
table.insert(bone, {
|
||||
frame = int(),
|
||||
position = position and vector3() or nil,
|
||||
scale = scale and vector3() or nil,
|
||||
rotation = rotation and quaternion() or nil
|
||||
})
|
||||
end
|
||||
-- Ensure frames are sorted ascending
|
||||
table.sort(bone, function(a, b) return a.frame < b.frame end)
|
||||
return bone
|
||||
end,
|
||||
ANIM = function()
|
||||
return {
|
||||
-- flags are unused
|
||||
flags = int(),
|
||||
frames = int(),
|
||||
fps = float()
|
||||
}
|
||||
end,
|
||||
NODE = function()
|
||||
local node = {
|
||||
name = string(),
|
||||
position = vector3(),
|
||||
scale = vector3(),
|
||||
keys = {},
|
||||
rotation = quaternion(),
|
||||
children = {}
|
||||
}
|
||||
local node_type
|
||||
-- See https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp#L263
|
||||
-- Order is not validated; double occurences of mutually exclusive node def are
|
||||
while content() do
|
||||
local elem, type = chunk()
|
||||
if type == "MESH" then
|
||||
assert(not node_type)
|
||||
node_type = "mesh"
|
||||
node.mesh = elem
|
||||
elseif type == "BONE" then
|
||||
assert(not node_type)
|
||||
node_type = "bone"
|
||||
node.bone = elem
|
||||
elseif type == "KEYS" then
|
||||
assert((node.keys[#node.keys] or {}).frame ~= (elem[1] or {}).frame, "duplicate frame")
|
||||
modlib.table.append(node.keys, elem)
|
||||
elseif type == "NODE" then
|
||||
table.insert(node.children, elem)
|
||||
elseif type == "ANIM" then
|
||||
node.animation = elem
|
||||
else
|
||||
assert(not node_type)
|
||||
node_type = "pivot"
|
||||
end
|
||||
end
|
||||
-- TODO somehow merge keys
|
||||
return node
|
||||
end,
|
||||
BB3D = function()
|
||||
local version = int()
|
||||
local self = {
|
||||
version = {
|
||||
major = math.floor(version / 100),
|
||||
minor = version % 100,
|
||||
raw = version
|
||||
},
|
||||
textures = {},
|
||||
brushes = {}
|
||||
}
|
||||
assert(self.version.major <= 2, "unsupported version: " .. self.version.major)
|
||||
while content() do
|
||||
local field, type = chunk{TEXS = true, BRUS = true, NODE = true}
|
||||
if type == "TEXS" then
|
||||
modlib.table.append(self.textures, field)
|
||||
elseif type == "BRUS" then
|
||||
modlib.table.append(self.brushes, field)
|
||||
else
|
||||
self.node = field
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
}
|
||||
|
||||
local function chunk_header()
|
||||
left = left - 4
|
||||
return stream:read(4), int()
|
||||
end
|
||||
local function chunk_header()
|
||||
left = left - 4
|
||||
return stream:read(4), int()
|
||||
end
|
||||
|
||||
function chunk(possible_chunks)
|
||||
local type, new_left = chunk_header()
|
||||
local parent_left
|
||||
left, parent_left = new_left, left
|
||||
if possible_chunks and not possible_chunks[type] then
|
||||
error("expected one of " .. table.concat(modlib.table.keys(possible_chunks), ", ") .. ", found " .. type)
|
||||
end
|
||||
local res = assert(chunks[type])()
|
||||
assert(left == 0)
|
||||
left = parent_left - new_left
|
||||
return res, type
|
||||
end
|
||||
function chunk(possible_chunks)
|
||||
local type, new_left = chunk_header()
|
||||
local parent_left
|
||||
left, parent_left = new_left, left
|
||||
if possible_chunks and not possible_chunks[type] then
|
||||
error("expected one of " .. table.concat(modlib.table.keys(possible_chunks), ", ") .. ", found " .. type)
|
||||
end
|
||||
local res = assert(chunks[type])()
|
||||
assert(left == 0)
|
||||
left = parent_left - new_left
|
||||
return res, type
|
||||
end
|
||||
|
||||
local self = chunk{BB3D = true}
|
||||
return setmetatable(self, metatable)
|
||||
local self = chunk{BB3D = true}
|
||||
return setmetatable(self, metatable)
|
||||
end
|
||||
|
||||
-- TODO function write(self, stream)
|
||||
|
||||
local binary_search_frame = modlib.table.binary_search_comparator(function(a, b)
|
||||
return modlib.table.default_comparator(a, b.frame)
|
||||
return modlib.table.default_comparator(a, b.frame)
|
||||
end)
|
||||
|
||||
--> [bonename] = { position = vector, rotation = quaternion, scale = vector }
|
||||
--> list of { bone_name = string, parent_bone_name = string, position = vector, rotation = quaternion, scale = vector }
|
||||
function get_animated_bone_properties(self, keyframe, interpolate)
|
||||
local function get_frame_values(keys)
|
||||
local values = keys[keyframe]
|
||||
if values and values.frame == keyframe then
|
||||
return {
|
||||
position = values.position,
|
||||
rotation = values.rotation,
|
||||
scale = values.scale
|
||||
}
|
||||
end
|
||||
local index = binary_search_frame(keys, keyframe)
|
||||
if index > 0 then
|
||||
return keys[index]
|
||||
end
|
||||
index = -index
|
||||
assert(index > 1 and index <= #keys)
|
||||
local a, b = keys[index - 1], keys[index]
|
||||
if not interpolate then
|
||||
return a
|
||||
end
|
||||
local ratio = (keyframe - a.frame) / (b.frame - a.frame)
|
||||
return {
|
||||
position = (a.position and b.position and modlib.vector.interpolate(a.position, b.position, ratio)) or a.position or b.position,
|
||||
rotation = (a.rotation and b.rotation and modlib.quaternion.interpolate(a.rotation, b.rotation, ratio)) or a.rotation or b.rotation,
|
||||
scale = (a.scale and b.scale and modlib.vector.interpolate(a.scale, b.scale, ratio)) or a.scale or b.scale,
|
||||
}
|
||||
end
|
||||
local bone_properties = {}
|
||||
local function get_props(node)
|
||||
local properties = {}
|
||||
if node.keys and next(node.keys) ~= nil then
|
||||
properties = modlib.table.add_all(properties, get_frame_values(node.keys))
|
||||
end
|
||||
for _, property in pairs{"position", "rotation", "scale"} do
|
||||
properties[property] = properties[property] or modlib.table.copy(node[property])
|
||||
end
|
||||
if node.bone then
|
||||
assert(not bone_properties[node.name])
|
||||
bone_properties[node.name] = properties
|
||||
end
|
||||
for _, child in pairs(node.children or {}) do
|
||||
get_props(child)
|
||||
end
|
||||
end
|
||||
get_props(self.node)
|
||||
return bone_properties
|
||||
local function get_frame_values(keys)
|
||||
local values = keys[keyframe]
|
||||
if values and values.frame == keyframe then
|
||||
return {
|
||||
position = values.position,
|
||||
rotation = values.rotation,
|
||||
scale = values.scale
|
||||
}
|
||||
end
|
||||
local index = binary_search_frame(keys, keyframe)
|
||||
if index > 0 then
|
||||
return keys[index]
|
||||
end
|
||||
index = -index
|
||||
assert(index > 1 and index <= #keys)
|
||||
local a, b = keys[index - 1], keys[index]
|
||||
if not interpolate then
|
||||
return a
|
||||
end
|
||||
local ratio = (keyframe - a.frame) / (b.frame - a.frame)
|
||||
return {
|
||||
position = (a.position and b.position and modlib.vector.interpolate(a.position, b.position, ratio)) or a.position or b.position,
|
||||
rotation = (a.rotation and b.rotation and modlib.quaternion.slerp(a.rotation, b.rotation, ratio)) or a.rotation or b.rotation,
|
||||
scale = (a.scale and b.scale and modlib.vector.interpolate(a.scale, b.scale, ratio)) or a.scale or b.scale,
|
||||
}
|
||||
end
|
||||
local bone_properties = {}
|
||||
local function get_props(node, parent_bone_name)
|
||||
local properties = {parent_bone_name = parent_bone_name}
|
||||
if node.keys and next(node.keys) ~= nil then
|
||||
properties = modlib.table.add_all(properties, get_frame_values(node.keys))
|
||||
end
|
||||
for _, property in pairs{"position", "rotation", "scale"} do
|
||||
properties[property] = properties[property] or modlib.table.copy(node[property])
|
||||
end
|
||||
if node.bone then
|
||||
properties.bone_name = node.name
|
||||
table.insert(bone_properties, properties)
|
||||
end
|
||||
for _, child in pairs(node.children or {}) do
|
||||
get_props(child, properties.bone_name)
|
||||
end
|
||||
end
|
||||
get_props(self.node)
|
||||
return bone_properties
|
||||
end
|
|
@ -3,113 +3,113 @@
|
|||
--+ Reads doubles (f64) or floats (f32)
|
||||
--: double reads an f64 if true, f32 otherwise
|
||||
function read_float(read_byte, double)
|
||||
-- First read the mantissa
|
||||
local mantissa = 0
|
||||
for _ = 1, double and 6 or 2 do
|
||||
mantissa = (mantissa + read_byte()) / 0x100
|
||||
end
|
||||
-- Second and first byte in big endian: last bit of exponent + 7 bits of mantissa, sign bit + 7 bits of exponent
|
||||
local byte_2, byte_1 = read_byte(), read_byte()
|
||||
local sign = 1
|
||||
if byte_1 >= 0x80 then
|
||||
sign = -1
|
||||
byte_1 = byte_1 - 0x80
|
||||
end
|
||||
local exponent = byte_1 * 2
|
||||
if byte_2 >= 0x80 then
|
||||
exponent = exponent + 1
|
||||
byte_2 = byte_2 - 0x80
|
||||
end
|
||||
mantissa = (mantissa + byte_2) / 0x80
|
||||
if exponent == 0xFF then
|
||||
if mantissa == 0 then
|
||||
return sign * math.huge
|
||||
end
|
||||
-- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it
|
||||
-- HACK ((0/0)^1) yields nan, 0/0 yields -nan
|
||||
return sign == 1 and ((0/0)^1) or 0/0
|
||||
end
|
||||
assert(mantissa < 1)
|
||||
if exponent == 0 then
|
||||
-- subnormal value
|
||||
return sign * 2^-126 * mantissa
|
||||
end
|
||||
return sign * 2 ^ (exponent - 127) * (1 + mantissa)
|
||||
-- First read the mantissa
|
||||
local mantissa = 0
|
||||
for _ = 1, double and 6 or 2 do
|
||||
mantissa = (mantissa + read_byte()) / 0x100
|
||||
end
|
||||
-- Second and first byte in big endian: last bit of exponent + 7 bits of mantissa, sign bit + 7 bits of exponent
|
||||
local byte_2, byte_1 = read_byte(), read_byte()
|
||||
local sign = 1
|
||||
if byte_1 >= 0x80 then
|
||||
sign = -1
|
||||
byte_1 = byte_1 - 0x80
|
||||
end
|
||||
local exponent = byte_1 * 2
|
||||
if byte_2 >= 0x80 then
|
||||
exponent = exponent + 1
|
||||
byte_2 = byte_2 - 0x80
|
||||
end
|
||||
mantissa = (mantissa + byte_2) / 0x80
|
||||
if exponent == 0xFF then
|
||||
if mantissa == 0 then
|
||||
return sign * math.huge
|
||||
end
|
||||
-- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it
|
||||
-- HACK ((0/0)^1) yields nan, 0/0 yields -nan
|
||||
return sign == 1 and ((0/0)^1) or 0/0
|
||||
end
|
||||
assert(mantissa < 1)
|
||||
if exponent == 0 then
|
||||
-- subnormal value
|
||||
return sign * 2^-126 * mantissa
|
||||
end
|
||||
return sign * 2 ^ (exponent - 127) * (1 + mantissa)
|
||||
end
|
||||
|
||||
--+ Reads a single floating point number (f32)
|
||||
function read_single(read_byte)
|
||||
return read_float(read_byte)
|
||||
return read_float(read_byte)
|
||||
end
|
||||
|
||||
--+ Reads a double (f64)
|
||||
function read_double(read_byte)
|
||||
return read_float(read_byte, true)
|
||||
return read_float(read_byte, true)
|
||||
end
|
||||
|
||||
function read_uint(read_byte, bytes)
|
||||
local factor = 1
|
||||
local uint = 0
|
||||
for _ = 1, bytes do
|
||||
uint = uint + read_byte() * factor
|
||||
factor = factor * 0x100
|
||||
end
|
||||
return uint
|
||||
local factor = 1
|
||||
local uint = 0
|
||||
for _ = 1, bytes do
|
||||
uint = uint + read_byte() * factor
|
||||
factor = factor * 0x100
|
||||
end
|
||||
return uint
|
||||
end
|
||||
|
||||
function write_uint(write_byte, uint, bytes)
|
||||
for _ = 1, bytes do
|
||||
write_byte(uint % 0x100)
|
||||
uint = math.floor(uint / 0x100)
|
||||
end
|
||||
assert(uint == 0)
|
||||
for _ = 1, bytes do
|
||||
write_byte(uint % 0x100)
|
||||
uint = math.floor(uint / 0x100)
|
||||
end
|
||||
assert(uint == 0)
|
||||
end
|
||||
|
||||
--: on_write function(double)
|
||||
--: double set to true to force f64, false for f32, nil for auto
|
||||
function write_float(write_byte, number, on_write, double)
|
||||
local sign = 0
|
||||
if number < 0 then
|
||||
number = -number
|
||||
sign = 0x80
|
||||
end
|
||||
local mantissa, exponent = math.frexp(number)
|
||||
exponent = exponent + 127
|
||||
if exponent > 1 then
|
||||
-- TODO ensure this deals properly with subnormal numbers
|
||||
mantissa = mantissa * 2 - 1
|
||||
exponent = exponent - 1
|
||||
end
|
||||
local sign_byte = sign + math.floor(exponent / 2)
|
||||
mantissa = mantissa * 0x80
|
||||
local exponent_byte = (exponent % 2) * 0x80 + math.floor(mantissa)
|
||||
mantissa = mantissa % 1
|
||||
local mantissa_bytes = {}
|
||||
-- TODO ensure this check is proper
|
||||
if double == nil then
|
||||
double = mantissa % 2^-23 > 0
|
||||
end
|
||||
if on_write then
|
||||
on_write(double)
|
||||
end
|
||||
local len = double and 6 or 2
|
||||
for index = len, 1, -1 do
|
||||
mantissa = mantissa * 0x100
|
||||
mantissa_bytes[index] = math.floor(mantissa)
|
||||
mantissa = mantissa % 1
|
||||
end
|
||||
assert(mantissa == 0)
|
||||
for index = 1, len do
|
||||
write_byte(mantissa_bytes[index])
|
||||
end
|
||||
write_byte(exponent_byte)
|
||||
write_byte(sign_byte)
|
||||
local sign = 0
|
||||
if number < 0 then
|
||||
number = -number
|
||||
sign = 0x80
|
||||
end
|
||||
local mantissa, exponent = math.frexp(number)
|
||||
exponent = exponent + 127
|
||||
if exponent > 1 then
|
||||
-- TODO ensure this deals properly with subnormal numbers
|
||||
mantissa = mantissa * 2 - 1
|
||||
exponent = exponent - 1
|
||||
end
|
||||
local sign_byte = sign + math.floor(exponent / 2)
|
||||
mantissa = mantissa * 0x80
|
||||
local exponent_byte = (exponent % 2) * 0x80 + math.floor(mantissa)
|
||||
mantissa = mantissa % 1
|
||||
local mantissa_bytes = {}
|
||||
-- TODO ensure this check is proper
|
||||
if double == nil then
|
||||
double = mantissa % 2^-23 > 0
|
||||
end
|
||||
if on_write then
|
||||
on_write(double)
|
||||
end
|
||||
local len = double and 6 or 2
|
||||
for index = len, 1, -1 do
|
||||
mantissa = mantissa * 0x100
|
||||
mantissa_bytes[index] = math.floor(mantissa)
|
||||
mantissa = mantissa % 1
|
||||
end
|
||||
assert(mantissa == 0)
|
||||
for index = 1, len do
|
||||
write_byte(mantissa_bytes[index])
|
||||
end
|
||||
write_byte(exponent_byte)
|
||||
write_byte(sign_byte)
|
||||
end
|
||||
|
||||
function write_single(write_byte, number)
|
||||
return write_float(write_byte, number, nil, false)
|
||||
return write_float(write_byte, number, nil, false)
|
||||
end
|
||||
|
||||
function write_double(write_byte, number)
|
||||
return write_float(write_byte, number, nil, true)
|
||||
return write_float(write_byte, number, nil, true)
|
||||
end
|
|
@ -3,23 +3,23 @@ local bluon = getfenv(1)
|
|||
local metatable = {__index = bluon}
|
||||
|
||||
function new(self)
|
||||
return setmetatable(self or {}, metatable)
|
||||
return setmetatable(self or {}, metatable)
|
||||
end
|
||||
|
||||
function aux_is_valid()
|
||||
return false
|
||||
return false
|
||||
end
|
||||
|
||||
function aux_len(object)
|
||||
error("unsupported type: " .. type(object))
|
||||
error("unsupported type: " .. type(object))
|
||||
end
|
||||
|
||||
function aux_read(type)
|
||||
error(("unsupported type: 0x%02X"):format(type))
|
||||
error(("unsupported type: 0x%02X"):format(type))
|
||||
end
|
||||
|
||||
function aux_write(object)
|
||||
error("unsupported type: " .. type(object))
|
||||
error("unsupported type: " .. type(object))
|
||||
end
|
||||
|
||||
local uint_widths = {1, 2, 4, 8}
|
||||
|
@ -27,291 +27,291 @@ local uint_types = #uint_widths
|
|||
local type_ranges = {}
|
||||
local current = 0
|
||||
for _, type in ipairs{
|
||||
{"boolean", 2};
|
||||
-- 0, -nan, +inf, -inf: sign of nan can be ignored
|
||||
{"number_constant", 4};
|
||||
{"number_negative", uint_types};
|
||||
{"number_positive", uint_types};
|
||||
{"number_f32", 1};
|
||||
{"number", 1};
|
||||
{"string_constant", 1};
|
||||
{"string", uint_types};
|
||||
-- (T0, T8, T16, T32, T64) x (L0, L8, L16, L32, L64)
|
||||
{"table", (uint_types + 1) ^ 2};
|
||||
{"reference", uint_types}
|
||||
{"boolean", 2};
|
||||
-- 0, -nan, +inf, -inf: sign of nan can be ignored
|
||||
{"number_constant", 4};
|
||||
{"number_negative", uint_types};
|
||||
{"number_positive", uint_types};
|
||||
{"number_f32", 1};
|
||||
{"number", 1};
|
||||
{"string_constant", 1};
|
||||
{"string", uint_types};
|
||||
-- (T0, T8, T16, T32, T64) x (L0, L8, L16, L32, L64)
|
||||
{"table", (uint_types + 1) ^ 2};
|
||||
{"reference", uint_types}
|
||||
} do
|
||||
local typename, length = unpack(type)
|
||||
current = current + length
|
||||
type_ranges[typename] = current
|
||||
local typename, length = unpack(type)
|
||||
current = current + length
|
||||
type_ranges[typename] = current
|
||||
end
|
||||
|
||||
local constants = {
|
||||
[false] = "\0",
|
||||
[true] = "\1",
|
||||
[0] = "\2",
|
||||
-- not possible as table entry as Lua doesn't allow +/-nan as table key
|
||||
-- [0/0] = "\3",
|
||||
[math.huge] = "\4",
|
||||
[-math.huge] = "\5",
|
||||
[""] = "\20"
|
||||
[false] = "\0",
|
||||
[true] = "\1",
|
||||
[0] = "\2",
|
||||
-- not possible as table entry as Lua doesn't allow +/-nan as table key
|
||||
-- [0/0] = "\3",
|
||||
[math.huge] = "\4",
|
||||
[-math.huge] = "\5",
|
||||
[""] = "\20"
|
||||
}
|
||||
|
||||
local constant_nan = "\3"
|
||||
|
||||
local function uint_type(uint)
|
||||
--U8
|
||||
if uint <= 0xFF then return 1 end
|
||||
--U16
|
||||
if uint <= 0xFFFF then return 2 end
|
||||
--U32
|
||||
if uint <= 0xFFFFFFFF then return 3 end
|
||||
--U64
|
||||
return 4
|
||||
--U8
|
||||
if uint <= 0xFF then return 1 end
|
||||
--U16
|
||||
if uint <= 0xFFFF then return 2 end
|
||||
--U32
|
||||
if uint <= 0xFFFFFFFF then return 3 end
|
||||
--U64
|
||||
return 4
|
||||
end
|
||||
|
||||
local valid_types = modlib.table.set{"nil", "boolean", "number", "string"}
|
||||
function is_valid(self, object)
|
||||
local _type = type(object)
|
||||
if valid_types[_type] then
|
||||
return true
|
||||
end
|
||||
if _type == "table" then
|
||||
for key, value in pairs(object) do
|
||||
if not (is_valid(self, key) and is_valid(self, value)) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return self.aux_is_valid(object)
|
||||
local _type = type(object)
|
||||
if valid_types[_type] then
|
||||
return true
|
||||
end
|
||||
if _type == "table" then
|
||||
for key, value in pairs(object) do
|
||||
if not (is_valid(self, key) and is_valid(self, value)) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return self.aux_is_valid(object)
|
||||
end
|
||||
|
||||
local function uint_len(uint)
|
||||
return uint_widths[uint_type(uint)]
|
||||
return uint_widths[uint_type(uint)]
|
||||
end
|
||||
|
||||
local function is_map_key(key, list_len)
|
||||
return type(key) ~= "number" or (key < 1 or key > list_len or key % 1 ~= 0)
|
||||
return type(key) ~= "number" or (key < 1 or key > list_len or key % 1 ~= 0)
|
||||
end
|
||||
|
||||
function len(self, object)
|
||||
if constants[object] then
|
||||
return 1
|
||||
end
|
||||
local _type = type(object)
|
||||
if _type == "number" then
|
||||
if object ~= object then
|
||||
stream:write(constant_nan)
|
||||
return
|
||||
end
|
||||
if object % 1 == 0 then
|
||||
return 1 + uint_len(object > 0 and object or -object)
|
||||
end
|
||||
-- TODO ensure this check is proper
|
||||
if mantissa % 2^-23 > 0 then
|
||||
return 9
|
||||
end
|
||||
return 5
|
||||
end
|
||||
local id = object_ids[object]
|
||||
if id then
|
||||
return 1 + uint_len(id)
|
||||
end
|
||||
current_id = current_id + 1
|
||||
object_ids[object] = current_id
|
||||
if _type == "string" then
|
||||
local object_len = object:len()
|
||||
return 1 + uint_len(object_len) + object_len
|
||||
end
|
||||
if _type == "table" then
|
||||
if next(object) == nil then
|
||||
-- empty {} table
|
||||
byte(type_ranges.string + 1)
|
||||
return 1
|
||||
end
|
||||
local list_len = #object
|
||||
local kv_len = 0
|
||||
for key, _ in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
kv_len = kv_len + 1
|
||||
end
|
||||
end
|
||||
local table_len = 1 + uint_len(list_len) + uint_len(kv_len)
|
||||
for index = 1, list_len do
|
||||
table_len = table_len + len(self, object[index])
|
||||
end
|
||||
for key, value in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
table_len = table_len + len(self, key) + len(self, value)
|
||||
end
|
||||
end
|
||||
return len
|
||||
end
|
||||
return self.aux_len(object)
|
||||
if constants[object] then
|
||||
return 1
|
||||
end
|
||||
local _type = type(object)
|
||||
if _type == "number" then
|
||||
if object ~= object then
|
||||
stream:write(constant_nan)
|
||||
return
|
||||
end
|
||||
if object % 1 == 0 then
|
||||
return 1 + uint_len(object > 0 and object or -object)
|
||||
end
|
||||
-- TODO ensure this check is proper
|
||||
if mantissa % 2^-23 > 0 then
|
||||
return 9
|
||||
end
|
||||
return 5
|
||||
end
|
||||
local id = object_ids[object]
|
||||
if id then
|
||||
return 1 + uint_len(id)
|
||||
end
|
||||
current_id = current_id + 1
|
||||
object_ids[object] = current_id
|
||||
if _type == "string" then
|
||||
local object_len = object:len()
|
||||
return 1 + uint_len(object_len) + object_len
|
||||
end
|
||||
if _type == "table" then
|
||||
if next(object) == nil then
|
||||
-- empty {} table
|
||||
byte(type_ranges.string + 1)
|
||||
return 1
|
||||
end
|
||||
local list_len = #object
|
||||
local kv_len = 0
|
||||
for key, _ in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
kv_len = kv_len + 1
|
||||
end
|
||||
end
|
||||
local table_len = 1 + uint_len(list_len) + uint_len(kv_len)
|
||||
for index = 1, list_len do
|
||||
table_len = table_len + len(self, object[index])
|
||||
end
|
||||
for key, value in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
table_len = table_len + len(self, key) + len(self, value)
|
||||
end
|
||||
end
|
||||
return len
|
||||
end
|
||||
return self.aux_len(object)
|
||||
end
|
||||
|
||||
--: stream any object implementing :write(text)
|
||||
function write(self, object, stream)
|
||||
if object == nil then
|
||||
return
|
||||
end
|
||||
local object_ids = {}
|
||||
local current_id = 0
|
||||
local function byte(byte)
|
||||
stream:write(string.char(byte))
|
||||
end
|
||||
local write_uint = modlib.binary.write_uint
|
||||
local function uint(type, uint)
|
||||
write_uint(byte, uint, uint_widths[type])
|
||||
end
|
||||
local function uint_with_type(base, _uint)
|
||||
local type_offset = uint_type(_uint)
|
||||
byte(base + type_offset)
|
||||
uint(type_offset, _uint)
|
||||
end
|
||||
local write_float = modlib.binary.write_float
|
||||
local function float_on_write(double)
|
||||
byte(double and type_ranges.number or type_ranges.number_f32)
|
||||
end
|
||||
local function float(number)
|
||||
write_float(byte, number, float_on_write)
|
||||
end
|
||||
local aux_write = self.aux_write
|
||||
local function _write(object)
|
||||
local constant = constants[object]
|
||||
if constant then
|
||||
stream:write(constant)
|
||||
return
|
||||
end
|
||||
local _type = type(object)
|
||||
if _type == "number" then
|
||||
if object ~= object then
|
||||
stream:write(constant_nan)
|
||||
return
|
||||
end
|
||||
if object % 1 == 0 then
|
||||
uint_with_type(object > 0 and type_ranges.number_constant or type_ranges.number_negative, object > 0 and object or -object)
|
||||
return
|
||||
end
|
||||
float(object)
|
||||
return
|
||||
end
|
||||
local id = object_ids[object]
|
||||
if id then
|
||||
uint_with_type(type_ranges.table, id)
|
||||
return
|
||||
end
|
||||
if _type == "string" then
|
||||
local len = object:len()
|
||||
current_id = current_id + 1
|
||||
object_ids[object] = current_id
|
||||
uint_with_type(type_ranges.number, len)
|
||||
stream:write(object)
|
||||
return
|
||||
end
|
||||
if _type == "table" then
|
||||
current_id = current_id + 1
|
||||
object_ids[object] = current_id
|
||||
if next(object) == nil then
|
||||
-- empty {} table
|
||||
byte(type_ranges.string + 1)
|
||||
return
|
||||
end
|
||||
local list_len = #object
|
||||
local kv_len = 0
|
||||
for key, _ in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
kv_len = kv_len + 1
|
||||
end
|
||||
end
|
||||
local list_len_sig = uint_type(list_len)
|
||||
local kv_len_sig = uint_type(kv_len)
|
||||
byte(type_ranges.string + list_len_sig + kv_len_sig * 5 + 1)
|
||||
uint(list_len_sig, list_len)
|
||||
uint(kv_len_sig, kv_len)
|
||||
for index = 1, list_len do
|
||||
_write(object[index])
|
||||
end
|
||||
for key, value in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
_write(key)
|
||||
_write(value)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
aux_write(object, object_ids)
|
||||
end
|
||||
_write(object)
|
||||
if object == nil then
|
||||
return
|
||||
end
|
||||
local object_ids = {}
|
||||
local current_id = 0
|
||||
local function byte(byte)
|
||||
stream:write(string.char(byte))
|
||||
end
|
||||
local write_uint = modlib.binary.write_uint
|
||||
local function uint(type, uint)
|
||||
write_uint(byte, uint, uint_widths[type])
|
||||
end
|
||||
local function uint_with_type(base, _uint)
|
||||
local type_offset = uint_type(_uint)
|
||||
byte(base + type_offset)
|
||||
uint(type_offset, _uint)
|
||||
end
|
||||
local write_float = modlib.binary.write_float
|
||||
local function float_on_write(double)
|
||||
byte(double and type_ranges.number or type_ranges.number_f32)
|
||||
end
|
||||
local function float(number)
|
||||
write_float(byte, number, float_on_write)
|
||||
end
|
||||
local aux_write = self.aux_write
|
||||
local function _write(object)
|
||||
local constant = constants[object]
|
||||
if constant then
|
||||
stream:write(constant)
|
||||
return
|
||||
end
|
||||
local _type = type(object)
|
||||
if _type == "number" then
|
||||
if object ~= object then
|
||||
stream:write(constant_nan)
|
||||
return
|
||||
end
|
||||
if object % 1 == 0 then
|
||||
uint_with_type(object > 0 and type_ranges.number_constant or type_ranges.number_negative, object > 0 and object or -object)
|
||||
return
|
||||
end
|
||||
float(object)
|
||||
return
|
||||
end
|
||||
local id = object_ids[object]
|
||||
if id then
|
||||
uint_with_type(type_ranges.table, id)
|
||||
return
|
||||
end
|
||||
if _type == "string" then
|
||||
local len = object:len()
|
||||
current_id = current_id + 1
|
||||
object_ids[object] = current_id
|
||||
uint_with_type(type_ranges.number, len)
|
||||
stream:write(object)
|
||||
return
|
||||
end
|
||||
if _type == "table" then
|
||||
current_id = current_id + 1
|
||||
object_ids[object] = current_id
|
||||
if next(object) == nil then
|
||||
-- empty {} table
|
||||
byte(type_ranges.string + 1)
|
||||
return
|
||||
end
|
||||
local list_len = #object
|
||||
local kv_len = 0
|
||||
for key, _ in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
kv_len = kv_len + 1
|
||||
end
|
||||
end
|
||||
local list_len_sig = uint_type(list_len)
|
||||
local kv_len_sig = uint_type(kv_len)
|
||||
byte(type_ranges.string + list_len_sig + kv_len_sig * 5 + 1)
|
||||
uint(list_len_sig, list_len)
|
||||
uint(kv_len_sig, kv_len)
|
||||
for index = 1, list_len do
|
||||
_write(object[index])
|
||||
end
|
||||
for key, value in pairs(object) do
|
||||
if is_map_key(key, list_len) then
|
||||
_write(key)
|
||||
_write(value)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
aux_write(object, object_ids)
|
||||
end
|
||||
_write(object)
|
||||
end
|
||||
|
||||
local constants_flipped = modlib.table.flip(constants)
|
||||
|
||||
-- See https://www.lua.org/manual/5.1/manual.html#2.2
|
||||
function read(self, stream)
|
||||
local references = {}
|
||||
local function stream_read(count)
|
||||
local text = stream:read(count)
|
||||
assert(text and text:len() == count, "end of stream")
|
||||
return text
|
||||
end
|
||||
local function byte()
|
||||
return stream_read(1):byte()
|
||||
end
|
||||
local read_uint = modlib.binary.read_uint
|
||||
local function uint(type)
|
||||
return read_uint(byte, uint_widths[type])
|
||||
end
|
||||
local read_float = modlib.binary.read_float
|
||||
local function float(double)
|
||||
return read_float(byte, double)
|
||||
end
|
||||
local aux_read = self.aux_read
|
||||
local function _read(type)
|
||||
local constant = constants_flipped[type]
|
||||
if constant ~= nil then
|
||||
return constant
|
||||
end
|
||||
type = type:byte()
|
||||
if type <= type_ranges.number then
|
||||
if type <= type_ranges.number_negative then
|
||||
return uint(type - type_ranges.number_constant)
|
||||
end
|
||||
if type <= type_ranges.number_positive then
|
||||
return -uint(type - type_ranges.number_negative)
|
||||
end
|
||||
return float(type == type_ranges.number)
|
||||
end
|
||||
if type <= type_ranges.string then
|
||||
local string = stream_read(uint(type - type_ranges.number))
|
||||
table.insert(references, string)
|
||||
return string
|
||||
end
|
||||
if type <= type_ranges.table then
|
||||
type = type - type_ranges.string - 1
|
||||
local tab = {}
|
||||
table.insert(references, tab)
|
||||
if type == 0 then
|
||||
return tab
|
||||
end
|
||||
local list_len = uint(type % 5)
|
||||
local kv_len = uint(math.floor(type / 5))
|
||||
for index = 1, list_len do
|
||||
tab[index] = _read(stream_read(1))
|
||||
end
|
||||
for _ = 1, kv_len do
|
||||
tab[_read(stream_read(1))] = _read(stream_read(1))
|
||||
end
|
||||
return tab
|
||||
end
|
||||
if type <= type_ranges.reference then
|
||||
return references[uint(type - type_ranges.table)]
|
||||
end
|
||||
return aux_read(type, stream, references)
|
||||
end
|
||||
local type = stream:read(1)
|
||||
if type == nil then
|
||||
return
|
||||
end
|
||||
return _read(type)
|
||||
local references = {}
|
||||
local function stream_read(count)
|
||||
local text = stream:read(count)
|
||||
assert(text and text:len() == count, "end of stream")
|
||||
return text
|
||||
end
|
||||
local function byte()
|
||||
return stream_read(1):byte()
|
||||
end
|
||||
local read_uint = modlib.binary.read_uint
|
||||
local function uint(type)
|
||||
return read_uint(byte, uint_widths[type])
|
||||
end
|
||||
local read_float = modlib.binary.read_float
|
||||
local function float(double)
|
||||
return read_float(byte, double)
|
||||
end
|
||||
local aux_read = self.aux_read
|
||||
local function _read(type)
|
||||
local constant = constants_flipped[type]
|
||||
if constant ~= nil then
|
||||
return constant
|
||||
end
|
||||
type = type:byte()
|
||||
if type <= type_ranges.number then
|
||||
if type <= type_ranges.number_negative then
|
||||
return uint(type - type_ranges.number_constant)
|
||||
end
|
||||
if type <= type_ranges.number_positive then
|
||||
return -uint(type - type_ranges.number_negative)
|
||||
end
|
||||
return float(type == type_ranges.number)
|
||||
end
|
||||
if type <= type_ranges.string then
|
||||
local string = stream_read(uint(type - type_ranges.number))
|
||||
table.insert(references, string)
|
||||
return string
|
||||
end
|
||||
if type <= type_ranges.table then
|
||||
type = type - type_ranges.string - 1
|
||||
local tab = {}
|
||||
table.insert(references, tab)
|
||||
if type == 0 then
|
||||
return tab
|
||||
end
|
||||
local list_len = uint(type % 5)
|
||||
local kv_len = uint(math.floor(type / 5))
|
||||
for index = 1, list_len do
|
||||
tab[index] = _read(stream_read(1))
|
||||
end
|
||||
for _ = 1, kv_len do
|
||||
tab[_read(stream_read(1))] = _read(stream_read(1))
|
||||
end
|
||||
return tab
|
||||
end
|
||||
if type <= type_ranges.reference then
|
||||
return references[uint(type - type_ranges.table)]
|
||||
end
|
||||
return aux_read(type, stream, references)
|
||||
end
|
||||
local type = stream:read(1)
|
||||
if type == nil then
|
||||
return
|
||||
end
|
||||
return _read(type)
|
||||
end
|
|
@ -1,296 +1,296 @@
|
|||
-- not deprecated
|
||||
function build_tree(dict)
|
||||
local tree = {}
|
||||
for key, value in pairs(dict) do
|
||||
local path = modlib.text.split_unlimited(key, ".")
|
||||
local subtree = tree
|
||||
for i = 1, #path - 1 do
|
||||
local index = tonumber(path[i]) or path[i]
|
||||
subtree[index] = subtree[index] or {}
|
||||
subtree = subtree[index]
|
||||
end
|
||||
subtree[path[#path]] = value
|
||||
end
|
||||
return tree
|
||||
local tree = {}
|
||||
for key, value in pairs(dict) do
|
||||
local path = modlib.text.split_unlimited(key, ".")
|
||||
local subtree = tree
|
||||
for i = 1, #path - 1 do
|
||||
local index = tonumber(path[i]) or path[i]
|
||||
subtree[index] = subtree[index] or {}
|
||||
subtree = subtree[index]
|
||||
end
|
||||
subtree[path[#path]] = value
|
||||
end
|
||||
return tree
|
||||
end
|
||||
if minetest then
|
||||
function build_setting_tree()
|
||||
modlib.conf.settings = build_tree(minetest.settings:to_table())
|
||||
end
|
||||
-- deprecated, use modlib.mod.configuration instead
|
||||
minetest.mkdir(minetest.get_worldpath().."/config")
|
||||
function get_path(confname)
|
||||
return minetest.get_worldpath().."/config/"..confname
|
||||
end
|
||||
function build_setting_tree()
|
||||
modlib.conf.settings = build_tree(minetest.settings:to_table())
|
||||
end
|
||||
-- deprecated, use modlib.mod.configuration instead
|
||||
minetest.mkdir(minetest.get_worldpath().."/config")
|
||||
function get_path(confname)
|
||||
return minetest.get_worldpath().."/config/"..confname
|
||||
end
|
||||
end
|
||||
function read_conf(text)
|
||||
local lines = modlib.text.split_lines(text, nil, true)
|
||||
local dict = {}
|
||||
for i, line in ipairs(lines) do
|
||||
local error_base = "Line " .. (i+1) .. ": "
|
||||
line = modlib.text.trim_left(lines[i])
|
||||
if line ~= "" and line:sub(1,1) ~= "#" then
|
||||
line = modlib.text.split(line, "=", 2)
|
||||
if #line ~= 2 then
|
||||
error(error_base .. "No value given")
|
||||
end
|
||||
local prop = modlib.text.trim_right(line[1])
|
||||
if prop == "" then
|
||||
error(error_base .. "No key given")
|
||||
end
|
||||
local val = modlib.text.trim_left(line[2])
|
||||
if val == "" then
|
||||
error(error_base .. "No value given")
|
||||
end
|
||||
if modlib.text.starts_with(val, '"""') then
|
||||
val = val:sub(3)
|
||||
local total_val = {}
|
||||
local function readMultiline()
|
||||
while i < #lines do
|
||||
if modlib.text.ends_with(val, '"""') then
|
||||
val = val:sub(1, val:len() - 3)
|
||||
return
|
||||
end
|
||||
table.insert(total_val, val)
|
||||
i = i + 1
|
||||
val = lines[i]
|
||||
end
|
||||
i = i - 1
|
||||
error(error_base .. "Unclosed multiline block")
|
||||
end
|
||||
readMultiline()
|
||||
table.insert(total_val, val)
|
||||
val = table.concat(total_val, "\n")
|
||||
else
|
||||
val = modlib.text.trim_right(val)
|
||||
end
|
||||
if dict[prop] then
|
||||
error(error_base .. "Duplicate key")
|
||||
end
|
||||
dict[prop] = val
|
||||
end
|
||||
end
|
||||
return dict
|
||||
local lines = modlib.text.split_lines(text, nil, true)
|
||||
local dict = {}
|
||||
for i, line in ipairs(lines) do
|
||||
local error_base = "Line " .. (i+1) .. ": "
|
||||
line = modlib.text.trim_left(lines[i])
|
||||
if line ~= "" and line:sub(1,1) ~= "#" then
|
||||
line = modlib.text.split(line, "=", 2)
|
||||
if #line ~= 2 then
|
||||
error(error_base .. "No value given")
|
||||
end
|
||||
local prop = modlib.text.trim_right(line[1])
|
||||
if prop == "" then
|
||||
error(error_base .. "No key given")
|
||||
end
|
||||
local val = modlib.text.trim_left(line[2])
|
||||
if val == "" then
|
||||
error(error_base .. "No value given")
|
||||
end
|
||||
if modlib.text.starts_with(val, '"""') then
|
||||
val = val:sub(3)
|
||||
local total_val = {}
|
||||
local function readMultiline()
|
||||
while i < #lines do
|
||||
if modlib.text.ends_with(val, '"""') then
|
||||
val = val:sub(1, val:len() - 3)
|
||||
return
|
||||
end
|
||||
table.insert(total_val, val)
|
||||
i = i + 1
|
||||
val = lines[i]
|
||||
end
|
||||
i = i - 1
|
||||
error(error_base .. "Unclosed multiline block")
|
||||
end
|
||||
readMultiline()
|
||||
table.insert(total_val, val)
|
||||
val = table.concat(total_val, "\n")
|
||||
else
|
||||
val = modlib.text.trim_right(val)
|
||||
end
|
||||
if dict[prop] then
|
||||
error(error_base .. "Duplicate key")
|
||||
end
|
||||
dict[prop] = val
|
||||
end
|
||||
end
|
||||
return dict
|
||||
end
|
||||
function check_config_constraints(config, constraints, handler)
|
||||
local no_error, error_or_retval = pcall(function() check_constraints(config, constraints) end)
|
||||
if not no_error then
|
||||
handler(error_or_retval)
|
||||
end
|
||||
local no_error, error_or_retval = pcall(function() check_constraints(config, constraints) end)
|
||||
if not no_error then
|
||||
handler(error_or_retval)
|
||||
end
|
||||
end
|
||||
function load(filename, constraints)
|
||||
local config = minetest.parse_json(modlib.file.read(filename))
|
||||
if constraints then
|
||||
check_config_constraints(config, constraints, function(message)
|
||||
error('Configuration of file "'..filename.."\" doesn't satisfy constraints: "..message)
|
||||
end)
|
||||
end
|
||||
return config
|
||||
local config = minetest.parse_json(modlib.file.read(filename))
|
||||
if constraints then
|
||||
check_config_constraints(config, constraints, function(message)
|
||||
error('Configuration of file "'..filename.."\" doesn't satisfy constraints: "..message)
|
||||
end)
|
||||
end
|
||||
return config
|
||||
end
|
||||
function load_or_create(filename, replacement_file, constraints)
|
||||
modlib.file.create_if_not_exists_from_file(filename, replacement_file)
|
||||
return load(filename, constraints)
|
||||
modlib.file.create_if_not_exists_from_file(filename, replacement_file)
|
||||
return load(filename, constraints)
|
||||
end
|
||||
function import(modname, constraints, no_settingtypes)
|
||||
local default_config = modlib.mod.get_resource(modname, "default_config.json")
|
||||
local default_conf = minetest.parse_json(modlib.file.read(default_config))
|
||||
local config = load_or_create(get_path(modname)..".json", default_config, constraints)
|
||||
local formats = {
|
||||
{ extension = ".lua", load = minetest.deserialize },
|
||||
{ extension = ".luon", load = function(text) minetest.deserialize("return "..text) end },
|
||||
{ extension = ".conf", load = function(text) return fix_types(build_tree(read_conf(text)), constraints) end }
|
||||
}
|
||||
for _, format in ipairs(formats) do
|
||||
local conf = modlib.file.read(get_path(modname)..format.extension)
|
||||
if conf then
|
||||
config = merge_config(config, format.load(conf))
|
||||
end
|
||||
end
|
||||
if not no_settingtypes then
|
||||
constraints.name = modname
|
||||
local settingtypes = generate_settingtypes(default_conf, constraints)
|
||||
modlib.file.write(modlib.mod.get_resource(modname, "settingtypes.txt"), settingtypes)
|
||||
end
|
||||
local additional_settings = modlib.conf.settings[modname] or {}
|
||||
additional_settings = fix_types(additional_settings, constraints)
|
||||
-- TODO implement merge_config_legal(default_conf, ...)
|
||||
config = merge_config(config, additional_settings)
|
||||
if constraints then
|
||||
check_config_constraints(config, constraints, function(message)
|
||||
error('Configuration of mod "'..modname.."\" doesn't satisfy constraints: "..message)
|
||||
end)
|
||||
end
|
||||
return config
|
||||
local default_config = modlib.mod.get_resource(modname, "default_config.json")
|
||||
local default_conf = minetest.parse_json(modlib.file.read(default_config))
|
||||
local config = load_or_create(get_path(modname)..".json", default_config, constraints)
|
||||
local formats = {
|
||||
{ extension = ".lua", load = minetest.deserialize },
|
||||
{ extension = ".luon", load = function(text) minetest.deserialize("return "..text) end },
|
||||
{ extension = ".conf", load = function(text) return fix_types(build_tree(read_conf(text)), constraints) end }
|
||||
}
|
||||
for _, format in ipairs(formats) do
|
||||
local conf = modlib.file.read(get_path(modname)..format.extension)
|
||||
if conf then
|
||||
config = merge_config(config, format.load(conf))
|
||||
end
|
||||
end
|
||||
if not no_settingtypes then
|
||||
constraints.name = modname
|
||||
local settingtypes = generate_settingtypes(default_conf, constraints)
|
||||
modlib.file.write(modlib.mod.get_resource(modname, "settingtypes.txt"), settingtypes)
|
||||
end
|
||||
local additional_settings = modlib.conf.settings[modname] or {}
|
||||
additional_settings = fix_types(additional_settings, constraints)
|
||||
-- TODO implement merge_config_legal(default_conf, ...)
|
||||
config = merge_config(config, additional_settings)
|
||||
if constraints then
|
||||
check_config_constraints(config, constraints, function(message)
|
||||
error('Configuration of mod "'..modname.."\" doesn't satisfy constraints: "..message)
|
||||
end)
|
||||
end
|
||||
return config
|
||||
end
|
||||
function merge_config(config, additional_settings)
|
||||
if not config or type(additional_settings) ~= "table" then
|
||||
return additional_settings
|
||||
end
|
||||
for setting, value in pairs(additional_settings) do
|
||||
if config[setting] then
|
||||
config[setting] = merge_config(config[setting], value)
|
||||
end
|
||||
end
|
||||
return config
|
||||
if not config or type(additional_settings) ~= "table" then
|
||||
return additional_settings
|
||||
end
|
||||
for setting, value in pairs(additional_settings) do
|
||||
if config[setting] then
|
||||
config[setting] = merge_config(config[setting], value)
|
||||
end
|
||||
end
|
||||
return config
|
||||
end
|
||||
-- format: # comment
|
||||
-- name (Readable name) type type_args
|
||||
function generate_settingtypes(default_conf, constraints)
|
||||
local constraint_type = constraints.type
|
||||
if constraints.children or constraints.possible_children or constraints.required_children or constraints.keys or constraints.values then
|
||||
constraint_type = "table"
|
||||
end
|
||||
local settingtype, type_args
|
||||
local title = constraints.title
|
||||
if not title then
|
||||
title = modlib.text.split(constraints.name, "_")
|
||||
title[1] = modlib.text.upper_first(title[1])
|
||||
title = table.concat(title, " ")
|
||||
end
|
||||
if constraint_type == "boolean" then
|
||||
settingtype = "bool"
|
||||
default_conf = default_conf and "true" or "false"
|
||||
elseif constraint_type == "string" then
|
||||
settingtype = "string"
|
||||
elseif constraint_type == "number" then
|
||||
settingtype = constraints.int and "int" or "float"
|
||||
local range = constraints.range
|
||||
if range then
|
||||
-- TODO consider better max
|
||||
type_args = (constraints.int and "%d %d" or "%f %f"):format(range[1], range[2] or (2 ^ 30))
|
||||
end
|
||||
-- HACK
|
||||
if not default_conf then default_conf = range[1] end
|
||||
elseif constraint_type == "table" then
|
||||
local handled = {}
|
||||
local settings = {}
|
||||
local function setting(key, value_constraints)
|
||||
if handled[key] then
|
||||
return
|
||||
end
|
||||
handled[key] = true
|
||||
value_constraints.name = constraints.name .. "." .. key
|
||||
value_constraints.title = title .. " " .. key
|
||||
table.insert(settings, generate_settingtypes(default_conf and default_conf[key], value_constraints))
|
||||
end
|
||||
for _, table in ipairs{"children", "required_children", "possible_children"} do
|
||||
for key, constraints in pairs(constraints[table] or {}) do
|
||||
setting(key, constraints)
|
||||
end
|
||||
end
|
||||
return table.concat(settings, "\n")
|
||||
end
|
||||
if not constraint_type then
|
||||
return ""
|
||||
end
|
||||
local comment = constraints.comment
|
||||
if comment then
|
||||
comment = "# " .. comment .. "\n"
|
||||
else
|
||||
comment = ""
|
||||
end
|
||||
assert(type(default_conf) == "string" or type(default_conf) == "number" or type(default_conf) == "nil", dump(default_conf))
|
||||
return comment .. constraints.name .. " (" .. title .. ") " .. settingtype .. " " .. (default_conf or "") ..(type_args and (" "..type_args) or "")
|
||||
local constraint_type = constraints.type
|
||||
if constraints.children or constraints.possible_children or constraints.required_children or constraints.keys or constraints.values then
|
||||
constraint_type = "table"
|
||||
end
|
||||
local settingtype, type_args
|
||||
local title = constraints.title
|
||||
if not title then
|
||||
title = modlib.text.split(constraints.name, "_")
|
||||
title[1] = modlib.text.upper_first(title[1])
|
||||
title = table.concat(title, " ")
|
||||
end
|
||||
if constraint_type == "boolean" then
|
||||
settingtype = "bool"
|
||||
default_conf = default_conf and "true" or "false"
|
||||
elseif constraint_type == "string" then
|
||||
settingtype = "string"
|
||||
elseif constraint_type == "number" then
|
||||
settingtype = constraints.int and "int" or "float"
|
||||
local range = constraints.range
|
||||
if range then
|
||||
-- TODO consider better max
|
||||
type_args = (constraints.int and "%d %d" or "%f %f"):format(range[1], range[2] or (2 ^ 30))
|
||||
end
|
||||
-- HACK
|
||||
if not default_conf then default_conf = range[1] end
|
||||
elseif constraint_type == "table" then
|
||||
local handled = {}
|
||||
local settings = {}
|
||||
local function setting(key, value_constraints)
|
||||
if handled[key] then
|
||||
return
|
||||
end
|
||||
handled[key] = true
|
||||
value_constraints.name = constraints.name .. "." .. key
|
||||
value_constraints.title = title .. " " .. key
|
||||
table.insert(settings, generate_settingtypes(default_conf and default_conf[key], value_constraints))
|
||||
end
|
||||
for _, table in ipairs{"children", "required_children", "possible_children"} do
|
||||
for key, constraints in pairs(constraints[table] or {}) do
|
||||
setting(key, constraints)
|
||||
end
|
||||
end
|
||||
return table.concat(settings, "\n")
|
||||
end
|
||||
if not constraint_type then
|
||||
return ""
|
||||
end
|
||||
local comment = constraints.comment
|
||||
if comment then
|
||||
comment = "# " .. comment .. "\n"
|
||||
else
|
||||
comment = ""
|
||||
end
|
||||
assert(type(default_conf) == "string" or type(default_conf) == "number" or type(default_conf) == "nil", dump(default_conf))
|
||||
return comment .. constraints.name .. " (" .. title .. ") " .. settingtype .. " " .. (default_conf or "") ..(type_args and (" "..type_args) or "")
|
||||
end
|
||||
function fix_types(value, constraints)
|
||||
local type = type(value)
|
||||
local expected_type = constraints.type
|
||||
if expected_type and expected_type ~= type then
|
||||
assert(type == "string", "Can't fix non-string value")
|
||||
if expected_type == "boolean" then
|
||||
assert(value == "true" or value == "false", "Not a boolean (true or false): " .. value)
|
||||
value = value == "true"
|
||||
elseif expected_type == "number" then
|
||||
assert(tonumber(value), "Not a number: " .. value)
|
||||
value = tonumber(value)
|
||||
end
|
||||
end
|
||||
if type == "table" then
|
||||
for key, val in pairs(value) do
|
||||
for _, child_constraints in ipairs{"required_children", "children", "possible_children"} do
|
||||
child_constraints = (constraints[child_constraints] or {})[key]
|
||||
if child_constraints then
|
||||
val = fix_types(val, child_constraints)
|
||||
end
|
||||
end
|
||||
if constraints.values then
|
||||
val = fix_types(val, constraints.values)
|
||||
end
|
||||
if constraints.keys then
|
||||
value[key] = nil
|
||||
value[fix_types(key, constraints.keys)] = val
|
||||
else
|
||||
value[key] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
return value
|
||||
local type = type(value)
|
||||
local expected_type = constraints.type
|
||||
if expected_type and expected_type ~= type then
|
||||
assert(type == "string", "Can't fix non-string value")
|
||||
if expected_type == "boolean" then
|
||||
assert(value == "true" or value == "false", "Not a boolean (true or false): " .. value)
|
||||
value = value == "true"
|
||||
elseif expected_type == "number" then
|
||||
assert(tonumber(value), "Not a number: " .. value)
|
||||
value = tonumber(value)
|
||||
end
|
||||
end
|
||||
if type == "table" then
|
||||
for key, val in pairs(value) do
|
||||
for _, child_constraints in ipairs{"required_children", "children", "possible_children"} do
|
||||
child_constraints = (constraints[child_constraints] or {})[key]
|
||||
if child_constraints then
|
||||
val = fix_types(val, child_constraints)
|
||||
end
|
||||
end
|
||||
if constraints.values then
|
||||
val = fix_types(val, constraints.values)
|
||||
end
|
||||
if constraints.keys then
|
||||
value[key] = nil
|
||||
value[fix_types(key, constraints.keys)] = val
|
||||
else
|
||||
value[key] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
function check_constraints(value, constraints)
|
||||
local t = type(value)
|
||||
if constraints.type and constraints.type ~= t then
|
||||
error("Wrong type: Expected "..constraints.type..", found "..t)
|
||||
end
|
||||
if (t == "number" or t == "string") and constraints.range then
|
||||
if value < constraints.range[1] or (constraints.range[2] and value > constraints.range[2]) then
|
||||
error("Not inside range: Expected value >= "..constraints.range[1].." and <= "..(constraints.range[2] or "inf")..", found "..minetest.write_json(value))
|
||||
end
|
||||
end
|
||||
if t == "number" and constraints.int and value % 1 ~= 0 then
|
||||
error("Not an integer number: " .. minetest.write_json(value))
|
||||
end
|
||||
if constraints.possible_values and not constraints.possible_values[value] then
|
||||
error("None of the possible values: Expected one of "..minetest.write_json(modlib.table.keys(constraints.possible_values))..", found "..minetest.write_json(value))
|
||||
end
|
||||
if t == "table" then
|
||||
if constraints.children then
|
||||
for key, val in pairs(value) do
|
||||
local child_constraints = constraints.children[key]
|
||||
if not child_constraints then
|
||||
error("Unexpected table entry: Expected one of "..minetest.write_json(modlib.table.keys(constraints.children))..", found "..minetest.write_json(key))
|
||||
else
|
||||
check_constraints(val, child_constraints)
|
||||
end
|
||||
end
|
||||
for key, _ in pairs(constraints.children) do
|
||||
if value[key] == nil then
|
||||
error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.required_children then
|
||||
for key, value_constraints in pairs(constraints.required_children) do
|
||||
local val = value[key]
|
||||
if val then
|
||||
check_constraints(val, value_constraints)
|
||||
else
|
||||
error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.possible_children then
|
||||
for key, value_constraints in pairs(constraints.possible_children) do
|
||||
local val = value[key]
|
||||
if val then
|
||||
check_constraints(val, value_constraints)
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.keys then
|
||||
for key,_ in pairs(value) do
|
||||
check_constraints(key, constraints.keys)
|
||||
end
|
||||
end
|
||||
if constraints.values then
|
||||
for _, val in pairs(value) do
|
||||
check_constraints(val, constraints.values)
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.func then
|
||||
local possible_errors = constraints.func(value)
|
||||
if possible_errors then
|
||||
error(possible_errors)
|
||||
end
|
||||
end
|
||||
local t = type(value)
|
||||
if constraints.type and constraints.type ~= t then
|
||||
error("Wrong type: Expected "..constraints.type..", found "..t)
|
||||
end
|
||||
if (t == "number" or t == "string") and constraints.range then
|
||||
if value < constraints.range[1] or (constraints.range[2] and value > constraints.range[2]) then
|
||||
error("Not inside range: Expected value >= "..constraints.range[1].." and <= "..(constraints.range[2] or "inf")..", found "..minetest.write_json(value))
|
||||
end
|
||||
end
|
||||
if t == "number" and constraints.int and value % 1 ~= 0 then
|
||||
error("Not an integer number: " .. minetest.write_json(value))
|
||||
end
|
||||
if constraints.possible_values and not constraints.possible_values[value] then
|
||||
error("None of the possible values: Expected one of "..minetest.write_json(modlib.table.keys(constraints.possible_values))..", found "..minetest.write_json(value))
|
||||
end
|
||||
if t == "table" then
|
||||
if constraints.children then
|
||||
for key, val in pairs(value) do
|
||||
local child_constraints = constraints.children[key]
|
||||
if not child_constraints then
|
||||
error("Unexpected table entry: Expected one of "..minetest.write_json(modlib.table.keys(constraints.children))..", found "..minetest.write_json(key))
|
||||
else
|
||||
check_constraints(val, child_constraints)
|
||||
end
|
||||
end
|
||||
for key, _ in pairs(constraints.children) do
|
||||
if value[key] == nil then
|
||||
error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.required_children then
|
||||
for key, value_constraints in pairs(constraints.required_children) do
|
||||
local val = value[key]
|
||||
if val then
|
||||
check_constraints(val, value_constraints)
|
||||
else
|
||||
error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.possible_children then
|
||||
for key, value_constraints in pairs(constraints.possible_children) do
|
||||
local val = value[key]
|
||||
if val then
|
||||
check_constraints(val, value_constraints)
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.keys then
|
||||
for key,_ in pairs(value) do
|
||||
check_constraints(key, constraints.keys)
|
||||
end
|
||||
end
|
||||
if constraints.values then
|
||||
for _, val in pairs(value) do
|
||||
check_constraints(val, constraints.values)
|
||||
end
|
||||
end
|
||||
end
|
||||
if constraints.func then
|
||||
local possible_errors = constraints.func(value)
|
||||
if possible_errors then
|
||||
error(possible_errors)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
local function gather_info()
|
||||
local locals = {}
|
||||
local index = 1
|
||||
while true do
|
||||
local name, value = debug.getlocal(2, index)
|
||||
if not name then break end
|
||||
table.insert(locals, {name, value})
|
||||
index = index + 1
|
||||
end
|
||||
local upvalues = {}
|
||||
local func = debug.getinfo(2).func
|
||||
local envs = getfenv(func)
|
||||
index = 1
|
||||
while true do
|
||||
local name, value = debug.getupvalue(func, index)
|
||||
if not name then break end
|
||||
table.insert(upvalues, {name, value})
|
||||
index = index + 1
|
||||
end
|
||||
return {
|
||||
locals = locals,
|
||||
upvalues = upvalues,
|
||||
[envs == _G and "globals" or "envs"] = envs
|
||||
}
|
||||
end
|
||||
|
||||
local c = 3
|
||||
function test()
|
||||
local a = 1
|
||||
b = 2
|
||||
error(gather_info().upvalues[1][1])
|
||||
end
|
||||
|
||||
test()
|
|
@ -6,7 +6,7 @@ function read(filename)
|
|||
return content
|
||||
end
|
||||
|
||||
function write(filename, new_content)
|
||||
function write_unsafe(filename, new_content)
|
||||
local file = io.open(filename, "w")
|
||||
if file == nil then return false end
|
||||
file:write(new_content)
|
||||
|
@ -14,12 +14,14 @@ function write(filename, new_content)
|
|||
return true
|
||||
end
|
||||
|
||||
write = minetest and minetest.safe_file_write or write_unsafe
|
||||
|
||||
function ensure_content(filename, ensured_content)
|
||||
local content = read(filename)
|
||||
if content ~= ensured_content then
|
||||
return write(filename, ensured_content)
|
||||
end
|
||||
return true
|
||||
local content = read(filename)
|
||||
if content ~= ensured_content then
|
||||
return write(filename, ensured_content)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function append(filename, new_content)
|
||||
|
|
|
@ -7,7 +7,7 @@ end
|
|||
|
||||
function curry_tail(func, ...)
|
||||
local args = { ... }
|
||||
return function(...) return func(..., unpack(args)) end
|
||||
return function(...) return func(unpack(modlib.table.concat({...}, args))) end
|
||||
end
|
||||
|
||||
function call(...)
|
||||
|
@ -33,4 +33,9 @@ function assert(value, callback)
|
|||
if not value then
|
||||
error(callback())
|
||||
end
|
||||
end
|
||||
|
||||
--+ Calls func using the provided arguments, deepcopies all arguments
|
||||
function call_by_value(func, ...)
|
||||
return func(unpack(modlib.table.deepcopy{...}))
|
||||
end
|
|
@ -4,47 +4,47 @@ function less_than(a, b) return a < b end
|
|||
|
||||
--> empty min heap
|
||||
function new(less_than)
|
||||
return setmetatable({less_than = less_than}, metatable)
|
||||
return setmetatable({less_than = less_than}, metatable)
|
||||
end
|
||||
|
||||
function push(self, value)
|
||||
table.insert(self, value)
|
||||
local function heapify(index)
|
||||
if index == 1 then
|
||||
return
|
||||
end
|
||||
local parent = math.floor(index / 2)
|
||||
if self.less_than(self[index], self[parent]) then
|
||||
self[parent], self[index] = self[index], self[parent]
|
||||
heapify(parent)
|
||||
end
|
||||
end
|
||||
heapify(#self)
|
||||
table.insert(self, value)
|
||||
local function heapify(index)
|
||||
if index == 1 then
|
||||
return
|
||||
end
|
||||
local parent = math.floor(index / 2)
|
||||
if self.less_than(self[index], self[parent]) then
|
||||
self[parent], self[index] = self[index], self[parent]
|
||||
heapify(parent)
|
||||
end
|
||||
end
|
||||
heapify(#self)
|
||||
end
|
||||
|
||||
function pop(self)
|
||||
local value = self[1]
|
||||
local last = #self
|
||||
if last == 1 then
|
||||
self[1] = nil
|
||||
return value
|
||||
end
|
||||
self[1], self[last] = self[last], nil
|
||||
last = last - 1
|
||||
local function heapify(index)
|
||||
local left_child = index * 2
|
||||
if left_child > last then
|
||||
return
|
||||
end
|
||||
local smallest_child = left_child + 1
|
||||
if smallest_child > last or self.less_than(self[left_child], self[smallest_child]) then
|
||||
smallest_child = left_child
|
||||
end
|
||||
if self.less_than(self[smallest_child], self[index]) then
|
||||
self[index], self[smallest_child] = self[smallest_child], self[index]
|
||||
heapify(smallest_child)
|
||||
end
|
||||
end
|
||||
heapify(1)
|
||||
return value
|
||||
local value = self[1]
|
||||
local last = #self
|
||||
if last == 1 then
|
||||
self[1] = nil
|
||||
return value
|
||||
end
|
||||
self[1], self[last] = self[last], nil
|
||||
last = last - 1
|
||||
local function heapify(index)
|
||||
local left_child = index * 2
|
||||
if left_child > last then
|
||||
return
|
||||
end
|
||||
local smallest_child = left_child + 1
|
||||
if smallest_child > last or self.less_than(self[left_child], self[smallest_child]) then
|
||||
smallest_child = left_child
|
||||
end
|
||||
if self.less_than(self[smallest_child], self[index]) then
|
||||
self[index], self[smallest_child] = self[smallest_child], self[index]
|
||||
heapify(smallest_child)
|
||||
end
|
||||
end
|
||||
heapify(1)
|
||||
return value
|
||||
end
|
|
@ -1,120 +1,155 @@
|
|||
-- Lua version check
|
||||
if _VERSION then
|
||||
if _VERSION < "Lua 5" then
|
||||
error("Outdated Lua version! modlib requires Lua 5 or greater.")
|
||||
end
|
||||
if _VERSION > "Lua 5.1" then
|
||||
-- not throwing error("Too new Lua version! modlib requires Lua 5.1 or smaller.") anymore
|
||||
unpack = unpack or table.unpack -- unpack was moved to table.unpack in Lua 5.2
|
||||
loadstring = load
|
||||
function setfenv(fn, env)
|
||||
local i = 1
|
||||
while true do
|
||||
name = debug.getupvalue(fn, i)
|
||||
if name == "_ENV" then
|
||||
debug.setupvalue(fn, i, env)
|
||||
break
|
||||
elseif not name then
|
||||
break
|
||||
end
|
||||
end
|
||||
return fn
|
||||
end
|
||||
function getfenv(fn)
|
||||
local i = 1
|
||||
local name, val
|
||||
repeat
|
||||
name, val = debug.getupvalue(fn, i)
|
||||
if name == "_ENV" then
|
||||
return val
|
||||
end
|
||||
i = i + 1
|
||||
until not name
|
||||
end
|
||||
end
|
||||
if _VERSION < "Lua 5" then
|
||||
error("Outdated Lua version! modlib requires Lua 5 or greater.")
|
||||
end
|
||||
if _VERSION > "Lua 5.1" then
|
||||
-- not throwing error("Too new Lua version! modlib requires Lua 5.1 or smaller.") anymore
|
||||
unpack = unpack or table.unpack -- unpack was moved to table.unpack in Lua 5.2
|
||||
loadstring = load
|
||||
function setfenv(fn, env)
|
||||
local i = 1
|
||||
while true do
|
||||
local name = debug.getupvalue(fn, i)
|
||||
if name == "_ENV" then
|
||||
debug.setupvalue(fn, i, env)
|
||||
break
|
||||
elseif not name then
|
||||
break
|
||||
end
|
||||
end
|
||||
return fn
|
||||
end
|
||||
function getfenv(fn)
|
||||
local i = 1
|
||||
local name, val
|
||||
repeat
|
||||
name, val = debug.getupvalue(fn, i)
|
||||
if name == "_ENV" then
|
||||
return val
|
||||
end
|
||||
i = i + 1
|
||||
until not name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
modlib = {
|
||||
dir_delim = rawget(_G, "DIR_DELIM") or "/",
|
||||
_RG = setmetatable({}, {
|
||||
__index = function(_, index)
|
||||
return rawget(_G, index)
|
||||
end,
|
||||
__newindex = function(_, index, value)
|
||||
return rawset(_G, index, value)
|
||||
end
|
||||
})
|
||||
}
|
||||
|
||||
local function get_resource(modname, resource, ...)
|
||||
if not resource then
|
||||
resource = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
return table.concat({minetest.get_modpath(modname), resource, ...}, modlib.dir_delim)
|
||||
end
|
||||
|
||||
local function loadfile_exports(filename)
|
||||
local env = setmetatable({}, {__index = _G})
|
||||
local file = assert(loadfile(filename))
|
||||
setfenv(file, env)
|
||||
file()
|
||||
return env
|
||||
end
|
||||
|
||||
local minetest_only = {
|
||||
mod = true,
|
||||
minetest = true,
|
||||
data = true,
|
||||
log = true,
|
||||
player = true,
|
||||
-- not actually minetest-only, but a deprecated component
|
||||
conf = true
|
||||
}
|
||||
for _, component in ipairs{
|
||||
"mod",
|
||||
"conf",
|
||||
"schema",
|
||||
"data",
|
||||
"file",
|
||||
"func",
|
||||
"log",
|
||||
"math",
|
||||
"player",
|
||||
"table",
|
||||
"text",
|
||||
"vector",
|
||||
"quaternion",
|
||||
"minetest",
|
||||
"trie",
|
||||
"kdtree",
|
||||
"heap",
|
||||
"ranked_set",
|
||||
"binary",
|
||||
"b3d",
|
||||
"bluon"
|
||||
local modules = {}
|
||||
for _, file in pairs{
|
||||
"schema",
|
||||
"file",
|
||||
"func",
|
||||
"math",
|
||||
"table",
|
||||
"text",
|
||||
"vector",
|
||||
"quaternion",
|
||||
"trie",
|
||||
"kdtree",
|
||||
"heap",
|
||||
"ranked_set",
|
||||
"binary",
|
||||
"b3d",
|
||||
"bluon"
|
||||
} do
|
||||
if minetest or not minetest_only[component] then
|
||||
local path = minetest and get_resource(component .. ".lua") or component .. ".lua"
|
||||
modlib[component] = loadfile_exports(path)
|
||||
end
|
||||
modules[file] = file
|
||||
end
|
||||
if minetest then
|
||||
modules.minetest = {
|
||||
"misc",
|
||||
"collisionboxes",
|
||||
"liquid",
|
||||
"raycast",
|
||||
"wielditem_change",
|
||||
"colorspec"
|
||||
}
|
||||
for _, file in pairs{
|
||||
"data",
|
||||
"log",
|
||||
"player",
|
||||
-- deprecated
|
||||
"conf"
|
||||
} do
|
||||
modules[file] = file
|
||||
end
|
||||
end
|
||||
|
||||
local load_module, get_resource, loadfile_exports
|
||||
modlib = setmetatable({
|
||||
-- TODO bump on release
|
||||
version = 62,
|
||||
modname = minetest and minetest.get_current_modname(),
|
||||
dir_delim = rawget(_G, "DIR_DELIM") or "/",
|
||||
_RG = setmetatable({}, {
|
||||
__index = function(_, index)
|
||||
return rawget(_G, index)
|
||||
end,
|
||||
__newindex = function(_, index, value)
|
||||
return rawset(_G, index, value)
|
||||
end
|
||||
}),
|
||||
assertdump = minetest and function(v, value)
|
||||
if not v then
|
||||
error(dump(value), 2)
|
||||
end
|
||||
end
|
||||
}, {
|
||||
__index = function(self, module_name)
|
||||
local files = modules[module_name]
|
||||
local module
|
||||
if type(files) == "string" then
|
||||
module = load_module(files)
|
||||
elseif files then
|
||||
module = loadfile_exports(get_resource(self.modname, module_name, files[1] .. ".lua"))
|
||||
for index = 2, #files do
|
||||
self.mod.include_env(get_resource(self.modname, module_name, files[index] .. ".lua"), module)
|
||||
end
|
||||
end
|
||||
self[module_name] = module
|
||||
return module
|
||||
end
|
||||
})
|
||||
|
||||
function get_resource(modname, resource, ...)
|
||||
if not resource then
|
||||
resource = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
return table.concat({minetest.get_modpath(modname), resource, ...}, modlib.dir_delim)
|
||||
end
|
||||
|
||||
function loadfile_exports(filename)
|
||||
local env = setmetatable({}, {__index = _G})
|
||||
local file = assert(loadfile(filename))
|
||||
setfenv(file, env)
|
||||
file()
|
||||
return env
|
||||
end
|
||||
|
||||
local init_path = arg and arg[0]
|
||||
local parent_dir = init_path and init_path:match"^.[/\\]" or ""
|
||||
function load_module(module_name)
|
||||
local file = module_name .. ".lua"
|
||||
return loadfile_exports(minetest and get_resource(modlib.modname, file) or (parent_dir .. file))
|
||||
end
|
||||
|
||||
modlib.mod = minetest and loadfile_exports(get_resource(modlib.modname, "mod.lua"))
|
||||
|
||||
-- Aliases
|
||||
modlib.string = modlib.text
|
||||
modlib.number = modlib.math
|
||||
|
||||
if minetest then
|
||||
modlib.conf.build_setting_tree()
|
||||
modlib.conf.build_setting_tree()
|
||||
|
||||
modlib.mod.get_resource = get_resource
|
||||
modlib.mod.loadfile_exports = loadfile_exports
|
||||
modlib.mod.get_resource = get_resource
|
||||
modlib.mod.loadfile_exports = loadfile_exports
|
||||
end
|
||||
|
||||
_ml = modlib
|
||||
|
||||
--[[
|
||||
--modlib.mod.include("test.lua")
|
||||
--modlib.mod.include"test.lua"
|
||||
]]
|
||||
|
||||
return modlib
|
|
@ -5,9 +5,9 @@ distance = modlib.vector.distance
|
|||
--: vectors first vector is used to infer the dimension
|
||||
--: distance (vector, other_vector) -> number, default: modlib.vector.distance
|
||||
function new(vectors, distance)
|
||||
assert(#vectors > 0, "vector list must not be empty")
|
||||
local dimension = #vectors[1]
|
||||
local function builder(vectors, axis)
|
||||
assert(#vectors > 0, "vector list must not be empty")
|
||||
local dimension = #vectors[1]
|
||||
local function builder(vectors, axis)
|
||||
if #vectors == 1 then return { value = vectors[1] } end
|
||||
table.sort(vectors, function(a, b) return a[axis] > b[axis] end)
|
||||
local median = math.floor(#vectors / 2)
|
||||
|
@ -19,36 +19,36 @@ function new(vectors, distance)
|
|||
right = builder({ unpack(vectors, median + 1) }, next_axis)
|
||||
}, metatable)
|
||||
end
|
||||
local self = builder(vectors, 1)
|
||||
self.distance = distance
|
||||
return setmetatable(self, metatable)
|
||||
local self = builder(vectors, 1)
|
||||
self.distance = distance
|
||||
return setmetatable(self, metatable)
|
||||
end
|
||||
|
||||
function get_nearest_neighbor(self, vector)
|
||||
local min_distance = math.huge
|
||||
local nearest_neighbor
|
||||
local distance_func = self.distance
|
||||
local function visit(tree)
|
||||
local axis = tree.axis
|
||||
if tree.value ~= nil then
|
||||
local distance = distance_func(tree.value, vector)
|
||||
if distance < min_distance then
|
||||
min_distance = distance
|
||||
nearest_neighbor = tree.value
|
||||
end
|
||||
return
|
||||
else
|
||||
local this_side, other_side = tree.left, tree.right
|
||||
if vector[axis] < tree.pivot[axis] then this_side, other_side = other_side, this_side end
|
||||
visit(this_side)
|
||||
if tree.pivot then
|
||||
local dist = math.abs(tree.pivot[axis] - vector[axis])
|
||||
if dist <= min_distance then visit(other_side) end
|
||||
end
|
||||
end
|
||||
end
|
||||
visit(self)
|
||||
return nearest_neighbor, min_distance
|
||||
local min_distance = math.huge
|
||||
local nearest_neighbor
|
||||
local distance_func = self.distance
|
||||
local function visit(tree)
|
||||
local axis = tree.axis
|
||||
if tree.value ~= nil then
|
||||
local distance = distance_func(tree.value, vector)
|
||||
if distance < min_distance then
|
||||
min_distance = distance
|
||||
nearest_neighbor = tree.value
|
||||
end
|
||||
return
|
||||
else
|
||||
local this_side, other_side = tree.left, tree.right
|
||||
if vector[axis] < tree.pivot[axis] then this_side, other_side = other_side, this_side end
|
||||
visit(this_side)
|
||||
if tree.pivot then
|
||||
local dist = math.abs(tree.pivot[axis] - vector[axis])
|
||||
if dist <= min_distance then visit(other_side) end
|
||||
end
|
||||
end
|
||||
end
|
||||
visit(self)
|
||||
return nearest_neighbor, min_distance
|
||||
end
|
||||
|
||||
-- TODO insertion & deletion + rebalancing
|
|
@ -3,74 +3,74 @@ minetest.mkdir(minetest.get_worldpath() .. "/logs")
|
|||
channels = {}
|
||||
last_day = os.date("%d")
|
||||
function get_path(logname)
|
||||
return minetest.get_worldpath() .. "/logs/" .. logname
|
||||
return minetest.get_worldpath() .. "/logs/" .. logname
|
||||
end
|
||||
function create_channel(title)
|
||||
local dir = get_path(title)
|
||||
minetest.mkdir(dir)
|
||||
channels[title] = {dirname = dir, queue = {}}
|
||||
write(title, "Initialisation")
|
||||
local dir = get_path(title)
|
||||
minetest.mkdir(dir)
|
||||
channels[title] = {dirname = dir, queue = {}}
|
||||
write(title, "Initialisation")
|
||||
end
|
||||
function write(channelname, msg)
|
||||
local channel = channels[channelname]
|
||||
local current_day = os.date("%d")
|
||||
if current_day ~= last_day then
|
||||
last_day = current_day
|
||||
write_to_file(channelname, channel, os.date("%Y-%m-%d"))
|
||||
end
|
||||
table.insert(channel.queue, os.date("[%H:%M:%S] ") .. msg)
|
||||
local channel = channels[channelname]
|
||||
local current_day = os.date("%d")
|
||||
if current_day ~= last_day then
|
||||
last_day = current_day
|
||||
write_to_file(channelname, channel, os.date("%Y-%m-%d"))
|
||||
end
|
||||
table.insert(channel.queue, os.date("[%H:%M:%S] ") .. msg)
|
||||
end
|
||||
function write_to_all(msg)
|
||||
for channelname, _ in pairs(channels) do
|
||||
write(channelname, msg)
|
||||
end
|
||||
for channelname, _ in pairs(channels) do
|
||||
write(channelname, msg)
|
||||
end
|
||||
end
|
||||
function write_to_file(name, channel, current_date)
|
||||
if not channel then
|
||||
channel = channels[name]
|
||||
end
|
||||
if #(channel.queue) > 0 then
|
||||
local filename = channel.dirname .. "/" .. (current_date or os.date("%Y-%m-%d")) .. ".txt"
|
||||
local rope = {}
|
||||
for _, msg in ipairs(channel.queue) do
|
||||
table.insert(rope, msg)
|
||||
end
|
||||
modlib.file.append(filename, table.concat(rope, "\n") .. "\n")
|
||||
channels[name].queue = {}
|
||||
end
|
||||
if not channel then
|
||||
channel = channels[name]
|
||||
end
|
||||
if #(channel.queue) > 0 then
|
||||
local filename = channel.dirname .. "/" .. (current_date or os.date("%Y-%m-%d")) .. ".txt"
|
||||
local rope = {}
|
||||
for _, msg in ipairs(channel.queue) do
|
||||
table.insert(rope, msg)
|
||||
end
|
||||
modlib.file.append(filename, table.concat(rope, "\n") .. "\n")
|
||||
channels[name].queue = {}
|
||||
end
|
||||
end
|
||||
function write_all_to_file()
|
||||
local current_date = os.date("%Y-%m-%d")
|
||||
for name, channel in pairs(channels) do
|
||||
write_to_file(name, channel, current_date)
|
||||
end
|
||||
local current_date = os.date("%Y-%m-%d")
|
||||
for name, channel in pairs(channels) do
|
||||
write_to_file(name, channel, current_date)
|
||||
end
|
||||
end
|
||||
function write_safe(channelname, msg)
|
||||
write(channelname, msg)
|
||||
write_all_to_file()
|
||||
write(channelname, msg)
|
||||
write_all_to_file()
|
||||
end
|
||||
|
||||
local timer = 0
|
||||
|
||||
minetest.register_globalstep(
|
||||
function(dtime)
|
||||
timer = timer + dtime
|
||||
if timer > 5 then
|
||||
write_all_to_file()
|
||||
timer = 0
|
||||
end
|
||||
end
|
||||
function(dtime)
|
||||
timer = timer + dtime
|
||||
if timer > 5 then
|
||||
write_all_to_file()
|
||||
timer = 0
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
minetest.register_on_mods_loaded(
|
||||
function()
|
||||
write_to_all("Mods loaded")
|
||||
end
|
||||
function()
|
||||
write_to_all("Mods loaded")
|
||||
end
|
||||
)
|
||||
|
||||
minetest.register_on_shutdown(
|
||||
function()
|
||||
write_to_all("Shutdown")
|
||||
write_all_to_file()
|
||||
end
|
||||
function()
|
||||
write_to_all("Shutdown")
|
||||
write_all_to_file()
|
||||
end
|
||||
)
|
||||
|
|
|
@ -61,12 +61,11 @@ function fround(number)
|
|||
number = -number
|
||||
end
|
||||
local exp = math.floor(math.log(number, 2))
|
||||
local powexp = 2 ^ math.max(-126, math.min(number, 127))
|
||||
local powexp = 2 ^ math.max(-126, math.min(exp, 127))
|
||||
local leading = exp < -127 and 0 or 1
|
||||
local mantissa = math.floor((leading - number / powexp) * 0x800000 + 0.5)
|
||||
if mantissa <= -0x800000 then
|
||||
return sign * math.huge
|
||||
end
|
||||
mantissa = mantissa / 0x800000
|
||||
return sign * powexp * (leading - mantissa), mantissa
|
||||
return sign * powexp * (leading - mantissa / 0x800000)
|
||||
end
|
|
@ -1,722 +0,0 @@
|
|||
max_wear = 2 ^ 16 - 1
|
||||
function override(function_name, function_builder)
|
||||
local func = minetest[function_name]
|
||||
minetest["original_" .. function_name] = func
|
||||
minetest[function_name] = function_builder(func)
|
||||
end
|
||||
|
||||
-- TODO fix modlib.minetest.get_gametime() messing up responsible "mod" determined by engine on crash
|
||||
get_gametime = minetest.get_gametime
|
||||
local get_gametime_initialized
|
||||
local function get_gametime_init(dtime)
|
||||
if get_gametime_initialized then
|
||||
-- if the profiler is being used, the globalstep can't be unregistered
|
||||
return
|
||||
end
|
||||
get_gametime_initialized = true
|
||||
assert(dtime == 0)
|
||||
local gametime = minetest.get_gametime()
|
||||
assert(gametime)
|
||||
function modlib.minetest.get_gametime()
|
||||
local imprecise_gametime = minetest.get_gametime()
|
||||
if imprecise_gametime > gametime then
|
||||
minetest.log("warning", "modlib.minetest.get_gametime(): Called after increment and before first globalstep")
|
||||
return imprecise_gametime
|
||||
end
|
||||
return gametime
|
||||
end
|
||||
for index, globalstep in pairs(minetest.registered_globalsteps) do
|
||||
if globalstep == get_gametime_init then
|
||||
table.remove(minetest.registered_globalsteps, index)
|
||||
break
|
||||
end
|
||||
end
|
||||
-- globalsteps of mods which depend on modlib will execute after this
|
||||
minetest.register_globalstep(function(dtime)
|
||||
gametime = gametime + dtime
|
||||
end)
|
||||
end
|
||||
minetest.register_globalstep(get_gametime_init)
|
||||
|
||||
delta_times={}
|
||||
delays={}
|
||||
callbacks={}
|
||||
function register_globalstep(interval, callback)
|
||||
if type(callback) ~= "function" then
|
||||
return
|
||||
end
|
||||
table.insert(delta_times, 0)
|
||||
table.insert(delays, interval)
|
||||
table.insert(callbacks, callback)
|
||||
end
|
||||
function texture_modifier_inventorycube(face_1, face_2, face_3)
|
||||
return "[inventorycube{" .. string.gsub(face_1, "%^", "&")
|
||||
.. "{" .. string.gsub(face_2, "%^", "&")
|
||||
.. "{" .. string.gsub(face_3, "%^", "&")
|
||||
end
|
||||
function get_node_inventory_image(nodename)
|
||||
local n = minetest.registered_nodes[nodename]
|
||||
if not n then
|
||||
return
|
||||
end
|
||||
local tiles = {}
|
||||
for l, tile in pairs(n.tiles or {}) do
|
||||
tiles[l] = (type(tile) == "string" and tile) or tile.name
|
||||
end
|
||||
local chosen_tiles = { tiles[1], tiles[3], tiles[5] }
|
||||
if #chosen_tiles == 0 then
|
||||
return false
|
||||
end
|
||||
if not chosen_tiles[2] then
|
||||
chosen_tiles[2] = chosen_tiles[1]
|
||||
end
|
||||
if not chosen_tiles[3] then
|
||||
chosen_tiles[3] = chosen_tiles[2]
|
||||
end
|
||||
local img = minetest.registered_items[nodename].inventory_image
|
||||
if string.len(img) == 0 then
|
||||
img = nil
|
||||
end
|
||||
return img or texture_modifier_inventorycube(chosen_tiles[1], chosen_tiles[2], chosen_tiles[3])
|
||||
end
|
||||
function get_color_int(color)
|
||||
return color.b + (color.g*256) + (color.r*256*256)
|
||||
end
|
||||
function check_player_privs(playername, privtable)
|
||||
local privs=minetest.get_player_privs(playername)
|
||||
local missing_privs={}
|
||||
local to_lose_privs={}
|
||||
for priv, expected_value in pairs(privtable) do
|
||||
local actual_value=privs[priv]
|
||||
if expected_value then
|
||||
if not actual_value then
|
||||
table.insert(missing_privs, priv)
|
||||
end
|
||||
else
|
||||
if actual_value then
|
||||
table.insert(to_lose_privs, priv)
|
||||
end
|
||||
end
|
||||
end
|
||||
return missing_privs, to_lose_privs
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for k, v in pairs(delta_times) do
|
||||
local v=dtime+v
|
||||
if v > delays[k] then
|
||||
callbacks[k](v)
|
||||
v=0
|
||||
end
|
||||
delta_times[k]=v
|
||||
end
|
||||
end)
|
||||
|
||||
form_listeners = {}
|
||||
function register_form_listener(formname, func)
|
||||
local current_listeners = form_listeners[formname] or {}
|
||||
table.insert(current_listeners, func)
|
||||
form_listeners[formname] = current_listeners
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
local handlers = form_listeners[formname]
|
||||
if handlers then
|
||||
for _, handler in pairs(handlers) do
|
||||
handler(player, fields)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--+ Improved base64 decode removing valid padding
|
||||
function decode_base64(base64)
|
||||
local len = base64:len()
|
||||
local padding_char = base64:sub(len, len) == "="
|
||||
if padding_char then
|
||||
if len % 4 ~= 0 then
|
||||
return
|
||||
end
|
||||
if base64:sub(len-1, len-1) == "=" then
|
||||
base64 = base64:sub(1, len-2)
|
||||
else
|
||||
base64 = base64:sub(1, len-1)
|
||||
end
|
||||
end
|
||||
return minetest.decode_base64(base64)
|
||||
end
|
||||
|
||||
liquid_level_max = 8
|
||||
--+ Calculates the corner levels of a flowingliquid node
|
||||
--> 4 corner levels from -0.5 to 0.5 as list of `modlib.vector`
|
||||
function get_liquid_corner_levels(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local source, flowing = def.liquid_alternative_source, node.name
|
||||
local range = def.liquid_range or liquid_level_max
|
||||
local neighbors = {}
|
||||
for x = -1, 1 do
|
||||
neighbors[x] = {}
|
||||
for z = -1, 1 do
|
||||
local neighbor_pos = {x = pos.x + x, y = pos.y, z = pos.z + z}
|
||||
local neighbor_node = minetest.get_node(neighbor_pos)
|
||||
local level
|
||||
if neighbor_node.name == source then
|
||||
level = 1
|
||||
elseif neighbor_node.name == flowing then
|
||||
local neighbor_level = neighbor_node.param2 % 8
|
||||
level = (math.max(0, neighbor_level - liquid_level_max + range) + 0.5) / range
|
||||
end
|
||||
neighbor_pos.y = neighbor_pos.y + 1
|
||||
local node_above = minetest.get_node(neighbor_pos)
|
||||
neighbors[x][z] = {
|
||||
air = neighbor_node.name == "air",
|
||||
level = level,
|
||||
above_is_same_liquid = node_above.name == flowing or node_above.name == source
|
||||
}
|
||||
end
|
||||
end
|
||||
local function get_corner_level(x, z)
|
||||
local air_neighbor
|
||||
local levels = 0
|
||||
local neighbor_count = 0
|
||||
for nx = x - 1, x do
|
||||
for nz = z - 1, z do
|
||||
local neighbor = neighbors[nx][nz]
|
||||
if neighbor.above_is_same_liquid then
|
||||
return 1
|
||||
end
|
||||
local level = neighbor.level
|
||||
if level then
|
||||
if level == 1 then
|
||||
return 1
|
||||
end
|
||||
levels = levels + level
|
||||
neighbor_count = neighbor_count + 1
|
||||
elseif neighbor.air then
|
||||
if air_neighbor then
|
||||
return 0.02
|
||||
end
|
||||
air_neighbor = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if neighbor_count == 0 then
|
||||
return 0
|
||||
end
|
||||
return levels / neighbor_count
|
||||
end
|
||||
local corner_levels = {
|
||||
{0, nil, 0},
|
||||
{1, nil, 0},
|
||||
{1, nil, 1},
|
||||
{0, nil, 1}
|
||||
}
|
||||
for index, corner_level in pairs(corner_levels) do
|
||||
corner_level[2] = get_corner_level(corner_level[1], corner_level[3])
|
||||
corner_levels[index] = modlib.vector.subtract_scalar(modlib.vector.new(corner_level), 0.5)
|
||||
end
|
||||
return corner_levels
|
||||
end
|
||||
|
||||
flowing_downwards = modlib.vector.new{0, -1, 0}
|
||||
--+ Calculates the flow direction of a flowingliquid node
|
||||
--> `modlib.minetest.flowing_downwards = modlib.vector.new{0, -1, 0}` if only flowing downwards
|
||||
--> surface direction as `modlib.vector` else
|
||||
function get_liquid_flow_direction(pos)
|
||||
local corner_levels = get_liquid_corner_levels(pos)
|
||||
local max_level = corner_levels[1][2]
|
||||
for index = 2, 4 do
|
||||
local level = corner_levels[index][2]
|
||||
if level > max_level then
|
||||
max_level = level
|
||||
end
|
||||
end
|
||||
local dir = modlib.vector.new{0, 0, 0}
|
||||
local count = 0
|
||||
for max_level_index, corner_level in pairs(corner_levels) do
|
||||
if corner_level[2] == max_level then
|
||||
for offset = 1, 3 do
|
||||
local index = (max_level_index + offset - 1) % 4 + 1
|
||||
local diff = corner_level - corner_levels[index]
|
||||
if diff[2] ~= 0 then
|
||||
diff[1] = diff[1] * diff[2]
|
||||
diff[3] = diff[3] * diff[2]
|
||||
if offset == 3 then
|
||||
diff = modlib.vector.divide_scalar(diff, math.sqrt(2))
|
||||
end
|
||||
dir = dir + diff
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if count ~= 0 then
|
||||
dir = modlib.vector.divide_scalar(dir, count)
|
||||
end
|
||||
if dir == modlib.vector.new{0, 0, 0} then
|
||||
if minetest.get_node(pos).param2 % 32 > 7 then
|
||||
return flowing_downwards
|
||||
end
|
||||
end
|
||||
return dir
|
||||
end
|
||||
|
||||
--+ Raycast wrapper with proper flowingliquid intersections
|
||||
function raycast(_pos1, _pos2, objects, liquids)
|
||||
local raycast = minetest.raycast(_pos1, _pos2, objects, liquids)
|
||||
if not liquids then
|
||||
return raycast
|
||||
end
|
||||
local pos1 = modlib.vector.from_minetest(_pos1)
|
||||
local _direction = vector.direction(_pos1, _pos2)
|
||||
local direction = modlib.vector.from_minetest(_direction)
|
||||
local length = vector.distance(_pos1, _pos2)
|
||||
local function next()
|
||||
for pointed_thing in raycast do
|
||||
if pointed_thing.type ~= "node" then
|
||||
return pointed_thing
|
||||
end
|
||||
local _pos = pointed_thing.under
|
||||
local pos = modlib.vector.from_minetest(_pos)
|
||||
local node = minetest.get_node(_pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
if not (def and def.drawtype == "flowingliquid") then return pointed_thing end
|
||||
local corner_levels = get_liquid_corner_levels(_pos)
|
||||
local full_corner_levels = true
|
||||
for _, corner_level in pairs(corner_levels) do
|
||||
if corner_level[2] < 0.5 then
|
||||
full_corner_levels = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if full_corner_levels then
|
||||
return pointed_thing
|
||||
end
|
||||
local relative = pos1 - pos
|
||||
local inside = true
|
||||
for _, prop in pairs(relative) do
|
||||
if prop <= -0.5 or prop >= 0.5 then
|
||||
inside = false
|
||||
break
|
||||
end
|
||||
end
|
||||
local function level(x, z)
|
||||
local function distance_squared(corner)
|
||||
return (x - corner[1]) ^ 2 + (z - corner[3]) ^ 2
|
||||
end
|
||||
local irrelevant_corner, distance = 1, distance_squared(corner_levels[1])
|
||||
for index = 2, 4 do
|
||||
local other_distance = distance_squared(corner_levels[index])
|
||||
if other_distance > distance then
|
||||
irrelevant_corner, distance = index, other_distance
|
||||
end
|
||||
end
|
||||
local function corner(off)
|
||||
return corner_levels[((irrelevant_corner + off) % 4) + 1]
|
||||
end
|
||||
local base = corner(2)
|
||||
local edge_1, edge_2 = corner(1) - base, corner(3) - base
|
||||
-- Properly selected edges will have a total length of 2
|
||||
assert(math.abs(edge_1[1] + edge_1[3]) + math.abs(edge_2[1] + edge_2[3]) == 2)
|
||||
if edge_1[1] == 0 then
|
||||
edge_1, edge_2 = edge_2, edge_1
|
||||
end
|
||||
local level = base[2] + (edge_1[2] * ((x - base[1]) / edge_1[1])) + (edge_2[2] * ((z - base[3]) / edge_2[3]))
|
||||
assert(level >= -0.5 and level <= 0.5)
|
||||
return level
|
||||
end
|
||||
inside = inside and (relative[2] < level(relative[1], relative[3]))
|
||||
if inside then
|
||||
-- pos1 is inside the liquid node
|
||||
pointed_thing.intersection_point = _pos1
|
||||
pointed_thing.intersection_normal = vector.new(0, 0, 0)
|
||||
return pointed_thing
|
||||
end
|
||||
local function intersection_normal(axis, dir)
|
||||
return {x = 0, y = 0, z = 0, [axis] = dir}
|
||||
end
|
||||
local function plane(axis, dir)
|
||||
local offset = dir * 0.5
|
||||
local diff_axis = (relative[axis] - offset) / -direction[axis]
|
||||
local intersection_point = {}
|
||||
for plane_axis = 1, 3 do
|
||||
if plane_axis ~= axis then
|
||||
local value = direction[plane_axis] * diff_axis + relative[plane_axis]
|
||||
if value < -0.5 or value > 0.5 then
|
||||
return
|
||||
end
|
||||
intersection_point[plane_axis] = value
|
||||
end
|
||||
end
|
||||
intersection_point[axis] = offset
|
||||
return intersection_point
|
||||
end
|
||||
if direction[2] > 0 then
|
||||
local intersection_point = plane(2, -1)
|
||||
if intersection_point then
|
||||
pointed_thing.intersection_point = (intersection_point + pos):to_minetest()
|
||||
pointed_thing.intersection_normal = intersection_normal("y", -1)
|
||||
return pointed_thing
|
||||
end
|
||||
end
|
||||
for coord, other in pairs{[1] = 3, [3] = 1} do
|
||||
if direction[coord] ~= 0 then
|
||||
local dir = direction[coord] > 0 and -1 or 1
|
||||
local intersection_point = plane(coord, dir)
|
||||
if intersection_point then
|
||||
local height = 0
|
||||
for _, corner in pairs(corner_levels) do
|
||||
if corner[coord] == dir * 0.5 then
|
||||
height = height + (math.abs(intersection_point[other] + corner[other])) * corner[2]
|
||||
end
|
||||
end
|
||||
if intersection_point[2] <= height then
|
||||
pointed_thing.intersection_point = (intersection_point + pos):to_minetest()
|
||||
pointed_thing.intersection_normal = intersection_normal(modlib.vector.index_aliases[coord], dir)
|
||||
return pointed_thing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, triangle in pairs{
|
||||
{corner_levels[3], corner_levels[2], corner_levels[1]},
|
||||
{corner_levels[4], corner_levels[3], corner_levels[1]}
|
||||
} do
|
||||
local pos_on_ray = modlib.vector.ray_triangle_intersection(relative, direction, triangle)
|
||||
if pos_on_ray and pos_on_ray <= length then
|
||||
pointed_thing.intersection_point = (pos1 + modlib.vector.multiply_scalar(direction, pos_on_ray)):to_minetest()
|
||||
pointed_thing.intersection_normal = modlib.vector.triangle_normal(triangle):to_minetest()
|
||||
return pointed_thing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return setmetatable({next = next}, {__call = next})
|
||||
end
|
||||
|
||||
players = {}
|
||||
|
||||
registered_on_wielditem_changes = {function(...)
|
||||
local _, previous_item, _, item = ...
|
||||
if previous_item then
|
||||
((previous_item:get_definition()._modlib or {}).un_wield or modlib.func.no_op)(...)
|
||||
end
|
||||
if item then
|
||||
((item:get_definition()._modlib or {}).on_wield or modlib.func.no_op)(...)
|
||||
end
|
||||
end}
|
||||
|
||||
--+ Registers an on_wielditem_change callback: function(player, previous_item, previous_index, item)
|
||||
--+ Will be called once with player, nil, index, item on join
|
||||
register_on_wielditem_change = modlib.func.curry(table.insert, registered_on_wielditem_changes)
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
-- Other on_joinplayer / on_leaveplayer callbacks should execute first
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local item, index = player:get_wielded_item(), player:get_wield_index()
|
||||
players[player:get_player_name()] = {
|
||||
wield = {
|
||||
item = item,
|
||||
index = index
|
||||
}
|
||||
}
|
||||
modlib.table.icall(registered_on_wielditem_changes, player, nil, index, item)
|
||||
end)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
players[player:get_player_name()] = nil
|
||||
end)
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function()
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local item, index = player:get_wielded_item(), player:get_wield_index()
|
||||
local playerdata = players[player:get_player_name()]
|
||||
local previous_item, previous_index = playerdata.wield.item, playerdata.wield.index
|
||||
if item:get_name() ~= previous_item or index ~= previous_index then
|
||||
playerdata.wield.item = item
|
||||
playerdata.wield.index = index
|
||||
modlib.table.icall(registered_on_wielditem_changes, player, previous_item, previous_index, item)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- As in src/util/string.cpp
|
||||
named_colors = {
|
||||
aliceblue = 0xf0f8ff,
|
||||
antiquewhite = 0xfaebd7,
|
||||
aqua = 0x00ffff,
|
||||
aquamarine = 0x7fffd4,
|
||||
azure = 0xf0ffff,
|
||||
beige = 0xf5f5dc,
|
||||
bisque = 0xffe4c4,
|
||||
black = 0x000000,
|
||||
blanchedalmond = 0xffebcd,
|
||||
blue = 0x0000ff,
|
||||
blueviolet = 0x8a2be2,
|
||||
brown = 0xa52a2a,
|
||||
burlywood = 0xdeb887,
|
||||
cadetblue = 0x5f9ea0,
|
||||
chartreuse = 0x7fff00,
|
||||
chocolate = 0xd2691e,
|
||||
coral = 0xff7f50,
|
||||
cornflowerblue = 0x6495ed,
|
||||
cornsilk = 0xfff8dc,
|
||||
crimson = 0xdc143c,
|
||||
cyan = 0x00ffff,
|
||||
darkblue = 0x00008b,
|
||||
darkcyan = 0x008b8b,
|
||||
darkgoldenrod = 0xb8860b,
|
||||
darkgray = 0xa9a9a9,
|
||||
darkgreen = 0x006400,
|
||||
darkgrey = 0xa9a9a9,
|
||||
darkkhaki = 0xbdb76b,
|
||||
darkmagenta = 0x8b008b,
|
||||
darkolivegreen = 0x556b2f,
|
||||
darkorange = 0xff8c00,
|
||||
darkorchid = 0x9932cc,
|
||||
darkred = 0x8b0000,
|
||||
darksalmon = 0xe9967a,
|
||||
darkseagreen = 0x8fbc8f,
|
||||
darkslateblue = 0x483d8b,
|
||||
darkslategray = 0x2f4f4f,
|
||||
darkslategrey = 0x2f4f4f,
|
||||
darkturquoise = 0x00ced1,
|
||||
darkviolet = 0x9400d3,
|
||||
deeppink = 0xff1493,
|
||||
deepskyblue = 0x00bfff,
|
||||
dimgray = 0x696969,
|
||||
dimgrey = 0x696969,
|
||||
dodgerblue = 0x1e90ff,
|
||||
firebrick = 0xb22222,
|
||||
floralwhite = 0xfffaf0,
|
||||
forestgreen = 0x228b22,
|
||||
fuchsia = 0xff00ff,
|
||||
gainsboro = 0xdcdcdc,
|
||||
ghostwhite = 0xf8f8ff,
|
||||
gold = 0xffd700,
|
||||
goldenrod = 0xdaa520,
|
||||
gray = 0x808080,
|
||||
green = 0x008000,
|
||||
greenyellow = 0xadff2f,
|
||||
grey = 0x808080,
|
||||
honeydew = 0xf0fff0,
|
||||
hotpink = 0xff69b4,
|
||||
indianred = 0xcd5c5c,
|
||||
indigo = 0x4b0082,
|
||||
ivory = 0xfffff0,
|
||||
khaki = 0xf0e68c,
|
||||
lavender = 0xe6e6fa,
|
||||
lavenderblush = 0xfff0f5,
|
||||
lawngreen = 0x7cfc00,
|
||||
lemonchiffon = 0xfffacd,
|
||||
lightblue = 0xadd8e6,
|
||||
lightcoral = 0xf08080,
|
||||
lightcyan = 0xe0ffff,
|
||||
lightgoldenrodyellow = 0xfafad2,
|
||||
lightgray = 0xd3d3d3,
|
||||
lightgreen = 0x90ee90,
|
||||
lightgrey = 0xd3d3d3,
|
||||
lightpink = 0xffb6c1,
|
||||
lightsalmon = 0xffa07a,
|
||||
lightseagreen = 0x20b2aa,
|
||||
lightskyblue = 0x87cefa,
|
||||
lightslategray = 0x778899,
|
||||
lightslategrey = 0x778899,
|
||||
lightsteelblue = 0xb0c4de,
|
||||
lightyellow = 0xffffe0,
|
||||
lime = 0x00ff00,
|
||||
limegreen = 0x32cd32,
|
||||
linen = 0xfaf0e6,
|
||||
magenta = 0xff00ff,
|
||||
maroon = 0x800000,
|
||||
mediumaquamarine = 0x66cdaa,
|
||||
mediumblue = 0x0000cd,
|
||||
mediumorchid = 0xba55d3,
|
||||
mediumpurple = 0x9370db,
|
||||
mediumseagreen = 0x3cb371,
|
||||
mediumslateblue = 0x7b68ee,
|
||||
mediumspringgreen = 0x00fa9a,
|
||||
mediumturquoise = 0x48d1cc,
|
||||
mediumvioletred = 0xc71585,
|
||||
midnightblue = 0x191970,
|
||||
mintcream = 0xf5fffa,
|
||||
mistyrose = 0xffe4e1,
|
||||
moccasin = 0xffe4b5,
|
||||
navajowhite = 0xffdead,
|
||||
navy = 0x000080,
|
||||
oldlace = 0xfdf5e6,
|
||||
olive = 0x808000,
|
||||
olivedrab = 0x6b8e23,
|
||||
orange = 0xffa500,
|
||||
orangered = 0xff4500,
|
||||
orchid = 0xda70d6,
|
||||
palegoldenrod = 0xeee8aa,
|
||||
palegreen = 0x98fb98,
|
||||
paleturquoise = 0xafeeee,
|
||||
palevioletred = 0xdb7093,
|
||||
papayawhip = 0xffefd5,
|
||||
peachpuff = 0xffdab9,
|
||||
peru = 0xcd853f,
|
||||
pink = 0xffc0cb,
|
||||
plum = 0xdda0dd,
|
||||
powderblue = 0xb0e0e6,
|
||||
purple = 0x800080,
|
||||
red = 0xff0000,
|
||||
rosybrown = 0xbc8f8f,
|
||||
royalblue = 0x4169e1,
|
||||
saddlebrown = 0x8b4513,
|
||||
salmon = 0xfa8072,
|
||||
sandybrown = 0xf4a460,
|
||||
seagreen = 0x2e8b57,
|
||||
seashell = 0xfff5ee,
|
||||
sienna = 0xa0522d,
|
||||
silver = 0xc0c0c0,
|
||||
skyblue = 0x87ceeb,
|
||||
slateblue = 0x6a5acd,
|
||||
slategray = 0x708090,
|
||||
slategrey = 0x708090,
|
||||
snow = 0xfffafa,
|
||||
springgreen = 0x00ff7f,
|
||||
steelblue = 0x4682b4,
|
||||
tan = 0xd2b48c,
|
||||
teal = 0x008080,
|
||||
thistle = 0xd8bfd8,
|
||||
tomato = 0xff6347,
|
||||
turquoise = 0x40e0d0,
|
||||
violet = 0xee82ee,
|
||||
wheat = 0xf5deb3,
|
||||
white = 0xffffff,
|
||||
whitesmoke = 0xf5f5f5,
|
||||
yellow = 0xffff00,
|
||||
yellowgreen = 0x9acd32
|
||||
}
|
||||
|
||||
colorspec = {}
|
||||
|
||||
local colorspec_metatable = {__index = colorspec}
|
||||
|
||||
function colorspec.new(table)
|
||||
return setmetatable({
|
||||
r = assert(table.r),
|
||||
g = assert(table.g),
|
||||
b = assert(table.b),
|
||||
a = table.a or 255
|
||||
}, colorspec_metatable)
|
||||
end
|
||||
|
||||
colorspec.from_table = colorspec.new
|
||||
|
||||
function colorspec.from_string(string)
|
||||
local hex = "#([A-Fa-f%d])+"
|
||||
local number, alpha = named_colors[string], 0xFF
|
||||
if not number then
|
||||
local name, alpha_text = string:match("^([a-z])+" .. hex .. "$")
|
||||
assert(alpha_text:len() == 2)
|
||||
number = assert(named_colors[name])
|
||||
alpha = tonumber(alpha_text, 16)
|
||||
end
|
||||
if number then
|
||||
return colorspec.from_number(number * 0x100 + alpha)
|
||||
end
|
||||
local hex_text = string:match(hex)
|
||||
local len, num = hex_text:len(), tonumber(hex_text, 16)
|
||||
if len == 8 then
|
||||
return colorspec.from_number(num)
|
||||
end
|
||||
if len == 6 then
|
||||
return colorspec.from_number(num * 0x100 + 0xFF)
|
||||
end
|
||||
local floor = math.floor
|
||||
if len == 4 then
|
||||
return colorspec.from_table{
|
||||
a = (num % 16) * 17,
|
||||
b = (floor(num / 16) % 16) * 17,
|
||||
g = (floor(num / (16 ^ 2)) % 16) * 17,
|
||||
r = (floor(num / (16 ^ 3)) % 16) * 17
|
||||
}
|
||||
end
|
||||
if len == 3 then
|
||||
return colorspec.from_table{
|
||||
b = (num % 16) * 17,
|
||||
g = (floor(num / 16) % 16) * 17,
|
||||
r = (floor(num / (16 ^ 2)) % 16) * 17
|
||||
}
|
||||
end
|
||||
error("Invalid colorstring: " .. string)
|
||||
end
|
||||
|
||||
colorspec.from_text = colorspec.from_string
|
||||
|
||||
function colorspec.from_number(number)
|
||||
local floor = math.floor
|
||||
return colorspec.from_table{
|
||||
a = number % 0x100,
|
||||
b = floor(number / 0x100) % 0x100,
|
||||
g = floor(number / 0x10000) % 0x100,
|
||||
r = floor(number / 0x1000000)
|
||||
}
|
||||
end
|
||||
|
||||
function colorspec.from_any(value)
|
||||
local type = type(value)
|
||||
if type == "table" then
|
||||
return colorspec.from_table(value)
|
||||
end
|
||||
if type == "string" then
|
||||
return colorspec.from_string(value)
|
||||
end
|
||||
if type == "number" then
|
||||
return colorspec.from_number(value)
|
||||
end
|
||||
error("Unsupported type " .. type)
|
||||
end
|
||||
|
||||
function colorspec:to_table()
|
||||
return self
|
||||
end
|
||||
|
||||
--> hex string, omits alpha if possible (if opaque)
|
||||
function colorspec:to_string()
|
||||
if self.a == 255 then
|
||||
return ("%02X02X02X"):format(self.r, self.g, self.b)
|
||||
end
|
||||
return ("%02X02X02X02X"):format(self.r, self.g, self.b, self.a)
|
||||
end
|
||||
|
||||
function colorspec:to_number()
|
||||
return self.r * 0x1000000 + self.g * 0x10000 + self.b * 0x100 + self.a
|
||||
end
|
||||
|
||||
colorspec_to_colorstring = minetest.colorspec_to_colorstring or function(spec)
|
||||
return colorspec.from_any(spec):to_string()
|
||||
end
|
||||
|
||||
local object_refs = minetest.object_refs
|
||||
--+ Objects inside radius iterator. Uses a linear search.
|
||||
function objects_inside_radius(pos, radius)
|
||||
radius = radius^2
|
||||
local id, object, object_pos
|
||||
return function()
|
||||
repeat
|
||||
id, object = next(object_refs, id)
|
||||
object_pos = object:get_pos()
|
||||
until (not object) or ((pos.x-object_pos.x)^2 + (pos.y-object_pos.y)^2 + (pos.z-object_pos.z)^2) <= radius
|
||||
return object
|
||||
end
|
||||
end
|
||||
|
||||
--+ Objects inside area iterator. Uses a linear search.
|
||||
function objects_inside_area(min, max)
|
||||
local id, object, object_pos
|
||||
return function()
|
||||
repeat
|
||||
id, object = next(object_refs, id)
|
||||
object_pos = object:get_pos()
|
||||
until (not object) or (
|
||||
(min.x <= object_pos.x and min.y <= object_pos.y and min.z <= object_pos.z)
|
||||
and
|
||||
(max.y >= object_pos.x and max.y >= object_pos.y and max.z >= object_pos.z)
|
||||
)
|
||||
return object
|
||||
end
|
||||
end
|
|
@ -0,0 +1,135 @@
|
|||
-- Minetest allows shorthand collisionbox = {...} instead of {{...}}
|
||||
local function get_collisionboxes(box_or_boxes)
|
||||
return type(box_or_boxes[1]) == "number" and {box_or_boxes} or box_or_boxes
|
||||
end
|
||||
|
||||
--> list of collisionboxes in Minetest format
|
||||
function get_node_collisionboxes(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if (not node_def) or node_def.walkable == false then
|
||||
return {}
|
||||
end
|
||||
local boxes = {{-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}}
|
||||
local def_collision_box = node_def.collision_box or (node_def.drawtype == "nodebox" and node_def.node_box)
|
||||
if def_collision_box then
|
||||
local box_type = def_collision_box.type
|
||||
if box_type == "regular" then
|
||||
return boxes
|
||||
end
|
||||
local fixed = def_collision_box.fixed
|
||||
boxes = get_collisionboxes(fixed or {})
|
||||
local paramtype2 = node_def.paramtype2
|
||||
if box_type == "leveled" then
|
||||
boxes = table.copy(boxes)
|
||||
local level = (paramtype2 == "leveled" and node.param2 or node_def.leveled or 0) / 255 - 0.5
|
||||
for _, box in pairs(boxes) do
|
||||
box[5] = level
|
||||
end
|
||||
elseif box_type == "wallmounted" then
|
||||
-- TODO complete if only wall_top is given
|
||||
local dir = minetest.wallmounted_to_dir((paramtype2 == "colorwallmounted" and node.param2 % 8 or node.param2) or 0)
|
||||
local box
|
||||
if dir.y > 0 then
|
||||
box = def_collision_box.wall_top
|
||||
elseif dir.y < 0 then
|
||||
box = def_collision_box.wall_bottom
|
||||
else
|
||||
box = def_collision_box.wall_side
|
||||
if dir.z > 0 then
|
||||
box = {box[3], box[2], -box[4], box[6], box[5], -box[1]}
|
||||
elseif dir.z < 0 then
|
||||
box = {-box[6], box[2], box[1], -box[3], box[5], box[4]}
|
||||
elseif dir.x > 0 then
|
||||
box = {-box[4], box[2], box[3], -box[1], box[5], box[6]}
|
||||
else
|
||||
box = {box[1], box[2], -box[6], box[4], box[5], -box[3]}
|
||||
end
|
||||
end
|
||||
return {assert(box, "incomplete wallmounted collisionbox definition of " .. node.name)}
|
||||
end
|
||||
if box_type == "connected" then
|
||||
boxes = table.copy(boxes)
|
||||
local connect_sides = {
|
||||
top = {x = 0, y = 1, z = 0},
|
||||
bottom = {x = 0, y = -1, z = 0},
|
||||
front = {x = 0, y = 0, z = -1},
|
||||
left = {x = -1, y = 0, z = 0},
|
||||
back = {x = 0, y = 0, z = 1},
|
||||
right = {x = 1, y = 0, z = 0}
|
||||
}
|
||||
if node_def.connect_sides then
|
||||
for side in pairs(connect_sides) do
|
||||
if not node_def.connect_sides[side] then
|
||||
connect_sides[side] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
local function add_collisionbox(key)
|
||||
for _, box in ipairs(get_collisionboxes(def_collision_box[key] or {})) do
|
||||
table.insert(boxes, box)
|
||||
end
|
||||
end
|
||||
local matchers = {}
|
||||
for _, nodename_or_group in pairs(node_def.connects_to or {}) do
|
||||
table.insert(matchers, nodename_matcher(nodename_or_group))
|
||||
end
|
||||
local function connects_to(nodename)
|
||||
for _, matcher in pairs(matchers) do
|
||||
if matcher(nodename) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
local connected, connected_sides
|
||||
for side, direction in pairs(connect_sides) do
|
||||
local neighbor = minetest.get_node(vector.add(pos, direction))
|
||||
local connects = connects_to(neighbor.name)
|
||||
connected = connected or connects
|
||||
connected_sides = connected_sides or (side ~= "top" and side ~= "bottom")
|
||||
add_collisionbox((connects and "connect_" or "disconnected_") .. side)
|
||||
end
|
||||
if not connected then
|
||||
add_collisionbox("disconnected")
|
||||
end
|
||||
if not connected_sides then
|
||||
add_collisionbox("disconnected_sides")
|
||||
end
|
||||
return boxes
|
||||
end
|
||||
if box_type == "fixed" and paramtype2 == "facedir" or paramtype2 == "colorfacedir" then
|
||||
local param2 = paramtype2 == "colorfacedir" and node.param2 % 32 or node.param2 or 0
|
||||
if param2 ~= 0 then
|
||||
boxes = table.copy(boxes)
|
||||
local axis = ({5, 6, 3, 4, 1, 2})[math.floor(param2 / 4) + 1]
|
||||
local other_axis_1, other_axis_2 = (axis % 3) + 1, ((axis + 1) % 3) + 1
|
||||
local rotation = (param2 % 4) / 2 * math.pi
|
||||
local flip = axis > 3
|
||||
if flip then axis = axis - 3; rotation = -rotation end
|
||||
local sin, cos = math.sin(rotation), math.cos(rotation)
|
||||
if axis == 2 then
|
||||
sin = -sin
|
||||
end
|
||||
for _, box in pairs(boxes) do
|
||||
for off = 0, 3, 3 do
|
||||
local axis_1, axis_2 = other_axis_1 + off, other_axis_2 + off
|
||||
local value_1, value_2 = box[axis_1], box[axis_2]
|
||||
box[axis_1] = value_1 * cos - value_2 * sin
|
||||
box[axis_2] = value_1 * sin + value_2 * cos
|
||||
end
|
||||
if not flip then
|
||||
box[axis], box[axis + 3] = -box[axis + 3], -box[axis]
|
||||
end
|
||||
local function fix(coord)
|
||||
if box[coord] > box[coord + 3] then
|
||||
box[coord], box[coord + 3] = box[coord + 3], box[coord]
|
||||
end
|
||||
end
|
||||
fix(other_axis_1)
|
||||
fix(other_axis_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return boxes
|
||||
end
|
|
@ -0,0 +1,266 @@
|
|||
-- As in src/util/string.cpp
|
||||
named_colors = {
|
||||
aliceblue = 0xf0f8ff,
|
||||
antiquewhite = 0xfaebd7,
|
||||
aqua = 0x00ffff,
|
||||
aquamarine = 0x7fffd4,
|
||||
azure = 0xf0ffff,
|
||||
beige = 0xf5f5dc,
|
||||
bisque = 0xffe4c4,
|
||||
black = 0x000000,
|
||||
blanchedalmond = 0xffebcd,
|
||||
blue = 0x0000ff,
|
||||
blueviolet = 0x8a2be2,
|
||||
brown = 0xa52a2a,
|
||||
burlywood = 0xdeb887,
|
||||
cadetblue = 0x5f9ea0,
|
||||
chartreuse = 0x7fff00,
|
||||
chocolate = 0xd2691e,
|
||||
coral = 0xff7f50,
|
||||
cornflowerblue = 0x6495ed,
|
||||
cornsilk = 0xfff8dc,
|
||||
crimson = 0xdc143c,
|
||||
cyan = 0x00ffff,
|
||||
darkblue = 0x00008b,
|
||||
darkcyan = 0x008b8b,
|
||||
darkgoldenrod = 0xb8860b,
|
||||
darkgray = 0xa9a9a9,
|
||||
darkgreen = 0x006400,
|
||||
darkgrey = 0xa9a9a9,
|
||||
darkkhaki = 0xbdb76b,
|
||||
darkmagenta = 0x8b008b,
|
||||
darkolivegreen = 0x556b2f,
|
||||
darkorange = 0xff8c00,
|
||||
darkorchid = 0x9932cc,
|
||||
darkred = 0x8b0000,
|
||||
darksalmon = 0xe9967a,
|
||||
darkseagreen = 0x8fbc8f,
|
||||
darkslateblue = 0x483d8b,
|
||||
darkslategray = 0x2f4f4f,
|
||||
darkslategrey = 0x2f4f4f,
|
||||
darkturquoise = 0x00ced1,
|
||||
darkviolet = 0x9400d3,
|
||||
deeppink = 0xff1493,
|
||||
deepskyblue = 0x00bfff,
|
||||
dimgray = 0x696969,
|
||||
dimgrey = 0x696969,
|
||||
dodgerblue = 0x1e90ff,
|
||||
firebrick = 0xb22222,
|
||||
floralwhite = 0xfffaf0,
|
||||
forestgreen = 0x228b22,
|
||||
fuchsia = 0xff00ff,
|
||||
gainsboro = 0xdcdcdc,
|
||||
ghostwhite = 0xf8f8ff,
|
||||
gold = 0xffd700,
|
||||
goldenrod = 0xdaa520,
|
||||
gray = 0x808080,
|
||||
green = 0x008000,
|
||||
greenyellow = 0xadff2f,
|
||||
grey = 0x808080,
|
||||
honeydew = 0xf0fff0,
|
||||
hotpink = 0xff69b4,
|
||||
indianred = 0xcd5c5c,
|
||||
indigo = 0x4b0082,
|
||||
ivory = 0xfffff0,
|
||||
khaki = 0xf0e68c,
|
||||
lavender = 0xe6e6fa,
|
||||
lavenderblush = 0xfff0f5,
|
||||
lawngreen = 0x7cfc00,
|
||||
lemonchiffon = 0xfffacd,
|
||||
lightblue = 0xadd8e6,
|
||||
lightcoral = 0xf08080,
|
||||
lightcyan = 0xe0ffff,
|
||||
lightgoldenrodyellow = 0xfafad2,
|
||||
lightgray = 0xd3d3d3,
|
||||
lightgreen = 0x90ee90,
|
||||
lightgrey = 0xd3d3d3,
|
||||
lightpink = 0xffb6c1,
|
||||
lightsalmon = 0xffa07a,
|
||||
lightseagreen = 0x20b2aa,
|
||||
lightskyblue = 0x87cefa,
|
||||
lightslategray = 0x778899,
|
||||
lightslategrey = 0x778899,
|
||||
lightsteelblue = 0xb0c4de,
|
||||
lightyellow = 0xffffe0,
|
||||
lime = 0x00ff00,
|
||||
limegreen = 0x32cd32,
|
||||
linen = 0xfaf0e6,
|
||||
magenta = 0xff00ff,
|
||||
maroon = 0x800000,
|
||||
mediumaquamarine = 0x66cdaa,
|
||||
mediumblue = 0x0000cd,
|
||||
mediumorchid = 0xba55d3,
|
||||
mediumpurple = 0x9370db,
|
||||
mediumseagreen = 0x3cb371,
|
||||
mediumslateblue = 0x7b68ee,
|
||||
mediumspringgreen = 0x00fa9a,
|
||||
mediumturquoise = 0x48d1cc,
|
||||
mediumvioletred = 0xc71585,
|
||||
midnightblue = 0x191970,
|
||||
mintcream = 0xf5fffa,
|
||||
mistyrose = 0xffe4e1,
|
||||
moccasin = 0xffe4b5,
|
||||
navajowhite = 0xffdead,
|
||||
navy = 0x000080,
|
||||
oldlace = 0xfdf5e6,
|
||||
olive = 0x808000,
|
||||
olivedrab = 0x6b8e23,
|
||||
orange = 0xffa500,
|
||||
orangered = 0xff4500,
|
||||
orchid = 0xda70d6,
|
||||
palegoldenrod = 0xeee8aa,
|
||||
palegreen = 0x98fb98,
|
||||
paleturquoise = 0xafeeee,
|
||||
palevioletred = 0xdb7093,
|
||||
papayawhip = 0xffefd5,
|
||||
peachpuff = 0xffdab9,
|
||||
peru = 0xcd853f,
|
||||
pink = 0xffc0cb,
|
||||
plum = 0xdda0dd,
|
||||
powderblue = 0xb0e0e6,
|
||||
purple = 0x800080,
|
||||
red = 0xff0000,
|
||||
rosybrown = 0xbc8f8f,
|
||||
royalblue = 0x4169e1,
|
||||
saddlebrown = 0x8b4513,
|
||||
salmon = 0xfa8072,
|
||||
sandybrown = 0xf4a460,
|
||||
seagreen = 0x2e8b57,
|
||||
seashell = 0xfff5ee,
|
||||
sienna = 0xa0522d,
|
||||
silver = 0xc0c0c0,
|
||||
skyblue = 0x87ceeb,
|
||||
slateblue = 0x6a5acd,
|
||||
slategray = 0x708090,
|
||||
slategrey = 0x708090,
|
||||
snow = 0xfffafa,
|
||||
springgreen = 0x00ff7f,
|
||||
steelblue = 0x4682b4,
|
||||
tan = 0xd2b48c,
|
||||
teal = 0x008080,
|
||||
thistle = 0xd8bfd8,
|
||||
tomato = 0xff6347,
|
||||
turquoise = 0x40e0d0,
|
||||
violet = 0xee82ee,
|
||||
wheat = 0xf5deb3,
|
||||
white = 0xffffff,
|
||||
whitesmoke = 0xf5f5f5,
|
||||
yellow = 0xffff00,
|
||||
yellowgreen = 0x9acd32
|
||||
}
|
||||
|
||||
colorspec = {}
|
||||
|
||||
local colorspec_metatable = {__index = colorspec}
|
||||
|
||||
function colorspec.new(table)
|
||||
return setmetatable({
|
||||
r = assert(table.r),
|
||||
g = assert(table.g),
|
||||
b = assert(table.b),
|
||||
a = table.a or 255
|
||||
}, colorspec_metatable)
|
||||
end
|
||||
|
||||
colorspec.from_table = colorspec.new
|
||||
|
||||
function colorspec.from_string(string)
|
||||
local hex = "#([A-Fa-f%d]+)"
|
||||
local number, alpha = named_colors[string], 0xFF
|
||||
if not number then
|
||||
local name, alpha_text = string:match("^([a-z]+)" .. hex .. "$")
|
||||
if name then
|
||||
assert(alpha_text:len() == 2)
|
||||
number = assert(named_colors[name])
|
||||
alpha = tonumber(alpha_text, 16)
|
||||
end
|
||||
end
|
||||
if number then
|
||||
return colorspec.from_number(number * 0x100 + alpha)
|
||||
end
|
||||
local hex_text = string:match(hex)
|
||||
local len, num = hex_text:len(), tonumber(hex_text, 16)
|
||||
if len == 8 then
|
||||
return colorspec.from_number(num)
|
||||
end
|
||||
if len == 6 then
|
||||
return colorspec.from_number(num * 0x100 + 0xFF)
|
||||
end
|
||||
local floor = math.floor
|
||||
if len == 4 then
|
||||
return colorspec.from_table{
|
||||
a = (num % 16) * 17,
|
||||
b = (floor(num / 16) % 16) * 17,
|
||||
g = (floor(num / (16 ^ 2)) % 16) * 17,
|
||||
r = (floor(num / (16 ^ 3)) % 16) * 17
|
||||
}
|
||||
end
|
||||
if len == 3 then
|
||||
return colorspec.from_table{
|
||||
b = (num % 16) * 17,
|
||||
g = (floor(num / 16) % 16) * 17,
|
||||
r = (floor(num / (16 ^ 2)) % 16) * 17
|
||||
}
|
||||
end
|
||||
error("Invalid colorstring: " .. string)
|
||||
end
|
||||
|
||||
colorspec.from_text = colorspec.from_string
|
||||
|
||||
function colorspec.from_number(number)
|
||||
local floor = math.floor
|
||||
return colorspec.from_table{
|
||||
a = number % 0x100,
|
||||
b = floor(number / 0x100) % 0x100,
|
||||
g = floor(number / 0x10000) % 0x100,
|
||||
r = floor(number / 0x1000000)
|
||||
}
|
||||
end
|
||||
|
||||
function colorspec.from_number_rgb(number)
|
||||
local floor = math.floor
|
||||
return colorspec.from_table{
|
||||
a = 0xFF,
|
||||
b = number % 0x100,
|
||||
g = floor(number / 0x100) % 0x100,
|
||||
r = floor(number / 0x10000)
|
||||
}
|
||||
end
|
||||
|
||||
function colorspec.from_any(value)
|
||||
local type = type(value)
|
||||
if type == "table" then
|
||||
return colorspec.from_table(value)
|
||||
end
|
||||
if type == "string" then
|
||||
return colorspec.from_string(value)
|
||||
end
|
||||
if type == "number" then
|
||||
return colorspec.from_number(value)
|
||||
end
|
||||
error("Unsupported type " .. type)
|
||||
end
|
||||
|
||||
function colorspec:to_table()
|
||||
return self
|
||||
end
|
||||
|
||||
--> hex string, omits alpha if possible (if opaque)
|
||||
function colorspec:to_string()
|
||||
if self.a == 255 then
|
||||
return ("%02X02X02X"):format(self.r, self.g, self.b)
|
||||
end
|
||||
return ("%02X02X02X02X"):format(self.r, self.g, self.b, self.a)
|
||||
end
|
||||
|
||||
function colorspec:to_number()
|
||||
return self.r * 0x1000000 + self.g * 0x10000 + self.b * 0x100 + self.a
|
||||
end
|
||||
|
||||
function colorspec:to_number_rgb()
|
||||
return self.r * 0x10000 + self.g * 0x100 + self.b
|
||||
end
|
||||
|
||||
colorspec_to_colorstring = minetest.colorspec_to_colorstring or function(spec)
|
||||
return colorspec.from_any(spec):to_string()
|
||||
end
|
|
@ -0,0 +1,115 @@
|
|||
liquid_level_max = 8
|
||||
--+ Calculates the corner levels of a flowingliquid node
|
||||
--> 4 corner levels from -0.5 to 0.5 as list of `modlib.vector`
|
||||
function get_liquid_corner_levels(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local source, flowing = def.liquid_alternative_source, node.name
|
||||
local range = def.liquid_range or liquid_level_max
|
||||
local neighbors = {}
|
||||
for x = -1, 1 do
|
||||
neighbors[x] = {}
|
||||
for z = -1, 1 do
|
||||
local neighbor_pos = {x = pos.x + x, y = pos.y, z = pos.z + z}
|
||||
local neighbor_node = minetest.get_node(neighbor_pos)
|
||||
local level
|
||||
if neighbor_node.name == source then
|
||||
level = 1
|
||||
elseif neighbor_node.name == flowing then
|
||||
local neighbor_level = neighbor_node.param2 % 8
|
||||
level = (math.max(0, neighbor_level - liquid_level_max + range) + 0.5) / range
|
||||
end
|
||||
neighbor_pos.y = neighbor_pos.y + 1
|
||||
local node_above = minetest.get_node(neighbor_pos)
|
||||
neighbors[x][z] = {
|
||||
air = neighbor_node.name == "air",
|
||||
level = level,
|
||||
above_is_same_liquid = node_above.name == flowing or node_above.name == source
|
||||
}
|
||||
end
|
||||
end
|
||||
local function get_corner_level(x, z)
|
||||
local air_neighbor
|
||||
local levels = 0
|
||||
local neighbor_count = 0
|
||||
for nx = x - 1, x do
|
||||
for nz = z - 1, z do
|
||||
local neighbor = neighbors[nx][nz]
|
||||
if neighbor.above_is_same_liquid then
|
||||
return 1
|
||||
end
|
||||
local level = neighbor.level
|
||||
if level then
|
||||
if level == 1 then
|
||||
return 1
|
||||
end
|
||||
levels = levels + level
|
||||
neighbor_count = neighbor_count + 1
|
||||
elseif neighbor.air then
|
||||
if air_neighbor then
|
||||
return 0.02
|
||||
end
|
||||
air_neighbor = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if neighbor_count == 0 then
|
||||
return 0
|
||||
end
|
||||
return levels / neighbor_count
|
||||
end
|
||||
local corner_levels = {
|
||||
{0, nil, 0},
|
||||
{1, nil, 0},
|
||||
{1, nil, 1},
|
||||
{0, nil, 1}
|
||||
}
|
||||
for index, corner_level in pairs(corner_levels) do
|
||||
corner_level[2] = get_corner_level(corner_level[1], corner_level[3])
|
||||
corner_levels[index] = modlib.vector.subtract_scalar(modlib.vector.new(corner_level), 0.5)
|
||||
end
|
||||
return corner_levels
|
||||
end
|
||||
|
||||
flowing_downwards = modlib.vector.new{0, -1, 0}
|
||||
--+ Calculates the flow direction of a flowingliquid node
|
||||
--> `modlib.minetest.flowing_downwards = modlib.vector.new{0, -1, 0}` if only flowing downwards
|
||||
--> surface direction as `modlib.vector` else
|
||||
function get_liquid_flow_direction(pos)
|
||||
local corner_levels = get_liquid_corner_levels(pos)
|
||||
local max_level = corner_levels[1][2]
|
||||
for index = 2, 4 do
|
||||
local level = corner_levels[index][2]
|
||||
if level > max_level then
|
||||
max_level = level
|
||||
end
|
||||
end
|
||||
local dir = modlib.vector.new{0, 0, 0}
|
||||
local count = 0
|
||||
for max_level_index, corner_level in pairs(corner_levels) do
|
||||
if corner_level[2] == max_level then
|
||||
for offset = 1, 3 do
|
||||
local index = (max_level_index + offset - 1) % 4 + 1
|
||||
local diff = corner_level - corner_levels[index]
|
||||
if diff[2] ~= 0 then
|
||||
diff[1] = diff[1] * diff[2]
|
||||
diff[3] = diff[3] * diff[2]
|
||||
if offset == 3 then
|
||||
diff = modlib.vector.divide_scalar(diff, math.sqrt(2))
|
||||
end
|
||||
dir = dir + diff
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if count ~= 0 then
|
||||
dir = modlib.vector.divide_scalar(dir, count)
|
||||
end
|
||||
if dir == modlib.vector.new{0, 0, 0} then
|
||||
if minetest.get_node(pos).param2 % 32 > 7 then
|
||||
return flowing_downwards
|
||||
end
|
||||
end
|
||||
return dir
|
||||
end
|
|
@ -0,0 +1,197 @@
|
|||
max_wear = 2 ^ 16 - 1
|
||||
function override(function_name, function_builder)
|
||||
local func = minetest[function_name]
|
||||
minetest["original_" .. function_name] = func
|
||||
minetest[function_name] = function_builder(func)
|
||||
end
|
||||
|
||||
-- TODO fix modlib.minetest.get_gametime() messing up responsible "mod" determined by engine on crash
|
||||
get_gametime = minetest.get_gametime
|
||||
local get_gametime_initialized
|
||||
local function get_gametime_init(dtime)
|
||||
if get_gametime_initialized then
|
||||
-- if the profiler is being used, the globalstep can't be unregistered
|
||||
return
|
||||
end
|
||||
get_gametime_initialized = true
|
||||
assert(dtime == 0)
|
||||
local gametime = minetest.get_gametime()
|
||||
assert(gametime)
|
||||
function modlib.minetest.get_gametime()
|
||||
local imprecise_gametime = minetest.get_gametime()
|
||||
if imprecise_gametime > gametime then
|
||||
minetest.log("warning", "modlib.minetest.get_gametime(): Called after increment and before first globalstep")
|
||||
return imprecise_gametime
|
||||
end
|
||||
return gametime
|
||||
end
|
||||
for index, globalstep in pairs(minetest.registered_globalsteps) do
|
||||
if globalstep == get_gametime_init then
|
||||
table.remove(minetest.registered_globalsteps, index)
|
||||
break
|
||||
end
|
||||
end
|
||||
-- globalsteps of mods which depend on modlib will execute after this
|
||||
minetest.register_globalstep(function(dtime)
|
||||
gametime = gametime + dtime
|
||||
end)
|
||||
end
|
||||
minetest.register_globalstep(get_gametime_init)
|
||||
|
||||
delta_times={}
|
||||
delays={}
|
||||
callbacks={}
|
||||
function register_globalstep(interval, callback)
|
||||
if type(callback) ~= "function" then
|
||||
return
|
||||
end
|
||||
table.insert(delta_times, 0)
|
||||
table.insert(delays, interval)
|
||||
table.insert(callbacks, callback)
|
||||
end
|
||||
function texture_modifier_inventorycube(face_1, face_2, face_3)
|
||||
return "[inventorycube{" .. string.gsub(face_1, "%^", "&")
|
||||
.. "{" .. string.gsub(face_2, "%^", "&")
|
||||
.. "{" .. string.gsub(face_3, "%^", "&")
|
||||
end
|
||||
function get_node_inventory_image(nodename)
|
||||
local n = minetest.registered_nodes[nodename]
|
||||
if not n then
|
||||
return
|
||||
end
|
||||
local tiles = {}
|
||||
for l, tile in pairs(n.tiles or {}) do
|
||||
tiles[l] = (type(tile) == "string" and tile) or tile.name
|
||||
end
|
||||
local chosen_tiles = { tiles[1], tiles[3], tiles[5] }
|
||||
if #chosen_tiles == 0 then
|
||||
return false
|
||||
end
|
||||
if not chosen_tiles[2] then
|
||||
chosen_tiles[2] = chosen_tiles[1]
|
||||
end
|
||||
if not chosen_tiles[3] then
|
||||
chosen_tiles[3] = chosen_tiles[2]
|
||||
end
|
||||
local img = minetest.registered_items[nodename].inventory_image
|
||||
if string.len(img) == 0 then
|
||||
img = nil
|
||||
end
|
||||
return img or texture_modifier_inventorycube(chosen_tiles[1], chosen_tiles[2], chosen_tiles[3])
|
||||
end
|
||||
function get_color_int(color)
|
||||
return color.b + (color.g*256) + (color.r*256*256)
|
||||
end
|
||||
function check_player_privs(playername, privtable)
|
||||
local privs=minetest.get_player_privs(playername)
|
||||
local missing_privs={}
|
||||
local to_lose_privs={}
|
||||
for priv, expected_value in pairs(privtable) do
|
||||
local actual_value=privs[priv]
|
||||
if expected_value then
|
||||
if not actual_value then
|
||||
table.insert(missing_privs, priv)
|
||||
end
|
||||
else
|
||||
if actual_value then
|
||||
table.insert(to_lose_privs, priv)
|
||||
end
|
||||
end
|
||||
end
|
||||
return missing_privs, to_lose_privs
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for k, v in pairs(delta_times) do
|
||||
local v=dtime+v
|
||||
if v > delays[k] then
|
||||
callbacks[k](v)
|
||||
v=0
|
||||
end
|
||||
delta_times[k]=v
|
||||
end
|
||||
end)
|
||||
|
||||
form_listeners = {}
|
||||
function register_form_listener(formname, func)
|
||||
local current_listeners = form_listeners[formname] or {}
|
||||
table.insert(current_listeners, func)
|
||||
form_listeners[formname] = current_listeners
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
local handlers = form_listeners[formname]
|
||||
if handlers then
|
||||
for _, handler in pairs(handlers) do
|
||||
handler(player, fields)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--+ Improved base64 decode removing valid padding
|
||||
function decode_base64(base64)
|
||||
local len = base64:len()
|
||||
local padding_char = base64:sub(len, len) == "="
|
||||
if padding_char then
|
||||
if len % 4 ~= 0 then
|
||||
return
|
||||
end
|
||||
if base64:sub(len-1, len-1) == "=" then
|
||||
base64 = base64:sub(1, len-2)
|
||||
else
|
||||
base64 = base64:sub(1, len-1)
|
||||
end
|
||||
end
|
||||
return minetest.decode_base64(base64)
|
||||
end
|
||||
|
||||
local object_refs = minetest.object_refs
|
||||
--+ Objects inside radius iterator. Uses a linear search.
|
||||
function objects_inside_radius(pos, radius)
|
||||
radius = radius^2
|
||||
local id, object, object_pos
|
||||
return function()
|
||||
repeat
|
||||
id, object = next(object_refs, id)
|
||||
object_pos = object:get_pos()
|
||||
until (not object) or ((pos.x-object_pos.x)^2 + (pos.y-object_pos.y)^2 + (pos.z-object_pos.z)^2) <= radius
|
||||
return object
|
||||
end
|
||||
end
|
||||
|
||||
--+ Objects inside area iterator. Uses a linear search.
|
||||
function objects_inside_area(min, max)
|
||||
local id, object, object_pos
|
||||
return function()
|
||||
repeat
|
||||
id, object = next(object_refs, id)
|
||||
object_pos = object:get_pos()
|
||||
until (not object) or (
|
||||
(min.x <= object_pos.x and min.y <= object_pos.y and min.z <= object_pos.z)
|
||||
and
|
||||
(max.y >= object_pos.x and max.y >= object_pos.y and max.z >= object_pos.z)
|
||||
)
|
||||
return object
|
||||
end
|
||||
end
|
||||
|
||||
--: node_or_groupname "modname:nodename", "group:groupname[,groupname]"
|
||||
--> function(nodename) -> whether node matches
|
||||
function nodename_matcher(node_or_groupname)
|
||||
if modlib.text.starts_with(node_or_groupname, "group:") then
|
||||
-- TODO consider using modlib.text.split instead of Minetest's string.split
|
||||
local groups = node_or_groupname:sub(("group:"):len() + 1):split(",")
|
||||
return function(nodename)
|
||||
for _, groupname in pairs(groups) do
|
||||
if minetest.get_item_group(nodename, groupname) == 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
else
|
||||
return function(nodename)
|
||||
return nodename == node_or_groupname
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,132 @@
|
|||
--+ Raycast wrapper with proper flowingliquid intersections
|
||||
function raycast(_pos1, _pos2, objects, liquids)
|
||||
local raycast = minetest.raycast(_pos1, _pos2, objects, liquids)
|
||||
if not liquids then
|
||||
return raycast
|
||||
end
|
||||
local pos1 = modlib.vector.from_minetest(_pos1)
|
||||
local _direction = vector.direction(_pos1, _pos2)
|
||||
local direction = modlib.vector.from_minetest(_direction)
|
||||
local length = vector.distance(_pos1, _pos2)
|
||||
local function next()
|
||||
for pointed_thing in raycast do
|
||||
if pointed_thing.type ~= "node" then
|
||||
return pointed_thing
|
||||
end
|
||||
local _pos = pointed_thing.under
|
||||
local pos = modlib.vector.from_minetest(_pos)
|
||||
local node = minetest.get_node(_pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
if not (def and def.drawtype == "flowingliquid") then return pointed_thing end
|
||||
local corner_levels = get_liquid_corner_levels(_pos)
|
||||
local full_corner_levels = true
|
||||
for _, corner_level in pairs(corner_levels) do
|
||||
if corner_level[2] < 0.5 then
|
||||
full_corner_levels = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if full_corner_levels then
|
||||
return pointed_thing
|
||||
end
|
||||
local relative = pos1 - pos
|
||||
local inside = true
|
||||
for _, prop in pairs(relative) do
|
||||
if prop <= -0.5 or prop >= 0.5 then
|
||||
inside = false
|
||||
break
|
||||
end
|
||||
end
|
||||
local function level(x, z)
|
||||
local function distance_squared(corner)
|
||||
return (x - corner[1]) ^ 2 + (z - corner[3]) ^ 2
|
||||
end
|
||||
local irrelevant_corner, distance = 1, distance_squared(corner_levels[1])
|
||||
for index = 2, 4 do
|
||||
local other_distance = distance_squared(corner_levels[index])
|
||||
if other_distance > distance then
|
||||
irrelevant_corner, distance = index, other_distance
|
||||
end
|
||||
end
|
||||
local function corner(off)
|
||||
return corner_levels[((irrelevant_corner + off) % 4) + 1]
|
||||
end
|
||||
local base = corner(2)
|
||||
local edge_1, edge_2 = corner(1) - base, corner(3) - base
|
||||
-- Properly selected edges will have a total length of 2
|
||||
assert(math.abs(edge_1[1] + edge_1[3]) + math.abs(edge_2[1] + edge_2[3]) == 2)
|
||||
if edge_1[1] == 0 then
|
||||
edge_1, edge_2 = edge_2, edge_1
|
||||
end
|
||||
local level = base[2] + (edge_1[2] * ((x - base[1]) / edge_1[1])) + (edge_2[2] * ((z - base[3]) / edge_2[3]))
|
||||
assert(level >= -0.5 and level <= 0.5)
|
||||
return level
|
||||
end
|
||||
inside = inside and (relative[2] < level(relative[1], relative[3]))
|
||||
if inside then
|
||||
-- pos1 is inside the liquid node
|
||||
pointed_thing.intersection_point = _pos1
|
||||
pointed_thing.intersection_normal = vector.new(0, 0, 0)
|
||||
return pointed_thing
|
||||
end
|
||||
local function intersection_normal(axis, dir)
|
||||
return {x = 0, y = 0, z = 0, [axis] = dir}
|
||||
end
|
||||
local function plane(axis, dir)
|
||||
local offset = dir * 0.5
|
||||
local diff_axis = (relative[axis] - offset) / -direction[axis]
|
||||
local intersection_point = {}
|
||||
for plane_axis = 1, 3 do
|
||||
if plane_axis ~= axis then
|
||||
local value = direction[plane_axis] * diff_axis + relative[plane_axis]
|
||||
if value < -0.5 or value > 0.5 then
|
||||
return
|
||||
end
|
||||
intersection_point[plane_axis] = value
|
||||
end
|
||||
end
|
||||
intersection_point[axis] = offset
|
||||
return intersection_point
|
||||
end
|
||||
if direction[2] > 0 then
|
||||
local intersection_point = plane(2, -1)
|
||||
if intersection_point then
|
||||
pointed_thing.intersection_point = (intersection_point + pos):to_minetest()
|
||||
pointed_thing.intersection_normal = intersection_normal("y", -1)
|
||||
return pointed_thing
|
||||
end
|
||||
end
|
||||
for coord, other in pairs{[1] = 3, [3] = 1} do
|
||||
if direction[coord] ~= 0 then
|
||||
local dir = direction[coord] > 0 and -1 or 1
|
||||
local intersection_point = plane(coord, dir)
|
||||
if intersection_point then
|
||||
local height = 0
|
||||
for _, corner in pairs(corner_levels) do
|
||||
if corner[coord] == dir * 0.5 then
|
||||
height = height + (math.abs(intersection_point[other] + corner[other])) * corner[2]
|
||||
end
|
||||
end
|
||||
if intersection_point[2] <= height then
|
||||
pointed_thing.intersection_point = (intersection_point + pos):to_minetest()
|
||||
pointed_thing.intersection_normal = intersection_normal(modlib.vector.index_aliases[coord], dir)
|
||||
return pointed_thing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, triangle in pairs{
|
||||
{corner_levels[3], corner_levels[2], corner_levels[1]},
|
||||
{corner_levels[4], corner_levels[3], corner_levels[1]}
|
||||
} do
|
||||
local pos_on_ray = modlib.vector.ray_triangle_intersection(relative, direction, triangle)
|
||||
if pos_on_ray and pos_on_ray <= length then
|
||||
pointed_thing.intersection_point = (pos1 + modlib.vector.multiply_scalar(direction, pos_on_ray)):to_minetest()
|
||||
pointed_thing.intersection_normal = modlib.vector.triangle_normal(triangle):to_minetest()
|
||||
return pointed_thing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return setmetatable({next = next}, {__call = next})
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
players = {}
|
||||
|
||||
registered_on_wielditem_changes = {function(...)
|
||||
local _, previous_item, _, item = ...
|
||||
if previous_item then
|
||||
((previous_item:get_definition()._modlib or {}).un_wield or modlib.func.no_op)(...)
|
||||
end
|
||||
if item then
|
||||
((item:get_definition()._modlib or {}).on_wield or modlib.func.no_op)(...)
|
||||
end
|
||||
end}
|
||||
|
||||
--+ Registers an on_wielditem_change callback: function(player, previous_item, previous_index, item)
|
||||
--+ Will be called once with player, nil, index, item on join
|
||||
register_on_wielditem_change = modlib.func.curry(table.insert, registered_on_wielditem_changes)
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
-- Other on_joinplayer / on_leaveplayer callbacks should execute first
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local item, index = player:get_wielded_item(), player:get_wield_index()
|
||||
players[player:get_player_name()] = {
|
||||
wield = {
|
||||
item = item,
|
||||
index = index
|
||||
}
|
||||
}
|
||||
modlib.table.icall(registered_on_wielditem_changes, player, nil, index, item)
|
||||
end)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
players[player:get_player_name()] = nil
|
||||
end)
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function()
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local item, index = player:get_wielded_item(), player:get_wield_index()
|
||||
local playerdata = players[player:get_player_name()]
|
||||
if not playerdata then return end
|
||||
local previous_item, previous_index = playerdata.wield.item, playerdata.wield.index
|
||||
if item:get_name() ~= previous_item or index ~= previous_index then
|
||||
playerdata.wield.item = item
|
||||
playerdata.wield.index = index
|
||||
modlib.table.icall(registered_on_wielditem_changes, player, previous_item, previous_index, item)
|
||||
end
|
||||
end
|
||||
end)
|
|
@ -1,130 +1,131 @@
|
|||
-- get resource + dofile
|
||||
function include(modname, file)
|
||||
if not file then
|
||||
file = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
return dofile(get_resource(modname, file))
|
||||
if not file then
|
||||
file = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
return dofile(get_resource(modname, file))
|
||||
end
|
||||
|
||||
function include_env(file_or_string, env, is_string)
|
||||
setfenv(assert((is_string and loadstring or loadfile)(file_or_string)), env)()
|
||||
setfenv(assert((is_string and loadstring or loadfile)(file_or_string)), env)()
|
||||
end
|
||||
|
||||
function create_namespace(namespace_name, parent_namespace)
|
||||
namespace_name = namespace_name or minetest.get_current_modname()
|
||||
parent_namespace = parent_namespace or _G
|
||||
local metatable = {__index = parent_namespace == _G and function(_, key) return rawget(_G, key) end or parent_namespace}
|
||||
local namespace = {}
|
||||
namespace = setmetatable(namespace, metatable)
|
||||
if parent_namespace == _G then
|
||||
rawset(parent_namespace, namespace_name, namespace)
|
||||
else
|
||||
parent_namespace[namespace_name] = namespace
|
||||
end
|
||||
return namespace
|
||||
namespace_name = namespace_name or minetest.get_current_modname()
|
||||
parent_namespace = parent_namespace or _G
|
||||
local metatable = {__index = parent_namespace == _G and function(_, key) return rawget(_G, key) end or parent_namespace}
|
||||
local namespace = {}
|
||||
namespace = setmetatable(namespace, metatable)
|
||||
if parent_namespace == _G then
|
||||
rawset(parent_namespace, namespace_name, namespace)
|
||||
else
|
||||
parent_namespace[namespace_name] = namespace
|
||||
end
|
||||
return namespace
|
||||
end
|
||||
|
||||
-- formerly extend_mod
|
||||
function extend(modname, file)
|
||||
if not file then
|
||||
file = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
include_env(get_resource(modname, file .. ".lua"), rawget(_G, modname))
|
||||
if not file then
|
||||
file = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
include_env(get_resource(modname, file .. ".lua"), rawget(_G, modname))
|
||||
end
|
||||
|
||||
-- runs main.lua in table env
|
||||
-- formerly include_mod
|
||||
function init(modname)
|
||||
modname = modname or minetest.get_current_modname()
|
||||
create_namespace(modname)
|
||||
extend(modname, "main")
|
||||
modname = modname or minetest.get_current_modname()
|
||||
create_namespace(modname)
|
||||
extend(modname, "main")
|
||||
end
|
||||
|
||||
--! deprecated
|
||||
function extend_string(modname, string)
|
||||
if not string then
|
||||
string = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
include_env(string, rawget(_G, modname), true)
|
||||
if not string then
|
||||
string = modname
|
||||
modname = minetest.get_current_modname()
|
||||
end
|
||||
include_env(string, rawget(_G, modname), true)
|
||||
end
|
||||
|
||||
function configuration(modname)
|
||||
modname = modname or minetest.get_current_modname()
|
||||
local schema = modlib.schema.new(assert(include(modname, "schema.lua")))
|
||||
schema.name = schema.name or modname
|
||||
assert(schema.type == "table")
|
||||
local overrides = {}
|
||||
local conf
|
||||
local function add(path)
|
||||
for _, format in ipairs{
|
||||
{extension = "lua", read = function(text)
|
||||
assert(overrides._C == nil)
|
||||
local additions = setfenv(assert(loadstring(text)), setmetatable(overrides, {__index = {_C = overrides}}))()
|
||||
setmetatable(overrides, nil)
|
||||
if additions == nil then
|
||||
return overrides
|
||||
end
|
||||
return additions
|
||||
end},
|
||||
{extension = "luon", read = function(text)
|
||||
local value = {setfenv(assert(loadstring("return " .. text)), setmetatable(overrides, {}))()}
|
||||
assert(#value == 1)
|
||||
value = value[1]
|
||||
local function check_type(value)
|
||||
local type = type(value)
|
||||
if type == "table" then
|
||||
assert(getmetatable(value) == nil)
|
||||
for key, value in pairs(value) do
|
||||
check_type(key)
|
||||
check_type(value)
|
||||
end
|
||||
elseif not (type == "boolean" or type == "number" or type == "string") then
|
||||
error("disallowed type " .. type)
|
||||
end
|
||||
end
|
||||
check_type(value)
|
||||
return value
|
||||
end},
|
||||
{extension = "conf", read = function(text) return modlib.conf.build_setting_tree(Settings(text):to_table()) end, convert_strings = true},
|
||||
{extension = "json", read = minetest.parse_json}
|
||||
} do
|
||||
local content = modlib.file.read(path .. "." .. format.extension)
|
||||
if content then
|
||||
overrides = modlib.table.deep_add_all(overrides, format.read(content))
|
||||
conf = schema:load(overrides, {convert_strings = format.convert_strings, error_message = true})
|
||||
end
|
||||
end
|
||||
end
|
||||
add(minetest.get_worldpath() .. "/conf/" .. modname)
|
||||
add(get_resource(modname, "conf"))
|
||||
local minetest_conf = modlib.conf.settings[schema.name]
|
||||
if minetest_conf then
|
||||
overrides = modlib.table.deep_add_all(overrides, minetest_conf)
|
||||
conf = schema:load(overrides, {convert_strings = true, error_message = true})
|
||||
end
|
||||
modlib.file.ensure_content(get_resource(modname, "settingtypes.txt"), schema:generate_settingtypes())
|
||||
local readme_path = get_resource(modname, "Readme.md")
|
||||
local readme = modlib.file.read(readme_path)
|
||||
if readme then
|
||||
local modified = false
|
||||
readme = readme:gsub("<!%-%-modlib:conf:(%d)%-%->" .. "(.-)" .. "<!%-%-modlib:conf%-%->", function(level, content)
|
||||
schema._md_level = assert(tonumber(level)) + 1
|
||||
-- HACK: Newline between comment and heading (MD implementations don't handle comments properly)
|
||||
local markdown = "\n" .. schema:generate_markdown()
|
||||
if content ~= markdown then
|
||||
modified = true
|
||||
return "<!--modlib:conf:" .. level .. "-->" .. markdown .. "<!--modlib:conf-->"
|
||||
end
|
||||
end, 1)
|
||||
if modified then
|
||||
assert(modlib.file.write(readme_path, readme))
|
||||
end
|
||||
end
|
||||
if conf == nil then
|
||||
return schema:load({}, {error_message = true})
|
||||
end
|
||||
return conf
|
||||
modname = modname or minetest.get_current_modname()
|
||||
local schema = modlib.schema.new(assert(include(modname, "schema.lua")))
|
||||
schema.name = schema.name or modname
|
||||
local settingtypes = schema:generate_settingtypes()
|
||||
assert(schema.type == "table")
|
||||
local overrides = {}
|
||||
local conf
|
||||
local function add(path)
|
||||
for _, format in ipairs{
|
||||
{extension = "lua", read = function(text)
|
||||
assert(overrides._C == nil)
|
||||
local additions = setfenv(assert(loadstring(text)), setmetatable(overrides, {__index = {_C = overrides}}))()
|
||||
setmetatable(overrides, nil)
|
||||
if additions == nil then
|
||||
return overrides
|
||||
end
|
||||
return additions
|
||||
end},
|
||||
{extension = "luon", read = function(text)
|
||||
local value = {setfenv(assert(loadstring("return " .. text)), setmetatable(overrides, {}))()}
|
||||
assert(#value == 1)
|
||||
value = value[1]
|
||||
local function check_type(value)
|
||||
local type = type(value)
|
||||
if type == "table" then
|
||||
assert(getmetatable(value) == nil)
|
||||
for key, value in pairs(value) do
|
||||
check_type(key)
|
||||
check_type(value)
|
||||
end
|
||||
elseif not (type == "boolean" or type == "number" or type == "string") then
|
||||
error("disallowed type " .. type)
|
||||
end
|
||||
end
|
||||
check_type(value)
|
||||
return value
|
||||
end},
|
||||
{extension = "conf", read = function(text) return modlib.conf.build_setting_tree(Settings(text):to_table()) end, convert_strings = true},
|
||||
{extension = "json", read = minetest.parse_json}
|
||||
} do
|
||||
local content = modlib.file.read(path .. "." .. format.extension)
|
||||
if content then
|
||||
overrides = modlib.table.deep_add_all(overrides, format.read(content))
|
||||
conf = schema:load(overrides, {convert_strings = format.convert_strings, error_message = true})
|
||||
end
|
||||
end
|
||||
end
|
||||
add(minetest.get_worldpath() .. "/conf/" .. modname)
|
||||
add(get_resource(modname, "conf"))
|
||||
local minetest_conf = modlib.conf.settings[schema.name]
|
||||
if minetest_conf then
|
||||
overrides = modlib.table.deep_add_all(overrides, minetest_conf)
|
||||
conf = schema:load(overrides, {convert_strings = true, error_message = true})
|
||||
end
|
||||
modlib.file.ensure_content(get_resource(modname, "settingtypes.txt"), settingtypes)
|
||||
local readme_path = get_resource(modname, "Readme.md")
|
||||
local readme = modlib.file.read(readme_path)
|
||||
if readme then
|
||||
local modified = false
|
||||
readme = readme:gsub("<!%-%-modlib:conf:(%d)%-%->" .. "(.-)" .. "<!%-%-modlib:conf%-%->", function(level, content)
|
||||
schema._md_level = assert(tonumber(level)) + 1
|
||||
-- HACK: Newline between comment and heading (MD implementations don't handle comments properly)
|
||||
local markdown = "\n" .. schema:generate_markdown()
|
||||
if content ~= markdown then
|
||||
modified = true
|
||||
return "<!--modlib:conf:" .. level .. "-->" .. markdown .. "<!--modlib:conf-->"
|
||||
end
|
||||
end, 1)
|
||||
if modified then
|
||||
assert(modlib.file.write(readme_path, readme))
|
||||
end
|
||||
end
|
||||
if conf == nil then
|
||||
return schema:load({}, {error_message = true})
|
||||
end
|
||||
return conf
|
||||
end
|
|
@ -5,8 +5,8 @@ function from_euler_rotation(rotation)
|
|||
local cos = vector.apply(rotation, math.cos)
|
||||
local sin = vector.apply(rotation, math.sin)
|
||||
return {
|
||||
sin.z * cos.x * cos.y - cos.z * sin.x * sin.y,
|
||||
cos.z * sin.x * cos.y + sin.z * cos.x * sin.y,
|
||||
sin.z * cos.x * cos.y - cos.z * sin.x * sin.y,
|
||||
cos.z * sin.x * cos.y + sin.z * cos.x * sin.y,
|
||||
cos.z * cos.x * sin.y - sin.z * sin.x * cos.y,
|
||||
cos.z * cos.x * cos.y + sin.z * sin.x * sin.y
|
||||
}
|
||||
|
@ -76,24 +76,24 @@ end
|
|||
|
||||
--> {x = pitch, y = yaw, z = roll} euler rotation in degrees
|
||||
function to_euler_rotation(self)
|
||||
local rotation = {}
|
||||
local rotation = {}
|
||||
|
||||
local sinr_cosp = 2 * (self[4] * self[1] + self[2] * self[3])
|
||||
local cosr_cosp = 1 - 2 * (self[1] ^ 2 + self[2] ^ 2)
|
||||
rotation.x = math.atan2(sinr_cosp, cosr_cosp)
|
||||
local sinr_cosp = 2 * (self[4] * self[1] + self[2] * self[3])
|
||||
local cosr_cosp = 1 - 2 * (self[1] ^ 2 + self[2] ^ 2)
|
||||
rotation.x = math.atan2(sinr_cosp, cosr_cosp)
|
||||
|
||||
local sinp = 2 * (self[4] * self[2] - self[3] * self[1])
|
||||
if sinp <= -1 then
|
||||
rotation.z = -math.pi/2
|
||||
elseif sinp >= 1 then
|
||||
rotation.z = math.pi/2
|
||||
else
|
||||
rotation.z = math.asin(sinp)
|
||||
local sinp = 2 * (self[4] * self[2] - self[3] * self[1])
|
||||
if sinp <= -1 then
|
||||
rotation.y = -math.pi/2
|
||||
elseif sinp >= 1 then
|
||||
rotation.y = math.pi/2
|
||||
else
|
||||
rotation.y = math.asin(sinp)
|
||||
end
|
||||
|
||||
local siny_cosp = 2 * (self[4] * self[3] + self[1] * self[2])
|
||||
local cosy_cosp = 1 - 2 * (self[2] ^ 2 + self[3] ^ 2)
|
||||
rotation.y = math.atan2(siny_cosp, cosy_cosp)
|
||||
local siny_cosp = 2 * (self[4] * self[3] + self[1] * self[2])
|
||||
local cosy_cosp = 1 - 2 * (self[2] ^ 2 + self[3] ^ 2)
|
||||
rotation.z = math.atan2(siny_cosp, cosy_cosp)
|
||||
|
||||
return vector.apply(rotation, math.deg)
|
||||
end
|
||||
|
|
|
@ -5,308 +5,308 @@ comparator = modlib.table.default_comparator
|
|||
|
||||
--+ Uses a weight-balanced binary tree
|
||||
function new(comparator)
|
||||
return setmetatable({comparator = comparator, root = {total = 0}}, metatable)
|
||||
return setmetatable({comparator = comparator, root = {total = 0}}, metatable)
|
||||
end
|
||||
|
||||
function len(self)
|
||||
return self.root.total
|
||||
return self.root.total
|
||||
end
|
||||
metatable.__len = len
|
||||
|
||||
function is_empty(self)
|
||||
return len(self) == 0
|
||||
return len(self) == 0
|
||||
end
|
||||
|
||||
local function insert_all(tree, _table)
|
||||
if tree.left then
|
||||
insert_all(tree.left, _table)
|
||||
end
|
||||
table.insert(_table, tree.key)
|
||||
if tree.right then
|
||||
insert_all(tree.right, _table)
|
||||
end
|
||||
if tree.left then
|
||||
insert_all(tree.left, _table)
|
||||
end
|
||||
table.insert(_table, tree.key)
|
||||
if tree.right then
|
||||
insert_all(tree.right, _table)
|
||||
end
|
||||
end
|
||||
|
||||
function to_table(self)
|
||||
local table = {}
|
||||
if not is_empty(self) then
|
||||
insert_all(self.root, table)
|
||||
end
|
||||
return table
|
||||
local table = {}
|
||||
if not is_empty(self) then
|
||||
insert_all(self.root, table)
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
--> iterator: function() -> `rank, key` with ascending rank
|
||||
function ipairs(self, min, max)
|
||||
if is_empty(self) then
|
||||
return function() end
|
||||
end
|
||||
min = min or 1
|
||||
local tree = self.root
|
||||
local current_rank = (tree.left and tree.left.total or 0) + 1
|
||||
repeat
|
||||
if min == current_rank then
|
||||
break
|
||||
end
|
||||
local left, right = tree.left, tree.right
|
||||
if min < current_rank then
|
||||
current_rank = current_rank - (left and left.right and left.right.total or 0) - 1
|
||||
tree = left
|
||||
else
|
||||
current_rank = current_rank + (right and right.left and right.left.total or 0) + 1
|
||||
tree = right
|
||||
end
|
||||
until not tree
|
||||
max = max or len(self)
|
||||
local to_visit = {tree}
|
||||
tree = nil
|
||||
local rank = min - 1
|
||||
local function next()
|
||||
if not tree then
|
||||
local len = #to_visit
|
||||
if len == 0 then return end
|
||||
tree = to_visit[len]
|
||||
to_visit[len] = nil
|
||||
else
|
||||
while tree.left do
|
||||
table.insert(to_visit, tree)
|
||||
tree = tree.left
|
||||
end
|
||||
end
|
||||
local key = tree.key
|
||||
tree = tree.right
|
||||
return key
|
||||
end
|
||||
return function()
|
||||
if rank >= max then
|
||||
return
|
||||
end
|
||||
local key = next()
|
||||
if key == nil then
|
||||
return
|
||||
end
|
||||
rank = rank + 1
|
||||
return rank, key
|
||||
end
|
||||
if is_empty(self) then
|
||||
return function() end
|
||||
end
|
||||
min = min or 1
|
||||
local tree = self.root
|
||||
local current_rank = (tree.left and tree.left.total or 0) + 1
|
||||
repeat
|
||||
if min == current_rank then
|
||||
break
|
||||
end
|
||||
local left, right = tree.left, tree.right
|
||||
if min < current_rank then
|
||||
current_rank = current_rank - (left and left.right and left.right.total or 0) - 1
|
||||
tree = left
|
||||
else
|
||||
current_rank = current_rank + (right and right.left and right.left.total or 0) + 1
|
||||
tree = right
|
||||
end
|
||||
until not tree
|
||||
max = max or len(self)
|
||||
local to_visit = {tree}
|
||||
tree = nil
|
||||
local rank = min - 1
|
||||
local function next()
|
||||
if not tree then
|
||||
local len = #to_visit
|
||||
if len == 0 then return end
|
||||
tree = to_visit[len]
|
||||
to_visit[len] = nil
|
||||
else
|
||||
while tree.left do
|
||||
table.insert(to_visit, tree)
|
||||
tree = tree.left
|
||||
end
|
||||
end
|
||||
local key = tree.key
|
||||
tree = tree.right
|
||||
return key
|
||||
end
|
||||
return function()
|
||||
if rank >= max then
|
||||
return
|
||||
end
|
||||
local key = next()
|
||||
if key == nil then
|
||||
return
|
||||
end
|
||||
rank = rank + 1
|
||||
return rank, key
|
||||
end
|
||||
end
|
||||
|
||||
local function _right_rotation(parent, right, left)
|
||||
local new_parent = parent[left]
|
||||
parent[left] = new_parent[right]
|
||||
new_parent[right] = parent
|
||||
parent.total = (parent[left] and parent[left].total or 0) + (parent[right] and parent[right].total or 0) + 1
|
||||
assert(parent.total > 0 or (parent.left == nil and parent.right == nil))
|
||||
new_parent.total = (new_parent[left] and new_parent[left].total or 0) + parent.total + 1
|
||||
return new_parent
|
||||
local new_parent = parent[left]
|
||||
parent[left] = new_parent[right]
|
||||
new_parent[right] = parent
|
||||
parent.total = (parent[left] and parent[left].total or 0) + (parent[right] and parent[right].total or 0) + 1
|
||||
assert(parent.total > 0 or (parent.left == nil and parent.right == nil))
|
||||
new_parent.total = (new_parent[left] and new_parent[left].total or 0) + parent.total + 1
|
||||
return new_parent
|
||||
end
|
||||
|
||||
local function right_rotation(parent)
|
||||
return _right_rotation(parent, "right", "left")
|
||||
return _right_rotation(parent, "right", "left")
|
||||
end
|
||||
|
||||
local function left_rotation(parent)
|
||||
return _right_rotation(parent, "left", "right")
|
||||
return _right_rotation(parent, "left", "right")
|
||||
end
|
||||
|
||||
local function _rebalance(parent)
|
||||
local left_count, right_count = (parent.left and parent.left.total or 0), (parent.right and parent.right.total or 0)
|
||||
if right_count > 1 and left_count * 2 < right_count then
|
||||
return left_rotation(parent)
|
||||
end
|
||||
if left_count > 1 and right_count * 2 < left_count then
|
||||
return right_rotation(parent)
|
||||
end
|
||||
return parent
|
||||
local left_count, right_count = (parent.left and parent.left.total or 0), (parent.right and parent.right.total or 0)
|
||||
if right_count > 1 and left_count * 2 < right_count then
|
||||
return left_rotation(parent)
|
||||
end
|
||||
if left_count > 1 and right_count * 2 < left_count then
|
||||
return right_rotation(parent)
|
||||
end
|
||||
return parent
|
||||
end
|
||||
|
||||
-- Rebalances a parent chain
|
||||
local function rebalance(self, len, parents, sides)
|
||||
if len <= 1 then
|
||||
return
|
||||
end
|
||||
for i = len, 2, -1 do
|
||||
parents[i] = _rebalance(parents[i])
|
||||
parents[i - 1][sides[i - 1]] = parents[i]
|
||||
end
|
||||
self.root = parents[1]
|
||||
if len <= 1 then
|
||||
return
|
||||
end
|
||||
for i = len, 2, -1 do
|
||||
parents[i] = _rebalance(parents[i])
|
||||
parents[i - 1][sides[i - 1]] = parents[i]
|
||||
end
|
||||
self.root = parents[1]
|
||||
end
|
||||
|
||||
local function _insert(self, key, replace)
|
||||
assert(key ~= nil)
|
||||
if is_empty(self) then
|
||||
self.root = {key = key, total = 1}
|
||||
return
|
||||
end
|
||||
local comparator = self.comparator
|
||||
local parents, sides = {}, {}
|
||||
local tree = self.root
|
||||
repeat
|
||||
local tree_key = tree.key
|
||||
local compared = comparator(key, tree_key)
|
||||
if compared == 0 then
|
||||
if replace then
|
||||
tree.key = key
|
||||
return tree_key
|
||||
end
|
||||
return
|
||||
end
|
||||
table.insert(parents, tree)
|
||||
local side = compared < 0 and "left" or "right"
|
||||
table.insert(sides, side)
|
||||
tree = tree[side]
|
||||
until not tree
|
||||
local len = #parents
|
||||
parents[len][sides[len]] = {key = key, total = 1}
|
||||
for _, parent in pairs(parents) do
|
||||
parent.total = parent.total + 1
|
||||
end
|
||||
rebalance(self, len, parents, sides)
|
||||
assert(key ~= nil)
|
||||
if is_empty(self) then
|
||||
self.root = {key = key, total = 1}
|
||||
return
|
||||
end
|
||||
local comparator = self.comparator
|
||||
local parents, sides = {}, {}
|
||||
local tree = self.root
|
||||
repeat
|
||||
local tree_key = tree.key
|
||||
local compared = comparator(key, tree_key)
|
||||
if compared == 0 then
|
||||
if replace then
|
||||
tree.key = key
|
||||
return tree_key
|
||||
end
|
||||
return
|
||||
end
|
||||
table.insert(parents, tree)
|
||||
local side = compared < 0 and "left" or "right"
|
||||
table.insert(sides, side)
|
||||
tree = tree[side]
|
||||
until not tree
|
||||
local len = #parents
|
||||
parents[len][sides[len]] = {key = key, total = 1}
|
||||
for _, parent in pairs(parents) do
|
||||
parent.total = parent.total + 1
|
||||
end
|
||||
rebalance(self, len, parents, sides)
|
||||
end
|
||||
|
||||
function insert(self, key)
|
||||
return _insert(self, key)
|
||||
return _insert(self, key)
|
||||
end
|
||||
|
||||
function insert_or_replace(self, key)
|
||||
return _insert(self, key, true)
|
||||
return _insert(self, key, true)
|
||||
end
|
||||
|
||||
local function _delete(self, key, is_rank)
|
||||
assert(key ~= nil)
|
||||
if is_empty(self) then
|
||||
return
|
||||
end
|
||||
local comparator = self.comparator
|
||||
local parents, sides = {}, {}
|
||||
local tree = self.root
|
||||
local rank = (tree.left and tree.left.total or 0) + 1
|
||||
repeat
|
||||
local tree_key = tree.key
|
||||
local compared
|
||||
if is_rank then
|
||||
if key == rank then
|
||||
compared = 0
|
||||
elseif key < rank then
|
||||
rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1
|
||||
compared = -1
|
||||
else
|
||||
rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1
|
||||
compared = 1
|
||||
end
|
||||
else
|
||||
compared = comparator(key, tree_key)
|
||||
end
|
||||
if compared == 0 then
|
||||
local len = #parents
|
||||
local left, right = tree.left, tree.right
|
||||
if left then
|
||||
tree.total = tree.total - 1
|
||||
if right then
|
||||
-- Obtain successor
|
||||
local side = left.total > right.total and "left" or "right"
|
||||
local other_side = side == "left" and "right" or "left"
|
||||
local sidemost = tree[side]
|
||||
while sidemost[other_side] do
|
||||
sidemost.total = sidemost.total - 1
|
||||
table.insert(parents, sidemost)
|
||||
table.insert(sides, other_side)
|
||||
sidemost = sidemost[other_side]
|
||||
end
|
||||
-- Replace deleted key
|
||||
tree.key = rightmost.key
|
||||
-- Replace the successor by it's single child
|
||||
parents[len][sides[len]] = sidemost[side]
|
||||
else
|
||||
if len == 0 then
|
||||
self.root = left or {total = 0}
|
||||
else
|
||||
parents[len][sides[len]] = left
|
||||
end
|
||||
end
|
||||
elseif right then
|
||||
if len == 0 then
|
||||
self.root = right or {total = 0}
|
||||
else
|
||||
tree.total = tree.total - 1
|
||||
parents[len][sides[len]] = right
|
||||
end
|
||||
else
|
||||
if len == 0 then
|
||||
self.root = {total = 0}
|
||||
else
|
||||
parents[len][sides[len]] = nil
|
||||
end
|
||||
end
|
||||
for _, parent in pairs(parents) do
|
||||
parent.total = parent.total - 1
|
||||
end
|
||||
rebalance(self, len, parents, sides)
|
||||
if is_rank then
|
||||
return tree_key
|
||||
end
|
||||
return rank, tree_key
|
||||
end
|
||||
table.insert(parents, tree)
|
||||
local side
|
||||
if compared < 0 then
|
||||
side = "left"
|
||||
else
|
||||
side = "right"
|
||||
end
|
||||
table.insert(sides, side)
|
||||
tree = tree[side]
|
||||
until not tree
|
||||
assert(key ~= nil)
|
||||
if is_empty(self) then
|
||||
return
|
||||
end
|
||||
local comparator = self.comparator
|
||||
local parents, sides = {}, {}
|
||||
local tree = self.root
|
||||
local rank = (tree.left and tree.left.total or 0) + 1
|
||||
repeat
|
||||
local tree_key = tree.key
|
||||
local compared
|
||||
if is_rank then
|
||||
if key == rank then
|
||||
compared = 0
|
||||
elseif key < rank then
|
||||
rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1
|
||||
compared = -1
|
||||
else
|
||||
rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1
|
||||
compared = 1
|
||||
end
|
||||
else
|
||||
compared = comparator(key, tree_key)
|
||||
end
|
||||
if compared == 0 then
|
||||
local len = #parents
|
||||
local left, right = tree.left, tree.right
|
||||
if left then
|
||||
tree.total = tree.total - 1
|
||||
if right then
|
||||
-- Obtain successor
|
||||
local side = left.total > right.total and "left" or "right"
|
||||
local other_side = side == "left" and "right" or "left"
|
||||
local sidemost = tree[side]
|
||||
while sidemost[other_side] do
|
||||
sidemost.total = sidemost.total - 1
|
||||
table.insert(parents, sidemost)
|
||||
table.insert(sides, other_side)
|
||||
sidemost = sidemost[other_side]
|
||||
end
|
||||
-- Replace deleted key
|
||||
tree.key = rightmost.key
|
||||
-- Replace the successor by it's single child
|
||||
parents[len][sides[len]] = sidemost[side]
|
||||
else
|
||||
if len == 0 then
|
||||
self.root = left or {total = 0}
|
||||
else
|
||||
parents[len][sides[len]] = left
|
||||
end
|
||||
end
|
||||
elseif right then
|
||||
if len == 0 then
|
||||
self.root = right or {total = 0}
|
||||
else
|
||||
tree.total = tree.total - 1
|
||||
parents[len][sides[len]] = right
|
||||
end
|
||||
else
|
||||
if len == 0 then
|
||||
self.root = {total = 0}
|
||||
else
|
||||
parents[len][sides[len]] = nil
|
||||
end
|
||||
end
|
||||
for _, parent in pairs(parents) do
|
||||
parent.total = parent.total - 1
|
||||
end
|
||||
rebalance(self, len, parents, sides)
|
||||
if is_rank then
|
||||
return tree_key
|
||||
end
|
||||
return rank, tree_key
|
||||
end
|
||||
table.insert(parents, tree)
|
||||
local side
|
||||
if compared < 0 then
|
||||
side = "left"
|
||||
else
|
||||
side = "right"
|
||||
end
|
||||
table.insert(sides, side)
|
||||
tree = tree[side]
|
||||
until not tree
|
||||
end
|
||||
|
||||
function delete(self, key)
|
||||
return _delete(self, key)
|
||||
return _delete(self, key)
|
||||
end
|
||||
|
||||
delete_by_key = delete
|
||||
|
||||
function delete_by_rank(self, rank)
|
||||
return _delete(self, rank, true)
|
||||
return _delete(self, rank, true)
|
||||
end
|
||||
|
||||
--> `rank, key` if the key was found
|
||||
--> `rank` the key would have if inserted
|
||||
function get(self, key)
|
||||
if is_empty(self) then return end
|
||||
local comparator = self.comparator
|
||||
local tree = self.root
|
||||
local rank = (tree.left and tree.left.total or 0) + 1
|
||||
while tree do
|
||||
local compared = comparator(key, tree.key)
|
||||
if compared == 0 then
|
||||
return rank, tree.key
|
||||
end
|
||||
if compared < 0 then
|
||||
rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1
|
||||
tree = tree.left
|
||||
else
|
||||
rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1
|
||||
tree = tree.right
|
||||
end
|
||||
end
|
||||
return rank
|
||||
if is_empty(self) then return end
|
||||
local comparator = self.comparator
|
||||
local tree = self.root
|
||||
local rank = (tree.left and tree.left.total or 0) + 1
|
||||
while tree do
|
||||
local compared = comparator(key, tree.key)
|
||||
if compared == 0 then
|
||||
return rank, tree.key
|
||||
end
|
||||
if compared < 0 then
|
||||
rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1
|
||||
tree = tree.left
|
||||
else
|
||||
rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1
|
||||
tree = tree.right
|
||||
end
|
||||
end
|
||||
return rank
|
||||
end
|
||||
|
||||
get_by_key = get
|
||||
|
||||
--> key
|
||||
function get_by_rank(self, rank)
|
||||
local tree = self.root
|
||||
local current_rank = (tree.left and tree.left.total or 0) + 1
|
||||
repeat
|
||||
if rank == current_rank then
|
||||
return tree.key
|
||||
end
|
||||
local left, right = tree.left, tree.right
|
||||
if rank < current_rank then
|
||||
current_rank = current_rank - (left and left.right and left.right.total or 0) - 1
|
||||
tree = left
|
||||
else
|
||||
current_rank = current_rank + (right and right.left and right.left.total or 0) + 1
|
||||
tree = right
|
||||
end
|
||||
until not tree
|
||||
local tree = self.root
|
||||
local current_rank = (tree.left and tree.left.total or 0) + 1
|
||||
repeat
|
||||
if rank == current_rank then
|
||||
return tree.key
|
||||
end
|
||||
local left, right = tree.left, tree.right
|
||||
if rank < current_rank then
|
||||
current_rank = current_rank - (left and left.right and left.right.total or 0) - 1
|
||||
tree = left
|
||||
else
|
||||
current_rank = current_rank + (right and right.left and right.left.total or 0) + 1
|
||||
tree = right
|
||||
end
|
||||
until not tree
|
||||
end
|
|
@ -1,291 +1,295 @@
|
|||
local schema = getfenv(1)
|
||||
|
||||
function new(def)
|
||||
-- TODO type inference, sanity checking etc.
|
||||
return setmetatable(def, {__index = schema})
|
||||
-- TODO type inference, sanity checking etc.
|
||||
return setmetatable(def, {__index = schema})
|
||||
end
|
||||
|
||||
local function field_name_to_title(name)
|
||||
local title = modlib.text.split(name, "_")
|
||||
title[1] = modlib.text.upper_first(title[1])
|
||||
return table.concat(title, " ")
|
||||
local title = modlib.text.split(name, "_")
|
||||
title[1] = modlib.text.upper_first(title[1])
|
||||
return table.concat(title, " ")
|
||||
end
|
||||
|
||||
function generate_settingtypes(self)
|
||||
local typ = self.type
|
||||
local settingtype, type_args
|
||||
self.title = self.title or field_name_to_title(self.name)
|
||||
self._level = self._level or 0
|
||||
local default = self.default
|
||||
if typ == "boolean" then
|
||||
settingtype = "bool"
|
||||
default = default and "true" or "false"
|
||||
elseif typ == "string" then
|
||||
settingtype = "string"
|
||||
elseif typ == "number" then
|
||||
settingtype = self.int and "int" or "float"
|
||||
if self.min or self.max then
|
||||
-- TODO handle exclusive min/max
|
||||
type_args = (self.int and "%d %d" or "%f %f"):format(self.min or (2 ^ -30), self.max or (2 ^ 30))
|
||||
end
|
||||
elseif typ == "table" then
|
||||
local settings = {}
|
||||
if self._level > 0 then
|
||||
-- HACK: Minetest automatically adds the modname
|
||||
-- TODO simple names (not modname.field.other_field)
|
||||
settings = {"[" .. table.concat(modlib.table.repetition("*", self._level)) .. self.name .. "]"}
|
||||
end
|
||||
local function setting(key, value_scheme)
|
||||
assert(not key:find("[=%.%s]"))
|
||||
value_scheme.name = self.name .. "." .. key
|
||||
value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key)
|
||||
value_scheme._level = self._level + 1
|
||||
table.insert(settings, generate_settingtypes(value_scheme))
|
||||
end
|
||||
local keys = {}
|
||||
for key in pairs(self.entries or {}) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
table.sort(keys)
|
||||
for _, key in ipairs(keys) do
|
||||
setting(key, self.entries[key])
|
||||
end
|
||||
return table.concat(settings, "\n")
|
||||
end
|
||||
if not typ then
|
||||
return ""
|
||||
end
|
||||
local description = self.description
|
||||
-- TODO extend description by range etc.?
|
||||
-- TODO enum etc. support
|
||||
if description then
|
||||
if type(description) ~= "table" then
|
||||
description = {description}
|
||||
end
|
||||
description = "# " .. table.concat(description, "\n# ") .. "\n"
|
||||
else
|
||||
description = ""
|
||||
end
|
||||
return description .. self.name .. " (" .. self.title .. ") " .. settingtype .. " " .. (default or "") .. (type_args and (" " .. type_args) or "")
|
||||
local typ = self.type
|
||||
local settingtype, type_args
|
||||
self.title = self.title or field_name_to_title(self.name)
|
||||
self._level = self._level or 0
|
||||
local default = self.default
|
||||
if typ == "boolean" then
|
||||
settingtype = "bool"
|
||||
default = default and "true" or "false"
|
||||
elseif typ == "string" then
|
||||
settingtype = "string"
|
||||
elseif typ == "number" then
|
||||
settingtype = self.int and "int" or "float"
|
||||
if self.min or self.max then
|
||||
-- TODO handle exclusive min/max
|
||||
type_args = (self.int and "%d %d" or "%f %f"):format(self.min or (2 ^ -30), self.max or (2 ^ 30))
|
||||
end
|
||||
elseif typ == "table" then
|
||||
local settings = {}
|
||||
if self._level > 0 then
|
||||
-- HACK: Minetest automatically adds the modname
|
||||
-- TODO simple names (not modname.field.other_field)
|
||||
settings = {"[" .. table.concat(modlib.table.repetition("*", self._level)) .. self.name .. "]"}
|
||||
end
|
||||
local function setting(key, value_scheme)
|
||||
key = tostring(key)
|
||||
assert(not key:find("[=%.%s]"))
|
||||
value_scheme.name = self.name .. "." .. key
|
||||
value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key)
|
||||
value_scheme._level = self._level + 1
|
||||
table.insert(settings, generate_settingtypes(value_scheme))
|
||||
end
|
||||
local keys = {}
|
||||
for key in pairs(self.entries or {}) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
table.sort(keys)
|
||||
for _, key in ipairs(keys) do
|
||||
setting(key, self.entries[key])
|
||||
end
|
||||
return table.concat(settings, "\n")
|
||||
end
|
||||
if not typ then
|
||||
return ""
|
||||
end
|
||||
local description = self.description
|
||||
-- TODO extend description by range etc.?
|
||||
-- TODO enum etc. support
|
||||
if description then
|
||||
if type(description) ~= "table" then
|
||||
description = {description}
|
||||
end
|
||||
description = "# " .. table.concat(description, "\n# ") .. "\n"
|
||||
else
|
||||
description = ""
|
||||
end
|
||||
return description .. self.name .. " (" .. self.title .. ") " .. settingtype .. " " .. (default or "") .. (type_args and (" " .. type_args) or "")
|
||||
end
|
||||
|
||||
function generate_markdown(self)
|
||||
-- TODO address redundancies
|
||||
local typ = self.type
|
||||
self.title = self.title or field_name_to_title(self._md_name)
|
||||
self._md_level = self._md_level or 1
|
||||
if typ == "table" then
|
||||
local settings = {}
|
||||
local function setting(key, value_scheme)
|
||||
value_scheme._md_name = key
|
||||
value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key)
|
||||
value_scheme._md_level = self._md_level + 1
|
||||
table.insert(settings, table.concat(modlib.table.repetition("#", self._md_level)) .. " `" .. key .. "`")
|
||||
table.insert(settings, "")
|
||||
table.insert(settings, generate_markdown(value_scheme))
|
||||
table.insert(settings, "")
|
||||
end
|
||||
local keys = {}
|
||||
for key in pairs(self.entries or {}) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
table.sort(keys)
|
||||
for _, key in ipairs(keys) do
|
||||
setting(key, self.entries[key])
|
||||
end
|
||||
return table.concat(settings, "\n")
|
||||
end
|
||||
if not typ then
|
||||
return ""
|
||||
end
|
||||
local lines = {}
|
||||
local function line(text)
|
||||
table.insert(lines, "* " .. text)
|
||||
end
|
||||
local description = self.description
|
||||
if description then
|
||||
if type(description) ~= "table" then
|
||||
table.insert(lines, description)
|
||||
else
|
||||
modlib.table.append(lines, description)
|
||||
end
|
||||
end
|
||||
table.insert(lines, "")
|
||||
line("Type: " .. self.type)
|
||||
if self.default ~= nil then
|
||||
line("Default: `" .. tostring(self.default) .. "`")
|
||||
end
|
||||
if self.int then
|
||||
line"Integer"
|
||||
elseif self.list then
|
||||
line"List"
|
||||
end
|
||||
if self.infinity then
|
||||
line"Infinities allowed"
|
||||
end
|
||||
if self.nan then
|
||||
line"Not-a-Number (NaN) allowed"
|
||||
end
|
||||
if self.range then
|
||||
if self.range.min then
|
||||
line(">= " .. self.range.min)
|
||||
elseif self.range.min_exclusive then
|
||||
line("> " .. self.range.min_exclusive)
|
||||
end
|
||||
if self.range.max then
|
||||
line("<= " .. self.range.max)
|
||||
elseif self.range.max_exclusive then
|
||||
line("< " .. self.range.max_exclusive)
|
||||
end
|
||||
end
|
||||
if self.values then
|
||||
line("Possible values:")
|
||||
for value in pairs(self.values) do
|
||||
table.insert(lines, " * " .. value)
|
||||
end
|
||||
end
|
||||
return table.concat(lines, "\n")
|
||||
-- TODO address redundancies
|
||||
local function description(lines)
|
||||
local description = self.description
|
||||
if description then
|
||||
if type(description) ~= "table" then
|
||||
table.insert(lines, description)
|
||||
else
|
||||
modlib.table.append(lines, description)
|
||||
end
|
||||
end
|
||||
end
|
||||
local typ = self.type
|
||||
self.title = self.title or field_name_to_title(self._md_name)
|
||||
self._md_level = self._md_level or 1
|
||||
if typ == "table" then
|
||||
local settings = {}
|
||||
description(settings)
|
||||
-- TODO generate Markdown for key/value-checks
|
||||
local function setting(key, value_scheme)
|
||||
value_scheme._md_name = key
|
||||
value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key)
|
||||
value_scheme._md_level = self._md_level + 1
|
||||
table.insert(settings, table.concat(modlib.table.repetition("#", self._md_level)) .. " `" .. key .. "`")
|
||||
table.insert(settings, "")
|
||||
table.insert(settings, generate_markdown(value_scheme))
|
||||
table.insert(settings, "")
|
||||
end
|
||||
local keys = {}
|
||||
for key in pairs(self.entries or {}) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
table.sort(keys)
|
||||
for _, key in ipairs(keys) do
|
||||
setting(key, self.entries[key])
|
||||
end
|
||||
return table.concat(settings, "\n")
|
||||
end
|
||||
if not typ then
|
||||
return ""
|
||||
end
|
||||
local lines = {}
|
||||
local function line(text)
|
||||
table.insert(lines, "* " .. text)
|
||||
end
|
||||
table.insert(lines, "")
|
||||
line("Type: " .. self.type)
|
||||
if self.default ~= nil then
|
||||
line("Default: `" .. tostring(self.default) .. "`")
|
||||
end
|
||||
if self.int then
|
||||
line"Integer"
|
||||
elseif self.list then
|
||||
line"List"
|
||||
end
|
||||
if self.infinity then
|
||||
line"Infinities allowed"
|
||||
end
|
||||
if self.nan then
|
||||
line"Not-a-Number (NaN) allowed"
|
||||
end
|
||||
if self.range then
|
||||
if self.range.min then
|
||||
line(">= " .. self.range.min)
|
||||
elseif self.range.min_exclusive then
|
||||
line("> " .. self.range.min_exclusive)
|
||||
end
|
||||
if self.range.max then
|
||||
line("<= " .. self.range.max)
|
||||
elseif self.range.max_exclusive then
|
||||
line("< " .. self.range.max_exclusive)
|
||||
end
|
||||
end
|
||||
if self.values then
|
||||
line("Possible values:")
|
||||
for value in pairs(self.values) do
|
||||
table.insert(lines, " * " .. value)
|
||||
end
|
||||
end
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
function settingtypes(self)
|
||||
self.settingtypes = self.settingtypes or generate_settingtypes(self)
|
||||
return self.settingtypes
|
||||
self.settingtypes = self.settingtypes or generate_settingtypes(self)
|
||||
return self.settingtypes
|
||||
end
|
||||
|
||||
function load(self, override, params)
|
||||
local converted
|
||||
if params.convert_strings and type(override) == "string" then
|
||||
converted = true
|
||||
if self.type == "boolean" then
|
||||
if override == "true" then
|
||||
override = true
|
||||
elseif override == "false" then
|
||||
override = false
|
||||
end
|
||||
elseif self.type == "number" then
|
||||
override = tonumber(override)
|
||||
else
|
||||
converted = false
|
||||
end
|
||||
end
|
||||
if override == nil and not converted then
|
||||
if self.default ~= nil then
|
||||
return self.default
|
||||
elseif self.type == "table" then
|
||||
override = {}
|
||||
end
|
||||
end
|
||||
local _error = error
|
||||
local function format_error(typ, ...)
|
||||
if typ == "type" then
|
||||
return "mismatched type: expected " .. self.type ..", got " .. type(override) .. (converted and " (converted)" or "")
|
||||
end
|
||||
if typ == "range" then
|
||||
local conditions = {}
|
||||
local function push(condition, bound)
|
||||
if self.range[bound] then
|
||||
table.insert(conditions, " " .. condition .. " " .. minetest.write_json(self.range[bound]))
|
||||
end
|
||||
end
|
||||
push(">", "min_exclusive")
|
||||
push(">=", "min")
|
||||
push("<", "max_exclusive")
|
||||
push("<=", "max")
|
||||
return "out of range: expected value " .. table.concat(conditions, "and")
|
||||
end
|
||||
if typ == "int" then
|
||||
return "expected integer"
|
||||
end
|
||||
if typ == "infinity" then
|
||||
return "expected no infinity"
|
||||
end
|
||||
if typ == "nan" then
|
||||
return "expected no nan"
|
||||
end
|
||||
if typ == "required" then
|
||||
local key = ...
|
||||
return "required field " .. minetest.write_json(key) .. " missing"
|
||||
end
|
||||
if typ == "additional" then
|
||||
local key = ...
|
||||
return "superfluous field " .. minetest.write_json(key)
|
||||
end
|
||||
if typ == "list" then
|
||||
return "not a list"
|
||||
end
|
||||
if typ == "values" then
|
||||
return "expected one of " .. minetest.write_json(modlib.table.keys(self.values)) .. ", got " .. minetest.write_json(override)
|
||||
end
|
||||
_error("unknown error type")
|
||||
end
|
||||
local function error(type, ...)
|
||||
if params.error_message then
|
||||
local formatted = format_error(type, ...)
|
||||
settingtypes(self)
|
||||
_error("Invalid value: " .. self.name .. ": " .. formatted)
|
||||
end
|
||||
_error{
|
||||
type = type,
|
||||
self = self,
|
||||
override = override,
|
||||
converted = converted
|
||||
}
|
||||
end
|
||||
local function assert(value, ...)
|
||||
if not value then
|
||||
error(...)
|
||||
end
|
||||
return value
|
||||
end
|
||||
assert(self.type == type(override), "type")
|
||||
if self.type == "number" or self.type == "string" then
|
||||
if self.range then
|
||||
if self.range.min then
|
||||
assert(self.range.min <= override, "range")
|
||||
elseif self.range.min_exclusive then
|
||||
assert(self.range.min_exclusive < override, "range")
|
||||
end
|
||||
if self.range.max then
|
||||
assert(self.range.max >= override, "range")
|
||||
elseif self.range.max_exclusive then
|
||||
assert(self.range.max_exclusive > override, "range")
|
||||
end
|
||||
end
|
||||
if self.type == "number" then
|
||||
assert((not self.int) or (override % 1 == 0), "int")
|
||||
assert(self.infinity or math.abs(override) ~= math.huge, "infinity")
|
||||
assert(self.nan or override == override, "nan")
|
||||
end
|
||||
elseif self.type == "table" then
|
||||
if self.entries then
|
||||
for key, schema in pairs(self.entries) do
|
||||
if schema.required and override[key] == nil then
|
||||
error("required", key)
|
||||
end
|
||||
override[key] = load(schema, override[key], params)
|
||||
end
|
||||
if self.additional == false then
|
||||
for key in pairs(override) do
|
||||
if self.entries[key] == nil then
|
||||
error("additional", key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.keys then
|
||||
for key, value in pairs(override) do
|
||||
override[load(self.keys, key, params)], override[key] = value, nil
|
||||
end
|
||||
end
|
||||
if self.values then
|
||||
for key, value in pairs(override) do
|
||||
override[key] = load(self.values, value, params)
|
||||
end
|
||||
end
|
||||
assert((not self.list) or modlib.table.count(override) == #override, "list")
|
||||
else
|
||||
assert((not self.values) or self.values[override], "values")
|
||||
end
|
||||
if self.func then self.func(override) end
|
||||
return override
|
||||
local converted
|
||||
if params.convert_strings and type(override) == "string" then
|
||||
converted = true
|
||||
if self.type == "boolean" then
|
||||
if override == "true" then
|
||||
override = true
|
||||
elseif override == "false" then
|
||||
override = false
|
||||
end
|
||||
elseif self.type == "number" then
|
||||
override = tonumber(override)
|
||||
else
|
||||
converted = false
|
||||
end
|
||||
end
|
||||
if override == nil and not converted then
|
||||
if self.default ~= nil then
|
||||
return self.default
|
||||
elseif self.type == "table" then
|
||||
override = {}
|
||||
end
|
||||
end
|
||||
local _error = error
|
||||
local function format_error(typ, ...)
|
||||
if typ == "type" then
|
||||
return "mismatched type: expected " .. self.type ..", got " .. type(override) .. (converted and " (converted)" or "")
|
||||
end
|
||||
if typ == "range" then
|
||||
local conditions = {}
|
||||
local function push(condition, bound)
|
||||
if self.range[bound] then
|
||||
table.insert(conditions, " " .. condition .. " " .. minetest.write_json(self.range[bound]))
|
||||
end
|
||||
end
|
||||
push(">", "min_exclusive")
|
||||
push(">=", "min")
|
||||
push("<", "max_exclusive")
|
||||
push("<=", "max")
|
||||
return "out of range: expected value" .. table.concat(conditions, " and")
|
||||
end
|
||||
if typ == "int" then
|
||||
return "expected integer"
|
||||
end
|
||||
if typ == "infinity" then
|
||||
return "expected no infinity"
|
||||
end
|
||||
if typ == "nan" then
|
||||
return "expected no nan"
|
||||
end
|
||||
if typ == "required" then
|
||||
local key = ...
|
||||
return "required field " .. minetest.write_json(key) .. " missing"
|
||||
end
|
||||
if typ == "additional" then
|
||||
local key = ...
|
||||
return "superfluous field " .. minetest.write_json(key)
|
||||
end
|
||||
if typ == "list" then
|
||||
return "not a list"
|
||||
end
|
||||
if typ == "values" then
|
||||
return "expected one of " .. minetest.write_json(modlib.table.keys(self.values)) .. ", got " .. minetest.write_json(override)
|
||||
end
|
||||
_error("unknown error type")
|
||||
end
|
||||
local function error(type, ...)
|
||||
if params.error_message then
|
||||
local formatted = format_error(type, ...)
|
||||
_error("Invalid value: " .. (self.name and (self.name .. ": ") or "") .. formatted)
|
||||
end
|
||||
_error{
|
||||
type = type,
|
||||
self = self,
|
||||
override = override,
|
||||
converted = converted
|
||||
}
|
||||
end
|
||||
local function assert(value, ...)
|
||||
if not value then
|
||||
error(...)
|
||||
end
|
||||
return value
|
||||
end
|
||||
assert(self.type == type(override), "type")
|
||||
if self.type == "number" or self.type == "string" then
|
||||
if self.range then
|
||||
if self.range.min then
|
||||
assert(self.range.min <= override, "range")
|
||||
elseif self.range.min_exclusive then
|
||||
assert(self.range.min_exclusive < override, "range")
|
||||
end
|
||||
if self.range.max then
|
||||
assert(self.range.max >= override, "range")
|
||||
elseif self.range.max_exclusive then
|
||||
assert(self.range.max_exclusive > override, "range")
|
||||
end
|
||||
end
|
||||
if self.type == "number" then
|
||||
assert((not self.int) or (override % 1 == 0), "int")
|
||||
assert(self.infinity or math.abs(override) ~= math.huge, "infinity")
|
||||
assert(self.nan or override == override, "nan")
|
||||
end
|
||||
elseif self.type == "table" then
|
||||
if self.keys then
|
||||
for key, value in pairs(override) do
|
||||
override[load(self.keys, key, params)], override[key] = value, nil
|
||||
end
|
||||
end
|
||||
if self.values then
|
||||
for key, value in pairs(override) do
|
||||
override[key] = load(self.values, value, params)
|
||||
end
|
||||
end
|
||||
if self.entries then
|
||||
for key, schema in pairs(self.entries) do
|
||||
if schema.required and override[key] == nil then
|
||||
error("required", key)
|
||||
end
|
||||
override[key] = load(schema, override[key], params)
|
||||
end
|
||||
if self.additional == false then
|
||||
for key in pairs(override) do
|
||||
if self.entries[key] == nil then
|
||||
error("additional", key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
assert((not self.list) or modlib.table.count(override) == #override, "list")
|
||||
else
|
||||
assert((not self.values) or self.values[override], "values")
|
||||
end
|
||||
if self.func then self.func(override) end
|
||||
return override
|
||||
end
|
|
@ -1,79 +1,79 @@
|
|||
-- Table helpers
|
||||
|
||||
function map_index(table, func)
|
||||
local mapping_metatable = {
|
||||
__index = function(table, key)
|
||||
return rawget(table, func(key))
|
||||
end,
|
||||
__newindex = function(table, key, value)
|
||||
rawset(table, func(key), value)
|
||||
end
|
||||
}
|
||||
return setmetatable(table, mapping_metatable)
|
||||
local mapping_metatable = {
|
||||
__index = function(table, key)
|
||||
return rawget(table, func(key))
|
||||
end,
|
||||
__newindex = function(table, key, value)
|
||||
rawset(table, func(key), value)
|
||||
end
|
||||
}
|
||||
return setmetatable(table, mapping_metatable)
|
||||
end
|
||||
|
||||
function set_case_insensitive_index(table)
|
||||
return map_index(table, string.lower)
|
||||
return map_index(table, string.lower)
|
||||
end
|
||||
|
||||
--+ nilget(a, "b", "c") == a?.b?.c
|
||||
function nilget(value, key, ...)
|
||||
if value == nil or key == nil then
|
||||
return value
|
||||
end
|
||||
return nilget(value[key], ...)
|
||||
if value == nil or key == nil then
|
||||
return value
|
||||
end
|
||||
return nilget(value[key], ...)
|
||||
end
|
||||
|
||||
-- Fisher-Yates
|
||||
function shuffle(table)
|
||||
for index = 1, #table - 1 do
|
||||
local index_2 = math.random(index + 1, #table)
|
||||
table[index], table[index_2] = table[index_2], table[index]
|
||||
end
|
||||
return table
|
||||
for index = 1, #table - 1 do
|
||||
local index_2 = math.random(index + 1, #table)
|
||||
table[index], table[index_2] = table[index_2], table[index]
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
local rope_metatable = {__index = {
|
||||
write = function(self, text)
|
||||
table.insert(self, text)
|
||||
end,
|
||||
to_text = function(self)
|
||||
return table.concat(self)
|
||||
end
|
||||
write = function(self, text)
|
||||
table.insert(self, text)
|
||||
end,
|
||||
to_text = function(self)
|
||||
return table.concat(self)
|
||||
end
|
||||
}}
|
||||
--> rope with simple metatable (:write(text) and :to_text())
|
||||
function rope(table)
|
||||
return setmetatable(table or {}, rope_metatable)
|
||||
return setmetatable(table or {}, rope_metatable)
|
||||
end
|
||||
|
||||
local rope_len_metatable = {__index = {
|
||||
write = function(self, text)
|
||||
self.len = self.len + text:len()
|
||||
end
|
||||
write = function(self, text)
|
||||
self.len = self.len + text:len()
|
||||
end
|
||||
}}
|
||||
--> rope for determining length supporting :write(text), .len being the length
|
||||
function rope_len(len)
|
||||
return setmetatable({len = len or 0}, rope_len_metatable)
|
||||
return setmetatable({len = len or 0}, rope_len_metatable)
|
||||
end
|
||||
|
||||
function is_circular(table)
|
||||
assert(type(table) == "table")
|
||||
local known = {}
|
||||
local function _is_circular(value)
|
||||
if type(value) ~= "table" then
|
||||
return false
|
||||
end
|
||||
if known[value] then
|
||||
return true
|
||||
end
|
||||
known[value] = true
|
||||
for key, value in pairs(value) do
|
||||
if _is_circular(key) or _is_circular(value) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return _is_circular(table)
|
||||
assert(type(table) == "table")
|
||||
local known = {}
|
||||
local function _is_circular(value)
|
||||
if type(value) ~= "table" then
|
||||
return false
|
||||
end
|
||||
if known[value] then
|
||||
return true
|
||||
end
|
||||
known[value] = true
|
||||
for key, value in pairs(value) do
|
||||
if _is_circular(key) or _is_circular(value) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return _is_circular(table)
|
||||
end
|
||||
|
||||
--+ Simple table equality check. Stack overflow if tables are too deep or circular.
|
||||
|
@ -81,44 +81,44 @@ end
|
|||
--> Equality of noncircular tables if `table` and `other_table` are tables
|
||||
--> `table == other_table` else
|
||||
function equals_noncircular(table, other_table)
|
||||
local is_equal = table == other_table
|
||||
if is_equal or type(table) ~= "table" or type(other_table) ~= "table" then
|
||||
return is_equal
|
||||
end
|
||||
if #table ~= #other_table then
|
||||
return false
|
||||
end
|
||||
local table_keys = {}
|
||||
for key, value in pairs(table) do
|
||||
local value_2 = other_table[key]
|
||||
if not equals_noncircular(value, value_2) then
|
||||
if type(key) == "table" then
|
||||
table_keys[key] = value
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
for other_key, other_value in pairs(other_table) do
|
||||
if type(other_key) == "table" then
|
||||
local found
|
||||
for table, value in pairs(table_keys) do
|
||||
if equals_noncircular(other_key, table) and equals_noncircular(other_value, value) then
|
||||
table_keys[table] = nil
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
return false
|
||||
end
|
||||
else
|
||||
if table[other_key] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
local is_equal = table == other_table
|
||||
if is_equal or type(table) ~= "table" or type(other_table) ~= "table" then
|
||||
return is_equal
|
||||
end
|
||||
if #table ~= #other_table then
|
||||
return false
|
||||
end
|
||||
local table_keys = {}
|
||||
for key, value in pairs(table) do
|
||||
local value_2 = other_table[key]
|
||||
if not equals_noncircular(value, value_2) then
|
||||
if type(key) == "table" then
|
||||
table_keys[key] = value
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
for other_key, other_value in pairs(other_table) do
|
||||
if type(other_key) == "table" then
|
||||
local found
|
||||
for table, value in pairs(table_keys) do
|
||||
if equals_noncircular(other_key, table) and equals_noncircular(other_value, value) then
|
||||
table_keys[table] = nil
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
return false
|
||||
end
|
||||
else
|
||||
if table[other_key] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
equals = equals_noncircular
|
||||
|
@ -127,60 +127,60 @@ equals = equals_noncircular
|
|||
--> Table content equality if `table` and `other_table` are tables
|
||||
--> `table == other_table` else
|
||||
function equals_content(table, other_table)
|
||||
local equal_tables = {}
|
||||
local function _equals(table, other_equal_table)
|
||||
local function set_equal_tables(value)
|
||||
equal_tables[table] = equal_tables[table] or {}
|
||||
equal_tables[table][other_equal_table] = value
|
||||
return value
|
||||
end
|
||||
local is_equal = table == other_equal_table
|
||||
if is_equal or type(table) ~= "table" or type(other_equal_table) ~= "table" then
|
||||
return is_equal
|
||||
end
|
||||
if #table ~= #other_equal_table then
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
local lookup_equal = (equal_tables[table] or {})[other_equal_table]
|
||||
if lookup_equal ~= nil then
|
||||
return lookup_equal
|
||||
end
|
||||
-- Premise
|
||||
set_equal_tables(true)
|
||||
local table_keys = {}
|
||||
for key, value in pairs(table) do
|
||||
local other_value = other_equal_table[key]
|
||||
if not _equals(value, other_value) then
|
||||
if type(key) == "table" then
|
||||
table_keys[key] = value
|
||||
else
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
for other_key, other_value in pairs(other_equal_table) do
|
||||
if type(other_key) == "table" then
|
||||
local found = false
|
||||
for table_key, value in pairs(table_keys) do
|
||||
if _equals(table_key, other_key) and _equals(value, other_value) then
|
||||
table_keys[table_key] = nil
|
||||
found = true
|
||||
-- Breaking is fine as per transitivity
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
else
|
||||
if table[other_key] == nil then
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return _equals(table, other_table)
|
||||
local equal_tables = {}
|
||||
local function _equals(table, other_equal_table)
|
||||
local function set_equal_tables(value)
|
||||
equal_tables[table] = equal_tables[table] or {}
|
||||
equal_tables[table][other_equal_table] = value
|
||||
return value
|
||||
end
|
||||
local is_equal = table == other_equal_table
|
||||
if is_equal or type(table) ~= "table" or type(other_equal_table) ~= "table" then
|
||||
return is_equal
|
||||
end
|
||||
if #table ~= #other_equal_table then
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
local lookup_equal = (equal_tables[table] or {})[other_equal_table]
|
||||
if lookup_equal ~= nil then
|
||||
return lookup_equal
|
||||
end
|
||||
-- Premise
|
||||
set_equal_tables(true)
|
||||
local table_keys = {}
|
||||
for key, value in pairs(table) do
|
||||
local other_value = other_equal_table[key]
|
||||
if not _equals(value, other_value) then
|
||||
if type(key) == "table" then
|
||||
table_keys[key] = value
|
||||
else
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
for other_key, other_value in pairs(other_equal_table) do
|
||||
if type(other_key) == "table" then
|
||||
local found = false
|
||||
for table_key, value in pairs(table_keys) do
|
||||
if _equals(table_key, other_key) and _equals(value, other_value) then
|
||||
table_keys[table_key] = nil
|
||||
found = true
|
||||
-- Breaking is fine as per transitivity
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
else
|
||||
if table[other_key] == nil then
|
||||
return set_equal_tables(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return _equals(table, other_table)
|
||||
end
|
||||
|
||||
--+ Table equality check: content has to be equal, relations between tables as well
|
||||
|
@ -190,404 +190,400 @@ end
|
|||
--> equality (same tables after table reference substitution) of circular tables if `table` and `other_table` are tables
|
||||
--> `table == other_table` else
|
||||
function equals_references(table, other_table)
|
||||
local function _equals(table, other_table, equal_refs)
|
||||
if equal_refs[table] then
|
||||
return equal_refs[table] == other_table
|
||||
end
|
||||
local is_equal = table == other_table
|
||||
-- this check could be omitted if table key equality is being checked
|
||||
if type(table) ~= "table" or type(other_table) ~= "table" then
|
||||
return is_equal
|
||||
end
|
||||
if is_equal then
|
||||
equal_refs[table] = other_table
|
||||
return true
|
||||
end
|
||||
-- Premise: table = other table
|
||||
equal_refs[table] = other_table
|
||||
local table_keys = {}
|
||||
for key, value in pairs(table) do
|
||||
if type(key) == "table" then
|
||||
table_keys[key] = value
|
||||
else
|
||||
local other_value = other_table[key]
|
||||
if not _equals(value, other_value, equal_refs) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
local other_table_keys = {}
|
||||
for other_key, other_value in pairs(other_table) do
|
||||
if type(other_key) == "table" then
|
||||
other_table_keys[other_key] = other_value
|
||||
elseif table[other_key] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
local function _next(current_key, equal_refs, available_keys)
|
||||
local key, value = next(table_keys, current_key)
|
||||
if key == nil then
|
||||
return true
|
||||
end
|
||||
for other_key, other_value in pairs(other_table_keys) do
|
||||
local copy_equal_refs = shallowcopy(equal_refs)
|
||||
if _equals(key, other_key, copy_equal_refs) and _equals(value, other_value, copy_equal_refs) then
|
||||
local copy_available_keys = shallowcopy(available_keys)
|
||||
copy_available_keys[other_key] = nil
|
||||
if _next(key, copy_equal_refs, copy_available_keys) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
return _next(nil, equal_refs, other_table_keys)
|
||||
end
|
||||
return _equals(table, other_table, {})
|
||||
local function _equals(table, other_table, equal_refs)
|
||||
if equal_refs[table] then
|
||||
return equal_refs[table] == other_table
|
||||
end
|
||||
local is_equal = table == other_table
|
||||
-- this check could be omitted if table key equality is being checked
|
||||
if type(table) ~= "table" or type(other_table) ~= "table" then
|
||||
return is_equal
|
||||
end
|
||||
if is_equal then
|
||||
equal_refs[table] = other_table
|
||||
return true
|
||||
end
|
||||
-- Premise: table = other table
|
||||
equal_refs[table] = other_table
|
||||
local table_keys = {}
|
||||
for key, value in pairs(table) do
|
||||
if type(key) == "table" then
|
||||
table_keys[key] = value
|
||||
else
|
||||
local other_value = other_table[key]
|
||||
if not _equals(value, other_value, equal_refs) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
local other_table_keys = {}
|
||||
for other_key, other_value in pairs(other_table) do
|
||||
if type(other_key) == "table" then
|
||||
other_table_keys[other_key] = other_value
|
||||
elseif table[other_key] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
local function _next(current_key, equal_refs, available_keys)
|
||||
local key, value = next(table_keys, current_key)
|
||||
if key == nil then
|
||||
return true
|
||||
end
|
||||
for other_key, other_value in pairs(other_table_keys) do
|
||||
local copy_equal_refs = shallowcopy(equal_refs)
|
||||
if _equals(key, other_key, copy_equal_refs) and _equals(value, other_value, copy_equal_refs) then
|
||||
local copy_available_keys = shallowcopy(available_keys)
|
||||
copy_available_keys[other_key] = nil
|
||||
if _next(key, copy_equal_refs, copy_available_keys) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
return _next(nil, equal_refs, other_table_keys)
|
||||
end
|
||||
return _equals(table, other_table, {})
|
||||
end
|
||||
|
||||
function shallowcopy(table)
|
||||
local copy = {}
|
||||
for key, value in pairs(table) do
|
||||
copy[key] = value
|
||||
end
|
||||
return copy
|
||||
local copy = {}
|
||||
for key, value in pairs(table) do
|
||||
copy[key] = value
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
function deepcopy_noncircular(table)
|
||||
local function _copy(value)
|
||||
if type(value) == "table" then
|
||||
return deepcopy_noncircular(value)
|
||||
end
|
||||
return value
|
||||
end
|
||||
local copy = {}
|
||||
for key, value in pairs(table) do
|
||||
copy[_copy(key)] = _copy(value)
|
||||
end
|
||||
return copy
|
||||
local function _copy(value)
|
||||
if type(value) == "table" then
|
||||
return deepcopy_noncircular(value)
|
||||
end
|
||||
return value
|
||||
end
|
||||
local copy = {}
|
||||
for key, value in pairs(table) do
|
||||
copy[_copy(key)] = _copy(value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
function deepcopy(table)
|
||||
local copies = {}
|
||||
local function _deepcopy(table)
|
||||
if copies[table] then
|
||||
return copies[table]
|
||||
end
|
||||
local copy = {}
|
||||
copies[table] = copy
|
||||
local function _copy(value)
|
||||
if type(value) == "table" then
|
||||
if copies[value] then
|
||||
return copies[value]
|
||||
end
|
||||
return _deepcopy(value)
|
||||
end
|
||||
return value
|
||||
end
|
||||
for key, value in pairs(table) do
|
||||
copy[_copy(key)] = _copy(value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
return _deepcopy(table)
|
||||
local copies = {}
|
||||
local function _deepcopy(table)
|
||||
if copies[table] then
|
||||
return copies[table]
|
||||
end
|
||||
local copy = {}
|
||||
copies[table] = copy
|
||||
local function _copy(value)
|
||||
if type(value) == "table" then
|
||||
if copies[value] then
|
||||
return copies[value]
|
||||
end
|
||||
return _deepcopy(value)
|
||||
end
|
||||
return value
|
||||
end
|
||||
for key, value in pairs(table) do
|
||||
copy[_copy(key)] = _copy(value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
return _deepcopy(table)
|
||||
end
|
||||
|
||||
tablecopy = deepcopy
|
||||
copy = deepcopy
|
||||
|
||||
function count(table)
|
||||
local count = 0
|
||||
for _ in pairs(table) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
local count = 0
|
||||
for _ in pairs(table) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
function is_empty(table)
|
||||
return next(table) == nil
|
||||
return next(table) == nil
|
||||
end
|
||||
|
||||
function foreach(table, func)
|
||||
for k, v in pairs(table) do
|
||||
func(k, v)
|
||||
end
|
||||
for k, v in pairs(table) do
|
||||
func(k, v)
|
||||
end
|
||||
end
|
||||
|
||||
function foreach_value(table, func)
|
||||
for _, v in pairs(table) do
|
||||
func(v)
|
||||
end
|
||||
for _, v in pairs(table) do
|
||||
func(v)
|
||||
end
|
||||
end
|
||||
|
||||
function call(table, ...)
|
||||
for _, func in pairs(table) do
|
||||
func(...)
|
||||
end
|
||||
for _, func in pairs(table) do
|
||||
func(...)
|
||||
end
|
||||
end
|
||||
|
||||
function icall(table, ...)
|
||||
for _, func in ipairs(table) do
|
||||
func(...)
|
||||
end
|
||||
for _, func in ipairs(table) do
|
||||
func(...)
|
||||
end
|
||||
end
|
||||
|
||||
function foreach_key(table, func)
|
||||
for key, _ in pairs(table) do
|
||||
func(key)
|
||||
end
|
||||
for key, _ in pairs(table) do
|
||||
func(key)
|
||||
end
|
||||
end
|
||||
|
||||
function map(table, func)
|
||||
for key, value in pairs(table) do
|
||||
table[key] = func(value)
|
||||
end
|
||||
return table
|
||||
for key, value in pairs(table) do
|
||||
table[key] = func(value)
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function map_keys(table, func)
|
||||
local new_tab = {}
|
||||
for key, value in pairs(table) do
|
||||
new_tab[func(key)] = value
|
||||
end
|
||||
return new_tab
|
||||
local new_tab = {}
|
||||
for key, value in pairs(table) do
|
||||
new_tab[func(key)] = value
|
||||
end
|
||||
return new_tab
|
||||
end
|
||||
|
||||
function process(tab, func)
|
||||
local results = {}
|
||||
for key, value in pairs(tab) do
|
||||
table.insert(results, func(key,value))
|
||||
end
|
||||
return results
|
||||
local results = {}
|
||||
for key, value in pairs(tab) do
|
||||
table.insert(results, func(key,value))
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
function call(funcs, ...)
|
||||
for _, func in ipairs(funcs) do
|
||||
func(...)
|
||||
end
|
||||
for _, func in ipairs(funcs) do
|
||||
func(...)
|
||||
end
|
||||
end
|
||||
|
||||
function find(list, value)
|
||||
for index, other_value in pairs(list) do
|
||||
if value == other_value then
|
||||
return index
|
||||
end
|
||||
end
|
||||
return
|
||||
for index, other_value in pairs(list) do
|
||||
if value == other_value then
|
||||
return index
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
contains = find
|
||||
|
||||
function to_add(table, after_additions)
|
||||
local additions = {}
|
||||
for key, value in pairs(after_additions) do
|
||||
if table[key] ~= value then
|
||||
additions[key] = value
|
||||
end
|
||||
end
|
||||
return additions
|
||||
local additions = {}
|
||||
for key, value in pairs(after_additions) do
|
||||
if table[key] ~= value then
|
||||
additions[key] = value
|
||||
end
|
||||
end
|
||||
return additions
|
||||
end
|
||||
|
||||
difference = to_add
|
||||
|
||||
function deep_to_add(table, after_additions)
|
||||
local additions = {}
|
||||
for key, value in pairs(after_additions) do
|
||||
if type(table[key]) == "table" and type(value) == "table" then
|
||||
additions[key] = deep_to_add(table[key], value)
|
||||
elseif table[key] ~= value then
|
||||
additions[key] = value
|
||||
end
|
||||
end
|
||||
return additions
|
||||
local additions = {}
|
||||
for key, value in pairs(after_additions) do
|
||||
if type(table[key]) == "table" and type(value) == "table" then
|
||||
additions[key] = deep_to_add(table[key], value)
|
||||
elseif table[key] ~= value then
|
||||
additions[key] = value
|
||||
end
|
||||
end
|
||||
return additions
|
||||
end
|
||||
|
||||
function add_all(table, additions)
|
||||
for key, value in pairs(additions) do
|
||||
table[key] = value
|
||||
end
|
||||
return table
|
||||
for key, value in pairs(additions) do
|
||||
table[key] = value
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function deep_add_all(table, additions)
|
||||
for key, value in pairs(additions) do
|
||||
if type(table[key]) == "table" and type(value) == "table" then
|
||||
deep_add_all(table[key], value)
|
||||
else
|
||||
table[key] = value
|
||||
end
|
||||
end
|
||||
return table
|
||||
for key, value in pairs(additions) do
|
||||
if type(table[key]) == "table" and type(value) == "table" then
|
||||
deep_add_all(table[key], value)
|
||||
else
|
||||
table[key] = value
|
||||
end
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function complete(table, completions)
|
||||
for key, value in pairs(completions) do
|
||||
if table[key] == nil then
|
||||
table[key] = value
|
||||
end
|
||||
end
|
||||
return table
|
||||
for key, value in pairs(completions) do
|
||||
if table[key] == nil then
|
||||
table[key] = value
|
||||
end
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function deepcomplete(table, completions)
|
||||
for key, value in pairs(completions) do
|
||||
if table[key] == nil then
|
||||
table[key] = value
|
||||
elseif type(table[key]) == "table" and type(value) == "table" then
|
||||
deepcomplete(table[key], value)
|
||||
end
|
||||
end
|
||||
return table
|
||||
for key, value in pairs(completions) do
|
||||
if table[key] == nil then
|
||||
table[key] = value
|
||||
elseif type(table[key]) == "table" and type(value) == "table" then
|
||||
deepcomplete(table[key], value)
|
||||
end
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function merge_tables(table, other_table)
|
||||
return add_all(copy(table), other_table)
|
||||
return add_all(copy(table), other_table)
|
||||
end
|
||||
|
||||
union = merge_tables
|
||||
|
||||
function intersection(table, other_table)
|
||||
local result = {}
|
||||
for key, value in pairs(table) do
|
||||
if other_table[key] then
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
return result
|
||||
local result = {}
|
||||
for key, value in pairs(table) do
|
||||
if other_table[key] then
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function append(table, other_table)
|
||||
local length = #table
|
||||
for index, value in ipairs(other_table) do
|
||||
table[length + index] = value
|
||||
end
|
||||
return table
|
||||
local length = #table
|
||||
for index, value in ipairs(other_table) do
|
||||
table[length + index] = value
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function keys(table)
|
||||
local keys = {}
|
||||
for key, _ in pairs(table) do
|
||||
keys[#keys + 1] = key
|
||||
end
|
||||
return keys
|
||||
local keys = {}
|
||||
for key, _ in pairs(table) do
|
||||
keys[#keys + 1] = key
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
function values(table)
|
||||
local values = {}
|
||||
for _, value in pairs(table) do
|
||||
values[#values + 1] = value
|
||||
end
|
||||
return values
|
||||
local values = {}
|
||||
for _, value in pairs(table) do
|
||||
values[#values + 1] = value
|
||||
end
|
||||
return values
|
||||
end
|
||||
|
||||
function flip(table)
|
||||
local flipped = {}
|
||||
for key, value in pairs(table) do
|
||||
flipped[value] = key
|
||||
end
|
||||
return flipped
|
||||
local flipped = {}
|
||||
for key, value in pairs(table) do
|
||||
flipped[value] = key
|
||||
end
|
||||
return flipped
|
||||
end
|
||||
|
||||
function set(table)
|
||||
local flipped = {}
|
||||
for _, value in pairs(table) do
|
||||
flipped[value] = true
|
||||
end
|
||||
return flipped
|
||||
local flipped = {}
|
||||
for _, value in pairs(table) do
|
||||
flipped[value] = true
|
||||
end
|
||||
return flipped
|
||||
end
|
||||
|
||||
function unique(table)
|
||||
local lookup = {}
|
||||
for _, value in pairs(table) do
|
||||
lookup[value] = true
|
||||
end
|
||||
return keys(lookup)
|
||||
return keys(set(table))
|
||||
end
|
||||
|
||||
function rpairs(table)
|
||||
local index = #table
|
||||
return function()
|
||||
if index >= 1 then
|
||||
local value = table[index]
|
||||
index = index - 1
|
||||
if value ~= nil then
|
||||
return index + 1, value
|
||||
end
|
||||
end
|
||||
end
|
||||
local index = #table
|
||||
return function()
|
||||
if index >= 1 then
|
||||
local value = table[index]
|
||||
index = index - 1
|
||||
if value ~= nil then
|
||||
return index + 1, value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function best_value(table, is_better_func)
|
||||
local best = next(table)
|
||||
if best == nil then
|
||||
return
|
||||
end
|
||||
local candidate = best
|
||||
while true do
|
||||
candidate = next(table, candidate)
|
||||
if candidate == nil then
|
||||
return best
|
||||
end
|
||||
if is_better_func(candidate, best) then
|
||||
best = candidate
|
||||
end
|
||||
end
|
||||
error()
|
||||
local best = next(table)
|
||||
if best == nil then
|
||||
return
|
||||
end
|
||||
local candidate = best
|
||||
while true do
|
||||
candidate = next(table, candidate)
|
||||
if candidate == nil then
|
||||
return best
|
||||
end
|
||||
if is_better_func(candidate, best) then
|
||||
best = candidate
|
||||
end
|
||||
end
|
||||
error()
|
||||
end
|
||||
|
||||
function min(table)
|
||||
return best_value(table, function(value, other_value) return value < other_value end)
|
||||
return best_value(table, function(value, other_value) return value < other_value end)
|
||||
end
|
||||
|
||||
function max(table)
|
||||
return best_value(table, function(value, other_value) return value > other_value end)
|
||||
return best_value(table, function(value, other_value) return value > other_value end)
|
||||
end
|
||||
|
||||
function default_comparator(value, other_value)
|
||||
if value == other_value then
|
||||
return 0
|
||||
end
|
||||
if value > other_value then
|
||||
return 1
|
||||
end
|
||||
return -1
|
||||
if value == other_value then
|
||||
return 0
|
||||
end
|
||||
if value > other_value then
|
||||
return 1
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
--> index if element found
|
||||
--> -index for insertion if not found
|
||||
function binary_search_comparator(comparator)
|
||||
return function(list, value)
|
||||
local min, max = 1, #list
|
||||
while min <= max do
|
||||
local pivot = min + math.floor((max - min) / 2)
|
||||
local element = list[pivot]
|
||||
local compared = comparator(value, element)
|
||||
if compared == 0 then
|
||||
return pivot
|
||||
elseif compared > 0 then
|
||||
min = pivot + 1
|
||||
else
|
||||
max = pivot - 1
|
||||
end
|
||||
end
|
||||
return -min
|
||||
end
|
||||
return function(list, value)
|
||||
local min, max = 1, #list
|
||||
while min <= max do
|
||||
local pivot = min + math.floor((max - min) / 2)
|
||||
local element = list[pivot]
|
||||
local compared = comparator(value, element)
|
||||
if compared == 0 then
|
||||
return pivot
|
||||
elseif compared > 0 then
|
||||
min = pivot + 1
|
||||
else
|
||||
max = pivot - 1
|
||||
end
|
||||
end
|
||||
return -min
|
||||
end
|
||||
end
|
||||
|
||||
binary_search = binary_search_comparator(default_comparator)
|
||||
|
||||
function reverse(table)
|
||||
local l = #table + 1
|
||||
for index = 1, math.floor(#table / 2) do
|
||||
table[l - index], table[index] = table[index], table[l - index]
|
||||
end
|
||||
return table
|
||||
local l = #table + 1
|
||||
for index = 1, math.floor(#table / 2) do
|
||||
table[l - index], table[index] = table[index], table[l - index]
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function repetition(value, count)
|
||||
local table = {}
|
||||
for index = 1, count do
|
||||
table[index] = value
|
||||
end
|
||||
return table
|
||||
local table = {}
|
||||
for index = 1, count do
|
||||
table[index] = value
|
||||
end
|
||||
return table
|
||||
end
|
|
@ -1,248 +1,276 @@
|
|||
local random, huge = math.random, math.huge
|
||||
local parent_env = getfenv(1)
|
||||
setfenv(1, setmetatable({}, {
|
||||
__index = function(_, key)
|
||||
local value = modlib[key]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
return parent_env[key]
|
||||
end,
|
||||
__newindex = function(_, key, value)
|
||||
error(dump{key = key, value = value})
|
||||
end
|
||||
}))
|
||||
|
||||
-- string
|
||||
assert(modlib.string.escape_magic_chars"%" == "%%")
|
||||
assert(string.escape_magic_chars"%" == "%%")
|
||||
|
||||
-- table
|
||||
do
|
||||
local table = {}
|
||||
table[table] = table
|
||||
local table_copy = modlib.table.deepcopy(table)
|
||||
assert(table_copy[table_copy] == table_copy)
|
||||
assert(modlib.table.is_circular(table))
|
||||
assert(not modlib.table.is_circular{a = 1})
|
||||
assert(modlib.table.equals_noncircular({[{}]={}}, {[{}]={}}))
|
||||
assert(modlib.table.equals_content(table, table_copy))
|
||||
local equals_references = modlib.table.equals_references
|
||||
assert(equals_references(table, table_copy))
|
||||
assert(equals_references({}, {}))
|
||||
assert(not equals_references({a = 1, b = 2}, {a = 1, b = 3}))
|
||||
table = {}
|
||||
table.a, table.b = table, table
|
||||
table_copy = modlib.table.deepcopy(table)
|
||||
assert(equals_references(table, table_copy))
|
||||
local x, y = {}, {}
|
||||
assert(not equals_references({[x] = x, [y] = y}, {[x] = y, [y] = x}))
|
||||
assert(equals_references({[x] = x, [y] = y}, {[x] = x, [y] = y}))
|
||||
local nilget = modlib.table.nilget
|
||||
assert(nilget({a = {b = {c = 42}}}, "a", "b", "c") == 42)
|
||||
assert(nilget({a = {}}, "a", "b", "c") == nil)
|
||||
assert(nilget(nil, "a", "b", "c") == nil)
|
||||
assert(nilget(nil, "a", nil, "c") == nil)
|
||||
local rope = modlib.table.rope{}
|
||||
rope:write"hello"
|
||||
rope:write" "
|
||||
rope:write"world"
|
||||
assert(rope:to_text() == "hello world", rope:to_text())
|
||||
local tab = {}
|
||||
tab[tab] = tab
|
||||
local table_copy = table.deepcopy(tab)
|
||||
assert(table_copy[table_copy] == table_copy)
|
||||
assert(table.is_circular(tab))
|
||||
assert(not table.is_circular{a = 1})
|
||||
assert(table.equals_noncircular({[{}]={}}, {[{}]={}}))
|
||||
assert(table.equals_content(tab, table_copy))
|
||||
local equals_references = table.equals_references
|
||||
assert(equals_references(tab, table_copy))
|
||||
assert(equals_references({}, {}))
|
||||
assert(not equals_references({a = 1, b = 2}, {a = 1, b = 3}))
|
||||
tab = {}
|
||||
tab.a, tab.b = tab, tab
|
||||
table_copy = table.deepcopy(tab)
|
||||
assert(equals_references(tab, table_copy))
|
||||
local x, y = {}, {}
|
||||
assert(not equals_references({[x] = x, [y] = y}, {[x] = y, [y] = x}))
|
||||
assert(equals_references({[x] = x, [y] = y}, {[x] = x, [y] = y}))
|
||||
local nilget = table.nilget
|
||||
assert(nilget({a = {b = {c = 42}}}, "a", "b", "c") == 42)
|
||||
assert(nilget({a = {}}, "a", "b", "c") == nil)
|
||||
assert(nilget(nil, "a", "b", "c") == nil)
|
||||
assert(nilget(nil, "a", nil, "c") == nil)
|
||||
local rope = table.rope{}
|
||||
rope:write"hello"
|
||||
rope:write" "
|
||||
rope:write"world"
|
||||
assert(rope:to_text() == "hello world", rope:to_text())
|
||||
end
|
||||
|
||||
-- heap
|
||||
do
|
||||
local n = 100
|
||||
local list = {}
|
||||
for index = 1, n do
|
||||
list[index] = index
|
||||
end
|
||||
modlib.table.shuffle(list)
|
||||
local heap = modlib.heap.new()
|
||||
for index = 1, #list do
|
||||
heap:push(list[index])
|
||||
end
|
||||
for index = 1, #list do
|
||||
local popped = heap:pop()
|
||||
assert(popped == index)
|
||||
end
|
||||
local n = 100
|
||||
local list = {}
|
||||
for index = 1, n do
|
||||
list[index] = index
|
||||
end
|
||||
table.shuffle(list)
|
||||
local heap = heap.new()
|
||||
for index = 1, #list do
|
||||
heap:push(list[index])
|
||||
end
|
||||
for index = 1, #list do
|
||||
local popped = heap:pop()
|
||||
assert(popped == index)
|
||||
end
|
||||
end
|
||||
|
||||
-- ranked set
|
||||
do
|
||||
local n = 100
|
||||
local ranked_set = modlib.ranked_set.new()
|
||||
local list = {}
|
||||
for i = 1, n do
|
||||
ranked_set:insert(i)
|
||||
list[i] = i
|
||||
end
|
||||
local n = 100
|
||||
local ranked_set = ranked_set.new()
|
||||
local list = {}
|
||||
for i = 1, n do
|
||||
ranked_set:insert(i)
|
||||
list[i] = i
|
||||
end
|
||||
|
||||
assert(modlib.table.equals(ranked_set:to_table(), list))
|
||||
assert(table.equals(ranked_set:to_table(), list))
|
||||
|
||||
local i = 0
|
||||
for rank, key in ranked_set:ipairs() do
|
||||
i = i + 1
|
||||
assert(i == key and i == rank)
|
||||
assert(ranked_set:get_by_rank(rank) == key)
|
||||
local rank, key = ranked_set:get(i)
|
||||
assert(key == i and i == rank)
|
||||
end
|
||||
assert(i == n)
|
||||
local i = 0
|
||||
for rank, key in ranked_set:ipairs() do
|
||||
i = i + 1
|
||||
assert(i == key and i == rank)
|
||||
assert(ranked_set:get_by_rank(rank) == key)
|
||||
local rank, key = ranked_set:get(i)
|
||||
assert(key == i and i == rank)
|
||||
end
|
||||
assert(i == n)
|
||||
|
||||
for i = 1, n do
|
||||
local _, v = ranked_set:delete(i)
|
||||
assert(v == i, i)
|
||||
end
|
||||
assert(not next(ranked_set:to_table()))
|
||||
for i = 1, n do
|
||||
local _, v = ranked_set:delete(i)
|
||||
assert(v == i, i)
|
||||
end
|
||||
assert(not next(ranked_set:to_table()))
|
||||
|
||||
local ranked_set = modlib.ranked_set.new()
|
||||
for i = 1, n do
|
||||
ranked_set:insert(i)
|
||||
end
|
||||
local ranked_set = ranked_set.new()
|
||||
for i = 1, n do
|
||||
ranked_set:insert(i)
|
||||
end
|
||||
|
||||
for rank, key in ranked_set:ipairs(10, 20) do
|
||||
assert(rank == key and key >= 10 and key <= 20)
|
||||
end
|
||||
for rank, key in ranked_set:ipairs(10, 20) do
|
||||
assert(rank == key and key >= 10 and key <= 20)
|
||||
end
|
||||
|
||||
for i = n, 1, -1 do
|
||||
local j = ranked_set:delete_by_rank(i)
|
||||
assert(j == i)
|
||||
end
|
||||
for i = n, 1, -1 do
|
||||
local j = ranked_set:delete_by_rank(i)
|
||||
assert(j == i)
|
||||
end
|
||||
end
|
||||
|
||||
-- colorspec
|
||||
local colorspec = modlib.minetest.colorspec.from_number(0xDDCCBBAA)
|
||||
assert(modlib.table.equals(colorspec, {a = 0xAA, b = 0xBB, g = 0xCC, r = 0xDD,}), dump(colorspec))
|
||||
|
||||
-- k-d-tree
|
||||
local vectors = {}
|
||||
for _ = 1, 1000 do
|
||||
table.insert(vectors, {math.random(), math.random(), math.random()})
|
||||
_G.table.insert(vectors, {random(), random(), random()})
|
||||
end
|
||||
local kdtree = modlib.kdtree.new(vectors)
|
||||
for _, vector in ipairs(vectors) do
|
||||
local neighbor, distance = kdtree:get_nearest_neighbor(vector)
|
||||
assert(modlib.vector.equals(vector, neighbor), distance == 0)
|
||||
local kdtree = kdtree.new(vectors)
|
||||
for _, v in ipairs(vectors) do
|
||||
local neighbor, distance = kdtree:get_nearest_neighbor(v)
|
||||
assert(vector.equals(v, neighbor), distance == 0)
|
||||
end
|
||||
|
||||
for _ = 1, 1000 do
|
||||
local vector = {math.random(), math.random(), math.random()}
|
||||
local _, distance = kdtree:get_nearest_neighbor(vector)
|
||||
local min_distance = math.huge
|
||||
for _, other_vector in ipairs(vectors) do
|
||||
local other_distance = modlib.vector.distance(vector, other_vector)
|
||||
if other_distance < min_distance then
|
||||
min_distance = other_distance
|
||||
end
|
||||
end
|
||||
assert(distance == min_distance)
|
||||
local v = {random(), random(), random()}
|
||||
local _, distance = kdtree:get_nearest_neighbor(v)
|
||||
local min_distance = huge
|
||||
for _, w in ipairs(vectors) do
|
||||
local other_distance = vector.distance(v, w)
|
||||
if other_distance < min_distance then
|
||||
min_distance = other_distance
|
||||
end
|
||||
end
|
||||
assert(distance == min_distance)
|
||||
end
|
||||
|
||||
-- bluon
|
||||
do
|
||||
local bluon = modlib.bluon
|
||||
local function assert_preserves(object)
|
||||
local rope = modlib.table.rope{}
|
||||
local written, read, input
|
||||
local _, err = pcall(function()
|
||||
bluon:write(object, rope)
|
||||
written = rope:to_text()
|
||||
input = modlib.text.inputstream(written)
|
||||
read = bluon:read(input)
|
||||
local remaining = input:read(1000)
|
||||
assert(not remaining)
|
||||
end)
|
||||
-- TODO assertdump
|
||||
assert(modlib.table.equals_references(object, read) and not err, dump{
|
||||
object = object,
|
||||
read = read,
|
||||
written = written and modlib.text.hexdump(written),
|
||||
err = err
|
||||
})
|
||||
end
|
||||
for _, constant in pairs{true, false, math.huge, -math.huge} do
|
||||
assert_preserves(constant)
|
||||
end
|
||||
for i = 1, 1000 do
|
||||
assert_preserves(table.concat(modlib.table.repetition(string.char(i % 256), i)))
|
||||
end
|
||||
for _ = 1, 1000 do
|
||||
local int = math.random(-2^50, 2^50)
|
||||
assert(int % 1 == 0)
|
||||
assert_preserves(int)
|
||||
assert_preserves((math.random() - 0.5) * 2^math.random(-20, 20))
|
||||
end
|
||||
assert_preserves{hello = "world", welt = "hallo"}
|
||||
assert_preserves{"hello", "hello", "hello"}
|
||||
local a = {}
|
||||
a[a] = a
|
||||
a[1] = a
|
||||
assert_preserves(a)
|
||||
local bluon = bluon
|
||||
local function assert_preserves(object)
|
||||
local rope = table.rope{}
|
||||
local written, read, input
|
||||
local _, err = pcall(function()
|
||||
bluon:write(object, rope)
|
||||
written = rope:to_text()
|
||||
input = text.inputstream(written)
|
||||
read = bluon:read(input)
|
||||
local remaining = input:read(1000)
|
||||
assert(not remaining)
|
||||
end)
|
||||
assertdump(table.equals_references(object, read) and not err, {
|
||||
object = object,
|
||||
read = read,
|
||||
written = written and text.hexdump(written),
|
||||
err = err
|
||||
})
|
||||
end
|
||||
for _, constant in pairs{true, false, huge, -huge} do
|
||||
assert_preserves(constant)
|
||||
end
|
||||
for i = 1, 1000 do
|
||||
assert_preserves(_G.table.concat(table.repetition(_G.string.char(i % 256), i)))
|
||||
end
|
||||
for _ = 1, 1000 do
|
||||
local int = random(-2^50, 2^50)
|
||||
assert(int % 1 == 0)
|
||||
assert_preserves(int)
|
||||
assert_preserves((random() - 0.5) * 2^random(-20, 20))
|
||||
end
|
||||
assert_preserves{hello = "world", welt = "hallo"}
|
||||
assert_preserves{"hello", "hello", "hello"}
|
||||
local a = {}
|
||||
a[a] = a
|
||||
a[1] = a
|
||||
assert_preserves(a)
|
||||
end
|
||||
|
||||
if not _G.minetest then return end
|
||||
|
||||
-- colorspec
|
||||
local colorspec = minetest.colorspec
|
||||
local function test_from_string(string, number)
|
||||
local spec = colorspec.from_string(string)
|
||||
local expected = colorspec.from_number(number)
|
||||
assertdump(table.equals(spec, expected), {expected = expected, actual = spec})
|
||||
end
|
||||
local spec = colorspec.from_number(0xDDCCBBAA)
|
||||
assertdump(table.equals(spec, {a = 0xAA, b = 0xBB, g = 0xCC, r = 0xDD}), spec)
|
||||
test_from_string("aliceblue", 0xf0f8ffff)
|
||||
test_from_string("aliceblue#42", 0xf0f8ff42)
|
||||
test_from_string("#333", 0x333333FF)
|
||||
test_from_string("#694269", 0x694269FF)
|
||||
test_from_string("#11223344", 0x11223344)
|
||||
|
||||
-- in-game tests & b3d testing
|
||||
local tests = {
|
||||
-- depends on player_api
|
||||
b3d = false,
|
||||
liquid_dir = false,
|
||||
liquid_raycast = false
|
||||
-- depends on player_api
|
||||
b3d = false,
|
||||
liquid_dir = false,
|
||||
liquid_raycast = false
|
||||
}
|
||||
if tests.b3d then
|
||||
local stream = io.open(modlib.mod.get_resource("player_api", "models", "character.b3d"), "r")
|
||||
assert(stream)
|
||||
local b3d = modlib.b3d.read(stream)
|
||||
--! dirty helper method to create truncate tables with 10+ number keys
|
||||
local function _b3d_truncate(table)
|
||||
local count = 1
|
||||
for key, value in pairs(table) do
|
||||
if type(key) == "table" then
|
||||
_b3d_truncate(key)
|
||||
end
|
||||
if type(value) == "table" then
|
||||
_b3d_truncate(value)
|
||||
end
|
||||
count = count + 1
|
||||
if type(key) == "number" and count >= 9 and next(table, key) then
|
||||
if count == 9 then
|
||||
table[key] = "TRUNCATED"
|
||||
else
|
||||
table[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
return table
|
||||
end
|
||||
modlib.file.write(modlib.mod.get_resource"character.b3d.lua", "return " .. dump(_b3d_truncate(modlib.table.copy(b3d))))
|
||||
stream:close()
|
||||
local stream = io.open(mod.get_resource("player_api", "models", "character.b3d"), "r")
|
||||
assert(stream)
|
||||
local b3d = b3d.read(stream)
|
||||
--! dirty helper method to create truncate tables with 10+ number keys
|
||||
local function _b3d_truncate(table)
|
||||
local count = 1
|
||||
for key, value in pairs(table) do
|
||||
if type(key) == "table" then
|
||||
_b3d_truncate(key)
|
||||
end
|
||||
if type(value) == "table" then
|
||||
_b3d_truncate(value)
|
||||
end
|
||||
count = count + 1
|
||||
if type(key) == "number" and count >= 9 and next(table, key) then
|
||||
if count == 9 then
|
||||
table[key] = "TRUNCATED"
|
||||
else
|
||||
table[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
return table
|
||||
end
|
||||
file.write(mod.get_resource"character.b3d.lua", "return " .. dump(_b3d_truncate(table.copy(b3d))))
|
||||
stream:close()
|
||||
end
|
||||
local vector, minetest, ml_mt = _G.vector, _G.minetest, minetest
|
||||
if tests.liquid_dir then
|
||||
minetest.register_abm{
|
||||
label = "get_liquid_corner_levels & get_liquid_direction test",
|
||||
nodenames = {"default:water_flowing"},
|
||||
interval = 1,
|
||||
chance = 1,
|
||||
action = function(pos, node)
|
||||
assert(type(node) == "table")
|
||||
for _, corner_level in pairs(modlib.minetest.get_liquid_corner_levels(pos, node)) do
|
||||
minetest.add_particle{
|
||||
pos = vector.add(pos, corner_level),
|
||||
size = 2,
|
||||
texture = "logo.png"
|
||||
}
|
||||
end
|
||||
local direction = modlib.minetest.get_liquid_flow_direction(pos, node)
|
||||
local start_pos = pos
|
||||
start_pos.y = start_pos.y + 1
|
||||
for i = 0, 5 do
|
||||
minetest.add_particle{
|
||||
pos = vector.add(start_pos, vector.multiply(direction, i/5)),
|
||||
size = i/2.5,
|
||||
texture = "logo.png"
|
||||
}
|
||||
end
|
||||
end
|
||||
}
|
||||
minetest.register_abm{
|
||||
label = "get_liquid_corner_levels & get_liquid_direction test",
|
||||
nodenames = {"group:liquid"},
|
||||
interval = 1,
|
||||
chance = 1,
|
||||
action = function(pos, node)
|
||||
assert(type(node) == "table")
|
||||
for _, corner_level in pairs(ml_mt.get_liquid_corner_levels(pos, node)) do
|
||||
minetest.add_particle{
|
||||
pos = vector.add(pos, corner_level),
|
||||
size = 2,
|
||||
texture = "logo.png"
|
||||
}
|
||||
end
|
||||
local direction = ml_mt.get_liquid_flow_direction(pos, node)
|
||||
local start_pos = pos
|
||||
start_pos.y = start_pos.y + 1
|
||||
for i = 0, 5 do
|
||||
minetest.add_particle{
|
||||
pos = vector.add(start_pos, vector.multiply(direction, i/5)),
|
||||
size = i/2.5,
|
||||
texture = "logo.png"
|
||||
}
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
if tests.liquid_raycast then
|
||||
minetest.register_globalstep(function()
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local eye_pos = vector.offset(player:get_pos(), 0, player:get_properties().eye_height, 0)
|
||||
local raycast = modlib.minetest.raycast(eye_pos, vector.add(eye_pos, vector.multiply(player:get_look_dir(), 3)), false, true)
|
||||
for pointed_thing in raycast do
|
||||
if pointed_thing.type == "node" and minetest.registered_nodes[minetest.get_node(pointed_thing.under).name].liquidtype == "flowing" then
|
||||
minetest.add_particle{
|
||||
pos = vector.add(pointed_thing.intersection_point, vector.multiply(pointed_thing.intersection_normal, 0.1)),
|
||||
size = 0.5,
|
||||
texture = "object_marker_red.png",
|
||||
expirationtime = 3
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
minetest.register_globalstep(function()
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local eye_pos = vector.offset(player:get_pos(), 0, player:get_properties().eye_height, 0)
|
||||
local raycast = ml_mt.raycast(eye_pos, vector.add(eye_pos, vector.multiply(player:get_look_dir(), 3)), false, true)
|
||||
for pointed_thing in raycast do
|
||||
if pointed_thing.type == "node" and minetest.registered_nodes[minetest.get_node(pointed_thing.under).name].liquidtype == "flowing" then
|
||||
minetest.add_particle{
|
||||
pos = vector.add(pointed_thing.intersection_point, vector.multiply(pointed_thing.intersection_normal, 0.1)),
|
||||
size = 0.5,
|
||||
texture = "object_marker_red.png",
|
||||
expirationtime = 3
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
|
@ -95,7 +95,6 @@ function split_lines(text, limit) return modlib.text.split(text, "\r?\n", limit,
|
|||
|
||||
function lines(text) return text:gmatch"[^\r\n]*" end
|
||||
|
||||
local hashtag = string.byte"#"
|
||||
local zero = string.byte"0"
|
||||
local nine = string.byte"9"
|
||||
local letter_a = string.byte"A"
|
||||
|
|
|
@ -2,113 +2,113 @@ local mt_vector = vector
|
|||
local vector = getfenv(1)
|
||||
|
||||
index_aliases = {
|
||||
x = 1,
|
||||
y = 2,
|
||||
z = 3,
|
||||
w = 4
|
||||
x = 1,
|
||||
y = 2,
|
||||
z = 3,
|
||||
w = 4
|
||||
}
|
||||
|
||||
modlib.table.add_all(index_aliases, modlib.table.flip(index_aliases))
|
||||
|
||||
metatable = {
|
||||
__index = function(table, key)
|
||||
local index = index_aliases[key]
|
||||
if index ~= nil then
|
||||
return table[index]
|
||||
end
|
||||
return vector[key]
|
||||
end,
|
||||
__newindex = function(table, key, value)
|
||||
local index = letters[key]
|
||||
if index ~= nil then
|
||||
return rawset(table, index, value)
|
||||
end
|
||||
end
|
||||
__index = function(table, key)
|
||||
local index = index_aliases[key]
|
||||
if index ~= nil then
|
||||
return table[index]
|
||||
end
|
||||
return vector[key]
|
||||
end,
|
||||
__newindex = function(table, key, value)
|
||||
local index = letters[key]
|
||||
if index ~= nil then
|
||||
return rawset(table, index, value)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
function new(v)
|
||||
return setmetatable(v, metatable)
|
||||
return setmetatable(v, metatable)
|
||||
end
|
||||
|
||||
function from_xyzw(v)
|
||||
return new{v.x, v.y, v.z, v.w}
|
||||
return new{v.x, v.y, v.z, v.w}
|
||||
end
|
||||
|
||||
function from_minetest(v)
|
||||
return new{v.x, v.y, v.z}
|
||||
return new{v.x, v.y, v.z}
|
||||
end
|
||||
|
||||
function to_xyzw(v)
|
||||
return {x = v[1], y = v[2], z = v[3], w = v[4]}
|
||||
return {x = v[1], y = v[2], z = v[3], w = v[4]}
|
||||
end
|
||||
|
||||
--+ not necessarily required, as Minetest respects the metatable
|
||||
function to_minetest(v)
|
||||
return mt_vector.new(unpack(v))
|
||||
return mt_vector.new(unpack(v))
|
||||
end
|
||||
|
||||
function equals(v, other_v)
|
||||
for k, v in pairs(v) do
|
||||
if v ~= other_v[k] then return false end
|
||||
end
|
||||
return true
|
||||
function equals(v, w)
|
||||
for k, v in pairs(v) do
|
||||
if v ~= w[k] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
metatable.__eq = equals
|
||||
|
||||
function less_than(v, other_v)
|
||||
for k, v in pairs(v) do
|
||||
if v >= other_v[k] then return false end
|
||||
end
|
||||
return true
|
||||
function less_than(v, w)
|
||||
for k, v in pairs(v) do
|
||||
if v >= w[k] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
metatable.__lt = less_than
|
||||
|
||||
function less_or_equal(v, other_v)
|
||||
for k, v in pairs(v) do
|
||||
if v > other_v[k] then return false end
|
||||
end
|
||||
return true
|
||||
function less_or_equal(v, w)
|
||||
for k, v in pairs(v) do
|
||||
if v > w[k] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
metatable.__le = less_or_equal
|
||||
|
||||
function combine(v, other_v, f)
|
||||
local new_vector = {}
|
||||
for key, value in pairs(v) do
|
||||
new_vector[key] = f(value, other_v[key])
|
||||
end
|
||||
return new(new_vector)
|
||||
function combine(v, w, f)
|
||||
local new_vector = {}
|
||||
for key, value in pairs(v) do
|
||||
new_vector[key] = f(value, w[key])
|
||||
end
|
||||
return new(new_vector)
|
||||
end
|
||||
|
||||
function apply(v, f, ...)
|
||||
local new_vector = {}
|
||||
for key, value in pairs(v) do
|
||||
new_vector[key] = f(value, ...)
|
||||
end
|
||||
return new(new_vector)
|
||||
local new_vector = {}
|
||||
for key, value in pairs(v) do
|
||||
new_vector[key] = f(value, ...)
|
||||
end
|
||||
return new(new_vector)
|
||||
end
|
||||
|
||||
function combinator(f)
|
||||
return function(v, other_v)
|
||||
return combine(v, other_v, f)
|
||||
end, function(v, ...)
|
||||
return apply(v, f, ...)
|
||||
end
|
||||
return function(v, w)
|
||||
return combine(v, w, f)
|
||||
end, function(v, ...)
|
||||
return apply(v, f, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function invert(v)
|
||||
for key, value in pairs(v) do
|
||||
v[key] = -value
|
||||
end
|
||||
for key, value in pairs(v) do
|
||||
v[key] = -value
|
||||
end
|
||||
end
|
||||
|
||||
add, add_scalar = combinator(function(a, b) return a + b end)
|
||||
subtract, subtract_scalar = combinator(function(a, b) return a - b end)
|
||||
multiply, multiply_scalar = combinator(function(a, b) return a * b end)
|
||||
divide, divide_scalar = combinator(function(a, b) return a / b end)
|
||||
pow, pow_scalar = combinator(function(a, b) return a ^ b end)
|
||||
add, add_scalar = combinator(function(v, w) return v + w end)
|
||||
subtract, subtract_scalar = combinator(function(v, w) return v - w end)
|
||||
multiply, multiply_scalar = combinator(function(v, w) return v * w end)
|
||||
divide, divide_scalar = combinator(function(v, w) return v / w end)
|
||||
pow, pow_scalar = combinator(function(v, w) return v ^ w end)
|
||||
|
||||
metatable.__add = add
|
||||
metatable.__unm = invert
|
||||
|
@ -118,100 +118,108 @@ metatable.__div = divide
|
|||
|
||||
--+ linear interpolation
|
||||
--: ratio number from 0 (all the first vector) to 1 (all the second vector)
|
||||
function interpolate(v, other_v, ratio)
|
||||
return add(multiply(v, 1 - ratio), multiply(other_v, ratio))
|
||||
function interpolate(v, w, ratio)
|
||||
return add(multiply_scalar(v, 1 - ratio), multiply_scalar(w, ratio))
|
||||
end
|
||||
|
||||
function norm(v)
|
||||
local sum = 0
|
||||
for _, value in pairs(v) do
|
||||
sum = sum + value ^ 2
|
||||
end
|
||||
return sum
|
||||
local sum = 0
|
||||
for _, value in pairs(v) do
|
||||
sum = sum + value ^ 2
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
function length(v)
|
||||
return math.sqrt(norm(v))
|
||||
return math.sqrt(norm(v))
|
||||
end
|
||||
|
||||
-- Minor code duplication for the sake of performance
|
||||
function distance(v, other_v)
|
||||
local sum = 0
|
||||
for key, value in pairs(v) do
|
||||
sum = sum + (value - other_v[key]) ^ 2
|
||||
end
|
||||
return math.sqrt(sum)
|
||||
function distance(v, w)
|
||||
local sum = 0
|
||||
for key, value in pairs(v) do
|
||||
sum = sum + (value - w[key]) ^ 2
|
||||
end
|
||||
return math.sqrt(sum)
|
||||
end
|
||||
|
||||
function normalize(v)
|
||||
return divide_scalar(v, length(v))
|
||||
return divide_scalar(v, length(v))
|
||||
end
|
||||
|
||||
function floor(v)
|
||||
return apply(v, math.floor)
|
||||
return apply(v, math.floor)
|
||||
end
|
||||
|
||||
function ceil(v)
|
||||
return apply(v, math.ceil)
|
||||
return apply(v, math.ceil)
|
||||
end
|
||||
|
||||
function clamp(v, min, max)
|
||||
return apply(apply(v, math.max, min), math.min, max)
|
||||
return apply(apply(v, math.max, min), math.min, max)
|
||||
end
|
||||
|
||||
function cross3(v, other_v)
|
||||
return new{
|
||||
v[2] * other_v[3] - v[3] * other_v[2],
|
||||
v[3] * other_v[1] - v[1] * other_v[3],
|
||||
v[1] * other_v[2] - v[2] * other_v[1]
|
||||
}
|
||||
function cross3(v, w)
|
||||
assert(#v == 3 and #w == 3)
|
||||
return new{
|
||||
v[2] * w[3] - v[3] * w[2],
|
||||
v[3] * w[1] - v[1] * w[3],
|
||||
v[1] * w[2] - v[2] * w[1]
|
||||
}
|
||||
end
|
||||
|
||||
function dot(v, other_v)
|
||||
local sum = 0
|
||||
for i, c in pairs(v) do
|
||||
sum = sum + c * other_v[i]
|
||||
end
|
||||
return sum
|
||||
function dot(v, w)
|
||||
local sum = 0
|
||||
for i, c in pairs(v) do
|
||||
sum = sum + c * w[i]
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
--+ Angle between two vectors
|
||||
--> Signed angle in radians
|
||||
function angle(v, w)
|
||||
-- Based on dot(v, w) = |v| * |w| * cos(x)
|
||||
return math.acos(dot(v, w) / length(v) / length(w))
|
||||
end
|
||||
|
||||
function box_box_collision(diff, box, other_box)
|
||||
for index, diff in pairs(diff) do
|
||||
if box[index] + diff > other_box[index + 3] or other_box[index] > box[index + 3] + diff then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
for index, diff in pairs(diff) do
|
||||
if box[index] + diff > other_box[index + 3] or other_box[index] > box[index + 3] + diff then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--+ Möller-Trumbore
|
||||
function ray_triangle_intersection(origin, direction, triangle)
|
||||
local point_1, point_2, point_3 = unpack(triangle)
|
||||
local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1)
|
||||
local h = cross3(direction, edge_2)
|
||||
local a = dot(edge_1, h)
|
||||
if math.abs(a) < 1e-9 then
|
||||
return
|
||||
end
|
||||
local f = 1 / a
|
||||
local diff = subtract(origin, point_1)
|
||||
local u = f * dot(diff, h)
|
||||
if u < 0 or u > 1 then
|
||||
return
|
||||
end
|
||||
local q = cross3(diff, edge_1)
|
||||
local v = f * dot(direction, q)
|
||||
if v < 0 or u + v > 1 then
|
||||
return
|
||||
end
|
||||
local pos_on_line = f * dot(edge_2, q)
|
||||
if pos_on_line >= 0 then
|
||||
return pos_on_line
|
||||
end
|
||||
local point_1, point_2, point_3 = unpack(triangle)
|
||||
local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1)
|
||||
local h = cross3(direction, edge_2)
|
||||
local a = dot(edge_1, h)
|
||||
if math.abs(a) < 1e-9 then
|
||||
return
|
||||
end
|
||||
local f = 1 / a
|
||||
local diff = subtract(origin, point_1)
|
||||
local u = f * dot(diff, h)
|
||||
if u < 0 or u > 1 then
|
||||
return
|
||||
end
|
||||
local q = cross3(diff, edge_1)
|
||||
local v = f * dot(direction, q)
|
||||
if v < 0 or u + v > 1 then
|
||||
return
|
||||
end
|
||||
local pos_on_line = f * dot(edge_2, q)
|
||||
if pos_on_line >= 0 then
|
||||
return pos_on_line
|
||||
end
|
||||
end
|
||||
|
||||
function triangle_normal(triangle)
|
||||
local point_1, point_2, point_3 = unpack(triangle)
|
||||
local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1)
|
||||
return normalize(cross3(edge_1, edge_2))
|
||||
local point_1, point_2, point_3 = unpack(triangle)
|
||||
local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1)
|
||||
return normalize(cross3(edge_1, edge_2))
|
||||
end
|
|
@ -1450,7 +1450,7 @@ minetest.register_node("default:junglegrass", {
|
|||
sunlight_propagates = true,
|
||||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flora = 1, attached_node = 1, flammable = 1},
|
||||
groups = {snappy = 3, flora = 1, attached_node = 1, grass = 1, junglegrass = 1, flammable = 1},
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
|
@ -1471,7 +1471,8 @@ minetest.register_node("default:grass_1", {
|
|||
sunlight_propagates = true,
|
||||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flora = 1, attached_node = 1, grass = 1, flammable = 1},
|
||||
groups = {snappy = 3, flora = 1, attached_node = 1, grass = 1,
|
||||
normal_grass = 1, flammable = 1},
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
|
@ -1501,7 +1502,8 @@ for i = 2, 5 do
|
|||
buildable_to = true,
|
||||
drop = "default:grass_1",
|
||||
groups = {snappy = 3, flora = 1, attached_node = 1,
|
||||
not_in_creative_inventory = 1, grass = 1, flammable = 1},
|
||||
not_in_creative_inventory = 1, grass = 1,
|
||||
normal_grass = 1, flammable = 1},
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
|
@ -1523,7 +1525,7 @@ minetest.register_node("default:dry_grass_1", {
|
|||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flammable = 3, flora = 1,
|
||||
attached_node = 1, dry_grass = 1},
|
||||
attached_node = 1, grass = 1, dry_grass = 1},
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
|
@ -1552,7 +1554,7 @@ for i = 2, 5 do
|
|||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flammable = 3, flora = 1, attached_node = 1,
|
||||
not_in_creative_inventory=1, dry_grass = 1},
|
||||
not_in_creative_inventory = 1, grass = 1, dry_grass = 1},
|
||||
drop = "default:dry_grass_1",
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
|
@ -1574,7 +1576,8 @@ minetest.register_node("default:fern_1", {
|
|||
sunlight_propagates = true,
|
||||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flammable = 3, flora = 1, attached_node = 1},
|
||||
groups = {snappy = 3, flammable = 3, flora = 1, grass = 1,
|
||||
fern = 1, attached_node = 1},
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
|
@ -1604,7 +1607,7 @@ for i = 2, 3 do
|
|||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flammable = 3, flora = 1, attached_node = 1,
|
||||
not_in_creative_inventory=1},
|
||||
grass = 1, fern = 1, not_in_creative_inventory = 1},
|
||||
drop = "default:fern_1",
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
|
@ -1626,7 +1629,8 @@ minetest.register_node("default:marram_grass_1", {
|
|||
sunlight_propagates = true,
|
||||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flammable = 3, attached_node = 1},
|
||||
groups = {snappy = 3, flammable = 3, flora = 1, grass = 1, marram_grass = 1,
|
||||
attached_node = 1},
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
|
@ -1654,8 +1658,8 @@ for i = 2, 3 do
|
|||
sunlight_propagates = true,
|
||||
walkable = false,
|
||||
buildable_to = true,
|
||||
groups = {snappy = 3, flammable = 3, attached_node = 1,
|
||||
not_in_creative_inventory=1},
|
||||
groups = {snappy = 3, flammable = 3, flora = 1, attached_node = 1,
|
||||
grass = 1, marram_grass = 1, not_in_creative_inventory = 1},
|
||||
drop = "default:marram_grass_1",
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
selection_box = {
|
||||
|
|
|
@ -2,9 +2,20 @@ petz.update_nametag = function(self)
|
|||
local name_tag
|
||||
if self.show_tag and self.tag and not(self.tag == "") then
|
||||
name_tag = self.tag
|
||||
self.object:set_nametag_attributes({text = name_tag .." ♥ "..tostring(self.hp).."/"..tostring(self.max_hp),})
|
||||
local _bgcolor
|
||||
if not petz.settings["tag_background"] then
|
||||
_bgcolor = "#FFFFFF00"
|
||||
else
|
||||
_bgcolor = false
|
||||
end
|
||||
self.object:set_nametag_attributes({
|
||||
text = name_tag .." ♥ "..tostring(self.hp).."/"..tostring(self.max_hp),
|
||||
bgcolor = _bgcolor,
|
||||
})
|
||||
else
|
||||
self.object:set_nametag_attributes({text = "",})
|
||||
self.object:set_nametag_attributes({
|
||||
text = "",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,9 +12,11 @@ petz.on_step = function(self, dtime)
|
|||
if self.gallop then
|
||||
petz.gallop(self, on_step_time)
|
||||
end
|
||||
--DELETE WHEN UPDATING TO 5.4-->
|
||||
if not(self.on_deactivate) then
|
||||
petz.dreamcatcher_save_metadata(self)
|
||||
end
|
||||
--<
|
||||
local lifetime = petz.check_lifetime(self)
|
||||
if lifetime then
|
||||
petz.lifetime_timer(self, lifetime, on_step_time)
|
||||
|
|
|
@ -91,6 +91,9 @@ blood = false
|
|||
colorize_punch = true
|
||||
punch_color = #FF0000
|
||||
|
||||
##Tag Background
|
||||
tag_background = false
|
||||
|
||||
##Smoke particles when die
|
||||
death_effect = true
|
||||
|
||||
|
|
|
@ -84,6 +84,12 @@ local settings_def = {
|
|||
type = "number",
|
||||
default = 1,
|
||||
},
|
||||
--Nametag Background
|
||||
{
|
||||
name = "tag_background",
|
||||
type = "boolean",
|
||||
default = false,
|
||||
},
|
||||
--Capture Mobs
|
||||
{
|
||||
name = "lasso",
|
||||
|
|
|
@ -440,8 +440,10 @@ end
|
|||
-- returns 2D angle from self to target in radians
|
||||
function water_life.get_yaw_to_object(self,target)
|
||||
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tpos = target:get_pos()
|
||||
if not self or not target then return 0 end
|
||||
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tpos = target:get_pos()
|
||||
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
|
||||
return tyaw
|
||||
end
|
||||
|
@ -449,8 +451,9 @@ end
|
|||
-- returns 2D angle from self to pos in radians
|
||||
function water_life.get_yaw_to_pos(self,tpos)
|
||||
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
|
||||
if not self then return 0 end
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
|
||||
|
||||
return tyaw
|
||||
end
|
||||
|
|
|
@ -440,8 +440,10 @@ end
|
|||
-- returns 2D angle from self to target in radians
|
||||
function water_life.get_yaw_to_object(self,target)
|
||||
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tpos = target:get_pos()
|
||||
if not self or not target then return 0 end
|
||||
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tpos = target:get_pos()
|
||||
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
|
||||
return tyaw
|
||||
end
|
||||
|
@ -449,8 +451,9 @@ end
|
|||
-- returns 2D angle from self to pos in radians
|
||||
function water_life.get_yaw_to_pos(self,tpos)
|
||||
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
|
||||
if not self then return 0 end
|
||||
local pos = mobkit.get_stand_pos(self)
|
||||
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
|
||||
|
||||
return tyaw
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
-----------------------------------------------------------
|
||||
|
||||
water_life = {}
|
||||
water_life.version = "210207"
|
||||
water_life.version = "210327"
|
||||
water_life.shark_food = {}
|
||||
water_life.repellant = {}
|
||||
water_life.gull_bait = {}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
-----------------------------------------------------------
|
||||
|
||||
water_life = {}
|
||||
water_life.version = "210207"
|
||||
water_life.version = "210327"
|
||||
water_life.shark_food = {}
|
||||
water_life.repellant = {}
|
||||
water_life.gull_bait = {}
|
||||
|
|
|
@ -49,7 +49,7 @@ mobs:register_mob("animalworld:ant", {
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:ant",
|
||||
nodes = {"default:dirt_with_coniferous_litter"},
|
||||
|
|
|
@ -33,7 +33,7 @@ mobs:register_mob("animalworld:anteater", {
|
|||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "mobs:leather", chance = 1, min = 0, max = 2},
|
||||
{name = "animalworld:anteatercorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:anteatercorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -72,7 +72,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"ethereal:grass_grove", "ethereal:green_dirt", "default:dirt_with_rainforest_litter"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:anteater",
|
||||
nodes = {"default:dirt_with_rainforest_litter"},
|
||||
|
|
|
@ -69,7 +69,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dirt_with_grass", "default:dry_dirt_with_dry_grass", "default:dirt_with_rainforest_litter", "default:dirt_with_coniferous_litter", "ethereal:gray_dirt", "ethereal:mushroom_dirt", "ethereal:grove_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:bat",
|
||||
nodes = {"default:dirt_with_grass", "default:dry_dirt_with_dry_grass", "default:dirt_with_rainforest_litter", "default:dirt_with_coniferous_litter"},
|
||||
|
|
|
@ -28,7 +28,7 @@ stepheight = 1,
|
|||
stepheight = 1,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:bearcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:bearcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -64,7 +64,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dirt_with_coniferous_litter", "default:permafrost_with_moss", "ethereal:bamboo_dirt", "ethereal:gray_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:bear",
|
||||
nodes = {"default:dirt_with_coniferous_litter"}, {"default:permafrost_with_moss"},
|
||||
|
|
|
@ -30,7 +30,7 @@ stepheight = 3,
|
|||
drops = {
|
||||
{name = "animalworld:chicken_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:chicken_feather", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:blackbirdcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:blackbirdcorpse", chance = 7, min = 1, max = 1},
|
||||
|
||||
},
|
||||
water_damage = 1,
|
||||
|
@ -67,7 +67,7 @@ view_range = 4,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:blackbird",
|
||||
nodes = {"default:dirt_with_grass"},
|
||||
|
|
|
@ -34,7 +34,7 @@ mobs:register_mob("animalworld:boar", {
|
|||
replace_with = "default:dirt",
|
||||
drops = {
|
||||
{name = "animalworld:pork_raw", chance = 1, min = 1, max = 3},
|
||||
{name = "animalworld:boarcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:boarcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -74,7 +74,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"ethereal:mushroom_dirt", "ethereal:bamboo_dirt", "ethereal:green_dirt", "ethereal:mushroom_dirt", "default:dirt_with_coniferous_litter", "default:dirt_gray"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:boar",
|
||||
nodes = {"default:dirt_with_coniferous_litter", "default:dirt_gray"},
|
||||
|
|
|
@ -33,7 +33,7 @@ mobs:register_mob("animalworld:camel", {
|
|||
view_range = 7,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:camelcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:camelcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -72,7 +72,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:desert_sand", "ethereal:dry_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:camel",
|
||||
nodes = {"default:desert_sand", "default:sandstone"},
|
||||
|
|
|
@ -64,7 +64,7 @@ stepheight = 0.0,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:carp",
|
||||
nodes = {"default:water_source"}, {"default:river_water_source"},
|
||||
|
|
|
@ -66,7 +66,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = "default:sand"
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:crab",
|
||||
nodes = {"default:sand"},
|
||||
|
|
|
@ -29,7 +29,7 @@ stepheight = 1,
|
|||
stepheight = 1,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:crocodilecorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:crocodilecorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -68,7 +68,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"ethereal:grove_dirt", "default:dry_dirt_with_dry_grass", "default:dirt_with_rainforest_litter"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:crocodile",
|
||||
nodes = {"default:dry_dirt_with_dry_grass"}, {"default:dirt_with_rainforest_litter"},
|
||||
|
|
|
@ -60,7 +60,7 @@ mobs:register_mob("animalworld:divingbeetle", {
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:divingbeetle",
|
||||
nodes = {"default:water_source", "default:river_water_source"},
|
||||
|
|
|
@ -64,7 +64,7 @@ view_range = 5,
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:dragonfly",
|
||||
nodes = {"air"},
|
||||
|
|
|
@ -29,7 +29,7 @@ stepheight = 2,
|
|||
stepheight = 2,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:elephantcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:elephantcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -68,7 +68,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dry_dirt_with_dry_grass"}, {"default:dirt_with_rainforest_litter"}, {"ethereal:grove_dirt"}, {"ethereal:bamboo_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:elephant",
|
||||
nodes = {"default:dry_dirt_with_dry_grass"}, {"default:dirt_with_rainforest_litter"},
|
||||
|
|
|
@ -60,7 +60,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = "ethereal:prairie_dirt", "default:dirt_with_grass", "ethereal:green_dirt"
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:frog",
|
||||
nodes = {"default:dirt_with_grass"}, {"default:dirt_with_rainforest_litter"},
|
||||
|
|
|
@ -33,7 +33,7 @@ mobs:register_mob("animalworld:gnu", {
|
|||
view_range = 10,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:gnucorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:gnucorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -73,7 +73,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dry_dirt_with_dry_grass", "ethereal:prairie_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:gnu",
|
||||
nodes = {"default:dry_dirt_with_dry_grass"},
|
||||
|
|
|
@ -63,7 +63,7 @@ stepheight = 0.0,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:goby",
|
||||
nodes = {"default:water_source"}, {"default:river_water_source"},
|
||||
|
|
|
@ -56,7 +56,7 @@ mobs:register_mob("animalworld:goldenmole", {
|
|||
|
||||
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:goldenmole",
|
||||
nodes = {"default:desert_sand"},
|
||||
|
|
|
@ -107,7 +107,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = "ethereal:prairie_dirt", "default:dirt_with_grass"
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:hare",
|
||||
nodes = {"default:dirt_with_grass"},
|
||||
|
|
|
@ -29,7 +29,7 @@ stepheight = 1,
|
|||
stepheight = 1,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:hippocorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:hippocorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -73,7 +73,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dry_dirt_with_dry_grass"}, {"default:dirt_with_rainforest_litter"}, {"ethereal:grove_dirt"}, {"ethereal:prairie_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:hippo",
|
||||
nodes = {"default:dry_dirt_with_dry_grass"}, {"default:dirt_with_rainforest_litter"},
|
||||
|
|
|
@ -29,7 +29,7 @@ stepheight = 2,
|
|||
stepheight = 2,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:hyenacorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:hyenacorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -65,7 +65,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dry_dirt_with_dry_grass"}, {"ethereal:dry_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:hyena",
|
||||
nodes = {"default:dry_dirt_with_dry_grass"},
|
||||
|
|
|
@ -7,12 +7,10 @@ local S = minetest.get_translator and minetest.get_translator("animalworld") or
|
|||
|
||||
mobs.intllib = S
|
||||
|
||||
|
||||
-- Check for custom mob spawn file
|
||||
local input = io.open(path .. "spawn.lua", "r")
|
||||
|
||||
if input then
|
||||
mobs.custom_spawn_animal = true
|
||||
mobs.custom_spawn_animalworld = true
|
||||
input:close()
|
||||
input = nil
|
||||
end
|
||||
|
@ -78,13 +76,12 @@ dofile(path .. "hunger.lua") --
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Load custom spawning
|
||||
if mobs.custom_spawn_animal then
|
||||
if mobs.custom_spawn_animalworld then
|
||||
dofile(path .. "spawn.lua")
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
print (S("[MOD] Mobs Redo Animals loaded"))
|
||||
|
|
|
@ -30,7 +30,7 @@ mobs:register_mob("animalworld:kangaroo", {
|
|||
view_range = 10,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:kangaroocorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:kangaroocorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -69,7 +69,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"ethereal:grass_grove", "default:desert_sand", "ethereal:dry_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:kangaroo",
|
||||
nodes = {"default:desert_sand", "default:dry_dirt_with_dry_grass"},
|
||||
|
|
|
@ -64,7 +64,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:desert_sandstone", "default:desert_stone", "default:sandstone", "default:dirt_with_rainforest_litter", "ethereal:grove_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:kobra",
|
||||
nodes = {"default:desert_sandstone", "default:desert_stone", "default:sandstone", "default:dirt_with_rainforest_litter"},
|
||||
|
|
|
@ -77,7 +77,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:water_source"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:lobster",
|
||||
nodes = {"default:water_source"},
|
||||
|
|
|
@ -66,7 +66,7 @@ view_range = 4,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:locust",
|
||||
nodes = {"default:dry_dirt_with_dry_grass", "ethereal:prairie_dirt"},
|
||||
|
|
|
@ -63,7 +63,7 @@ stepheight = 0.0,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:manatee",
|
||||
nodes = {"default:water_source"}, {"default:river_water_source"},
|
||||
|
|
|
@ -29,7 +29,7 @@ stepheight = 1,
|
|||
stepheight = 1,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:monitorcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:monitorcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -68,7 +68,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:desert_sand"}, {"default:desert_sandstone"}, {"default:sandstone"}, {"ethereal:dry_dirt"}, {"ethereal:fiery_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:monitor",
|
||||
nodes = {"default:desert_sand"}, {"default:desert_sandstone"}, {"default:sandstone"}, {"ethereal:dry_dirt"}, {"ethereal:fiery_dirt"},
|
||||
|
|
|
@ -34,7 +34,7 @@ mobs:register_mob("animalworld:moose", {
|
|||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "mobs:leather", chance = 1, min = 0, max = 2},
|
||||
{name = "animalworld:moosecorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:moosecorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -73,7 +73,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"ethereal:grass_grove", "default:dirt_with_grass", "default:dirt_with_coniferous_litter"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:moose",
|
||||
nodes = {"default:dirt_with_grass", "default:dirt_with_coniferous_litter"},
|
||||
|
|
|
@ -94,7 +94,7 @@ if minetest.get_modpath("ethereal") then
|
|||
end
|
||||
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:nandu",
|
||||
nodes = {"default:dirt_with_grass"},
|
||||
|
|
|
@ -61,7 +61,7 @@ mobs:register_mob("animalworld:notoptera", {
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:notoptera",
|
||||
nodes = {"default:permafrost", "default:permafrost_with_moss"},
|
||||
|
|
|
@ -62,7 +62,7 @@ mobs:register_mob("animalworld:nymph", {
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:nymph",
|
||||
nodes = {"default:water_source", "default:river_water_source"},
|
||||
|
|
|
@ -64,7 +64,7 @@ stepheight = 0.0,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:olm",
|
||||
nodes = {"default:water_source", "default:river_water_source", "default:water_flowing", "default:river_water_flowing"},
|
||||
|
|
|
@ -31,7 +31,7 @@ stepheight = 3,
|
|||
drops = {
|
||||
{name = "animalworld:chicken_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:chicken_feather", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:owlcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:owlcorpse", chance = 7, min = 1, max = 1},
|
||||
|
||||
},
|
||||
water_damage = 1,
|
||||
|
@ -72,7 +72,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dirt_with_coniferous_litter"}, {"default:pine_needles"}, {"ethereal:mushroom_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:owl",
|
||||
nodes = {"default:dirt_with_coniferous_litter"}, {"default:pine_needles"},
|
||||
|
|
|
@ -69,7 +69,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:snowblock"}, {"default:ice"}, {"ethereal:crystal_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:puffin",
|
||||
nodes = {"default:snowblock"}, {"default:ice"},
|
||||
|
|
|
@ -61,13 +61,13 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:stone", "default:mossycobble", "ethereal:dry_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:rat",
|
||||
nodes = {"default:stone", "default:mossycobble", "default:dirt"},
|
||||
min_light = 0,
|
||||
interval = 60,
|
||||
chance = 8000, -- 15000
|
||||
chance = 2000, -- 15000
|
||||
active_object_count = 2,
|
||||
min_height = -100,
|
||||
max_height = 0,
|
||||
|
|
|
@ -32,7 +32,7 @@ mobs:register_mob("animalworld:reindeer", {
|
|||
view_range = 10,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:reindeercorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:reindeercorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -71,7 +71,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:dirt_with_snow", "default:permafrost_with_moss", "ethereal:crystal_dirt"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:reindeer",
|
||||
nodes = {"default:dirt_with_snow", "default:permafrost_with_moss", "ethereal:crystal_dirt"},
|
||||
|
|
|
@ -47,7 +47,7 @@ mobs:register_mob("animalworld:scorpion", {
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:scorpion",
|
||||
nodes = {"default:desert_sand"}, {"default:desert_sandstone"}, {"default:sandstone"}, {"ethereal:dry_dirt"}, {"ethereal:fiery_dirt"},
|
||||
|
|
|
@ -66,7 +66,7 @@ stepheight = 0.0,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:seahorse",
|
||||
nodes = {"default:water_source"},
|
||||
|
|
|
@ -27,7 +27,7 @@ stepheight = 0.6,
|
|||
stepheight = 1.1,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:sealcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:sealcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -64,7 +64,7 @@ if minetest.get_modpath("ethereal") then
|
|||
spawn_on = {"default:snowblock", "default_ice"}
|
||||
end
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:seal",
|
||||
nodes = {"default:snowblock", "default_ice"},
|
||||
|
|
|
@ -31,7 +31,7 @@ stepheight = 0.0,
|
|||
stepheight = 0.0,
|
||||
drops = {
|
||||
{name = "animalworld:rawfish", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:sharkcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:sharkcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
air_damage = 1,
|
||||
|
@ -66,7 +66,7 @@ stepheight = 0.0,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:shark",
|
||||
nodes = {"default:water_source"},
|
||||
|
|
|
@ -31,7 +31,7 @@ mobs:register_mob("animalworld:snail", {
|
|||
"default:grass", "farming:cucumber", "farming:cabbage", "xocean:seagrass", "farming:lettuce", "default:junglegrass"},
|
||||
view_range = 5,
|
||||
drops = {
|
||||
{name = "animalworld:snail", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:snail", chance = 3, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 5,
|
||||
|
@ -60,7 +60,7 @@ mobs:register_mob("animalworld:snail", {
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:snail",
|
||||
nodes = {"default:dirt_with_grass"},
|
||||
|
|
|
@ -29,7 +29,7 @@ stepheight = 5,
|
|||
stepheight = 5,
|
||||
drops = {
|
||||
{name = "mobs:meat_raw", chance = 1, min = 1, max = 1},
|
||||
{name = "animalworld:snowleopardcorpse", chance = 9, min = 1, max = 1},
|
||||
{name = "animalworld:snowleopardcorpse", chance = 7, min = 1, max = 1},
|
||||
},
|
||||
water_damage = 0,
|
||||
lava_damage = 4,
|
||||
|
@ -62,7 +62,7 @@ stepheight = 5,
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:snowleopard",
|
||||
nodes = {"default:snowblock"}, {"default:dirt_with_snow"}, {"default:permafrost"}, {"default:stone"},
|
||||
|
|
|
@ -54,7 +54,7 @@ stepheight = 4,
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:spider",
|
||||
nodes = {"default:stone", "default:desert_stone", "default:sandstone"},
|
||||
|
|
|
@ -49,7 +49,7 @@ stepheight = 4,
|
|||
})
|
||||
|
||||
|
||||
if not mobs.custom_spawn_monster then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:spidermale",
|
||||
nodes = {"default:dry_dirt_with_dry_grass"}, {"default:dirt_with_rainforest_litter"},
|
||||
|
|
|
@ -64,7 +64,7 @@ follow = {
|
|||
end,
|
||||
})
|
||||
|
||||
if not mobs.custom_spawn_animal then
|
||||
if not mobs.custom_spawn_animalworld then
|
||||
mobs:spawn({
|
||||
name = "animalworld:squid",
|
||||
nodes = {"default:water_source"},
|
||||
|
|