1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-07-02 16:38:41 +00:00

Merge branch 'master' into master

This commit is contained in:
DustyBagel 2024-09-27 21:44:25 -05:00 committed by GitHub
commit 0976b89add
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
257 changed files with 3805 additions and 3840 deletions

View file

@ -35,7 +35,7 @@ jobs:
- name: Integration test + devtest
run: |
./util/test_multiplayer.sh
serverconf="profiler.load=true" ./util/test_multiplayer.sh
luacheck:
name: "Builtin Luacheck and Unit Tests"

View file

@ -29,8 +29,8 @@ on:
jobs:
build:
# use macOS 13 since it's the last one that still runs on x86
runs-on: macos-13
# use lowest possible macOS running on x86_64 to support more users
runs-on: macos-12
steps:
- uses: actions/checkout@v4
- name: Install deps

View file

@ -56,6 +56,11 @@ if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE)
endif()
set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization")
set(BUILD_WITH_TRACY FALSE CACHE BOOL
"Fetch and build with the Tracy profiler client")
set(FETCH_TRACY_GIT_TAG "master" CACHE STRING
"Git tag for fetching Tracy client. Match with your server (gui) version")
set(DEFAULT_RUN_IN_PLACE FALSE)
if(WIN32)
set(DEFAULT_RUN_IN_PLACE TRUE)
@ -370,3 +375,19 @@ if(BUILD_DOCUMENTATION)
)
endif()
endif()
# Fetch Tracy
if(BUILD_WITH_TRACY)
include(FetchContent)
message(STATUS "Fetching Tracy (${FETCH_TRACY_GIT_TAG})...")
FetchContent_Declare(
tracy
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
GIT_TAG ${FETCH_TRACY_GIT_TAG}
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(tracy)
message(STATUS "Fetching Tracy - done")
endif()

View file

@ -119,6 +119,7 @@ Command-line options
Compiling
---------
- [Compiling - common information](doc/compiling/README.md)
- [Compiling on GNU/Linux](doc/compiling/linux.md)
- [Compiling on Windows](doc/compiling/windows.md)
- [Compiling on MacOS](doc/compiling/macos.md)

View file

@ -1,15 +0,0 @@
-- CSM death formspec. Only used when clientside modding is enabled, otherwise
-- handled by the engine.
core.register_on_death(function()
local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
"label[4.85,1.35;" .. fgettext("You died") ..
"]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"
core.show_formspec("bultin:death", formspec)
end)
core.register_on_formspec_input(function(formname, fields)
if formname == "bultin:death" then
core.send_respawn()
end
end)

View file

@ -9,6 +9,5 @@ dofile(commonpath .. "mod_storage.lua")
dofile(commonpath .. "chatcommands.lua")
dofile(commonpath .. "information_formspecs.lua")
dofile(clientpath .. "chatcommands.lua")
dofile(clientpath .. "death_formspec.lua")
dofile(clientpath .. "misc.lua")
assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions

View file

@ -23,6 +23,8 @@ core.add_node = core.set_node
-- we don't deal with metadata currently
core.swap_node = core.set_node
core.bulk_swap_node = core.bulk_set_node
function core.remove_node(pos)
return core.vmanip:set_node_at(pos, {name="air"})
end

View file

@ -28,10 +28,8 @@ local function buttonbar_formspec(self)
end
local formspec = {
"style_type[box;noclip=true]",
string.format("box[%f,%f;%f,%f;%s]", self.pos.x, self.pos.y, self.size.x,
self.size.y, self.bgcolor),
"style_type[box;noclip=false]",
}
local btn_size = self.size.y - 2*BASE_SPACING
@ -71,7 +69,7 @@ local function buttonbar_formspec(self)
y = self.pos.y + BASE_SPACING,
}
table.insert(formspec, string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;false]tooltip[%s;%s]",
table.insert(formspec, string.format("image_button[%f,%f;%f,%f;%s;%s;%s;false;false]tooltip[%s;%s]",
btn_pos.x, btn_pos.y, btn_size, btn_size, btn.image, btn.name,
btn.caption, btn.name, btn.tooltip))
end
@ -86,9 +84,6 @@ local function buttonbar_formspec(self)
y = self.pos.y + BASE_SPACING,
}
table.insert(formspec, string.format("style[%s,%s;noclip=true]",
self.btn_prev_name, self.btn_next_name))
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;<]",
btn_prev_pos.x, btn_prev_pos.y, get_scroll_btn_width(), btn_size,
self.btn_prev_name))

View file

@ -66,11 +66,22 @@ local function get_formspec(self)
local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize)
local tsize = tab.tabsize or { width = self.width, height = self.height }
local ENABLE_TOUCH = core.settings:get_bool("enable_touch")
local orig_tsize = tab.tabsize or { width = self.width, height = self.height }
local tsize = { width = orig_tsize.width, height = orig_tsize.height }
tsize.height = tsize.height
+ TABHEADER_H -- tabheader included in formspec size
+ (ENABLE_TOUCH and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP)
+ GAMEBAR_H -- gamebar included in formspec size
if self.parent == nil and not prepend then
prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height,
dump(self.fixed_size))
local anchor_pos = TABHEADER_H + orig_tsize.height / 2
prepend = prepend .. ("anchor[0.5,%f]"):format(anchor_pos / tsize.height)
if tab.formspec_version then
prepend = ("formspec_version[%d]"):format(tab.formspec_version) .. prepend
end
@ -78,12 +89,15 @@ local function get_formspec(self)
local end_button_size = 0.75
local tab_header_size = { width = tsize.width, height = 0.85 }
local tab_header_size = { width = tsize.width, height = TABHEADER_H }
if self.end_button then
tab_header_size.width = tab_header_size.width - end_button_size - 0.1
end
local formspec = (prepend or "") .. self:tab_header(tab_header_size) .. content
local formspec = (prepend or "")
formspec = formspec .. ("bgcolor[;neither]container[0,%f]box[0,0;%f,%f;#0000008C]"):format(
TABHEADER_H, orig_tsize.width, orig_tsize.height)
formspec = formspec .. self:tab_header(tab_header_size) .. content
if self.end_button then
formspec = formspec ..
@ -98,6 +112,8 @@ local function get_formspec(self)
self.end_button.name)
end
formspec = formspec .. "container_end[]"
return formspec
end

View file

@ -0,0 +1,31 @@
local F = core.formspec_escape
local S = core.get_translator("__builtin")
function core.show_death_screen(player, _reason)
local fs = {
"formspec_version[1]",
"size[11,5.5,true]",
"bgcolor[#320000b4;true]",
"label[4.85,1.35;", F(S("You died")), "]",
"button_exit[4,3;3,0.5;btn_respawn;", F(S("Respawn")), "]",
}
core.show_formspec(player:get_player_name(), "__builtin:death", table.concat(fs, ""))
end
core.register_on_dieplayer(function(player, reason)
core.show_death_screen(player, reason)
end)
core.register_on_joinplayer(function(player)
if player:get_hp() == 0 then
core.show_death_screen(player, nil)
end
end)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname == "__builtin:death" and fields.quit and player:get_hp() == 0 then
player:respawn()
core.log("action", player:get_player_name() .. " respawns at " ..
player:get_pos():to_string())
end
end)

View file

@ -43,6 +43,8 @@ core.features = {
moveresult_new_pos = true,
override_item_remove_fields = true,
hotbar_hud_element = true,
bulk_lbms = true,
abm_without_neighbors = true,
}
function core.has_feature(arg)

View file

@ -38,6 +38,7 @@ dofile(gamepath .. "forceloading.lua")
dofile(gamepath .. "hud.lua")
dofile(gamepath .. "knockback.lua")
dofile(gamepath .. "async.lua")
dofile(gamepath .. "death_screen.lua")
core.after(0, builtin_shared.cache_content_ids)

View file

@ -6,14 +6,14 @@ local S = core.get_translator("__builtin")
-- Misc. API functions
--
-- @spec core.kick_player(String, String) :: Boolean
function core.kick_player(player_name, reason)
-- @spec core.kick_player(String, String, Boolean) :: Boolean
function core.kick_player(player_name, reason, reconnect)
if type(reason) == "string" then
reason = "Kicked: " .. reason
else
reason = "Kicked."
end
return core.disconnect_player(player_name, reason)
return core.disconnect_player(player_name, reason, reconnect)
end
function core.check_player_privs(name, ...)
@ -298,3 +298,28 @@ do
return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos))
end
end
--
-- Helper for LBM execution, called from C++
--
function core.run_lbm(id, pos_list, dtime_s)
local lbm = core.registered_lbms[id]
assert(lbm, "Entry with given id not found in registered_lbms table")
core.set_last_run_mod(lbm.mod_origin)
if lbm.bulk_action then
return lbm.bulk_action(pos_list, dtime_s)
end
-- emulate non-bulk LBMs
local expect = core.get_node(pos_list[1]).name
-- engine guarantees that
-- 1) all nodes are the same content type
-- 2) the list is up-to-date when we're called
assert(expect ~= "ignore")
for _, pos in ipairs(pos_list) do
local n = core.get_node(pos)
if n.name == expect then -- might have been changed by previous call
lbm.action(pos, n, dtime_s)
end
end
end

View file

@ -105,7 +105,12 @@ function core.register_lbm(spec)
-- Add to core.registered_lbms
check_modname_prefix(spec.name)
check_node_list(spec.nodenames, "nodenames")
assert(type(spec.action) == "function", "Required field 'action' of type function")
local have = spec.action ~= nil
local have_bulk = spec.bulk_action ~= nil
assert(not have or type(spec.action) == "function", "Field 'action' must be a function")
assert(not have_bulk or type(spec.bulk_action) == "function", "Field 'bulk_action' must be a function")
assert(have ~= have_bulk, "Either 'action' or 'bulk_action' must be present")
core.registered_lbms[#core.registered_lbms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Вы загінулі
Respawn=Адрадзіцца

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Умряхте
Respawn=Прераждане

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Has mort
Respawn=Reaparèixer

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Zemřel jsi
Respawn=Oživit

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Buest ti farw
Respawn=Atgyfodi

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Du døde
Respawn=Genopstå

View file

@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Es wurden insgesamt @1 Datenpunkt(e) aufgeze
The output is limited to '@1'.=Die Ausgabe ist beschränkt auf „@1“.
Saving of profile failed: @1=Speichern des Profils fehlgeschlagen: @1
Profile saved to @1=Profil abgespeichert nach @1
You died=Sie sind gestorben
Respawn=Wiederbeleben

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Πέθανες
Respawn=Επανεμφάνηση

View file

@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Sume @1 ekzemplero(j) konserviĝis.
The output is limited to '@1'.=La eligo estas limigita al «@1».
Saving of profile failed: @1=Konservado de profilo malsukcesis: @1
Profile saved to @1=Profilo konservita al @1
You died=Vi mortis
Respawn=Renaskiĝi

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Has muerto
Respawn=Reaparecer

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Said surma
Respawn=Ärka ellu

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Hil zara
Respawn=Birsortu

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Kuolit
Respawn=Synny uudelleen

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Namatay ka
Respawn=Mag-respawn

View file

@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=@1 échantillons ont été collectés.
The output is limited to '@1'.=La sortie est limitée à '@1'.
Saving of profile failed: @1=La sauvegarde du profil a échoué : @1
Profile saved to @1=Le profil a été sauvegardé dans @1
You died=Vous êtes mort
Respawn=Réapparaître

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Fuair tú bás
Respawn=Athsceith

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Morreches
Respawn=Reaparecer

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Meghaltál
Respawn=Újraéledés

View file

@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Total @1 sampel yang diambil.
The output is limited to '@1'.=Keluaran dibatasi ke '@1'.
Saving of profile failed: @1=Penyimpanan profil gagal: @1
Profile saved to @1=Profil disimpan ke @1
You died=Anda mati
Respawn=Bangkit kembali

View file

@ -245,3 +245,5 @@ A total of @1 sample(s) were taken.=Son stati ottenuti campioni per un totale di
The output is limited to '@1'.=L'output è limitato a '@1'.
Saving of profile failed: @1=Errore nel salvare il profilo: @1
Profile saved to @1=Profilo salvato in @1
You died=Sei morto
Respawn=Rinasci

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=死んでしまった
Respawn=リスポーン

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=.i do morsi
Respawn=tolcanci

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Panjenengan pejah
Respawn=Bangkit Malilh

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=사망했습니다
Respawn=리스폰

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Кулінныд
Respawn=Ловзьыны

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Сиз өлдүңүз.
Respawn=Кайтадан жаралуу

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Jūs numirėte
Respawn=Prisikelti

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Jūs nomirāt
Respawn=Atdzīvoties

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=尔死矣
Respawn=复生

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Та үхсэн
Respawn=Дахин төрөх

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=तू मेलास
Respawn=पुनर्जन्म

View file

@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Sebanyak @1 sampel telah diambil secara kese
The output is limited to '@1'.=Output dihadkan kepada '@1'.
Saving of profile failed: @1=Penyimpanan profil telah gagal: @1
Profile saved to @1=Profil telah disimpan ke @1
You died=Anda telah meninggal
Respawn=Jelma semula

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Du døde
Respawn=Gjenoppstå

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Je bent gestorven
Respawn=Herboren worden

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Du døydde
Respawn=Kom opp att

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Setz mòrt·a
Respawn=Tornar

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Nie żyjesz
Respawn=Wróć do gry

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Você morreu
Respawn=Renascer

View file

@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Um total de @1 amostra(s) foi coletada.
The output is limited to '@1'.=A saída é limitada a '@1'.
Saving of profile failed: @1=Falha ao salvar o perfil: @1
Profile saved to @1=Perfil salvo em @1
You died=Você morreu
Respawn=Reviver

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Ai murit
Respawn=Reînviere

View file

@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Всего было взято @1 образ
The output is limited to '@1'.=Вывод ограничен значением '@1'.
Saving of profile failed: @1=Не удалось сохранить данные профилирования: @1
Profile saved to @1=Данные профилирования сохранены в @1
You died=Вы умерли
Respawn=Возродиться

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Zomrel si
Respawn=Oživiť

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Umrl si
Respawn=Ponovno oživi

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Умро си
Respawn=Врати се у живот

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Umro/la si.
Respawn=Vrati se u zivot

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Du dog
Respawn=Återuppstå

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Umekufa.
Respawn=Respawn

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=sina moli
Respawn=o kama sin

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Öldün
Respawn=Yeniden Canlan

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Сез үлдегез
Respawn=Тергезелергә

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Ви загинули
Respawn=Відродитися

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=Bạn đã bị chết
Respawn=Hồi sinh

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=您已死亡
Respawn=重生

View file

@ -0,0 +1,3 @@
# textdomain: __builtin
You died=您已死亡
Respawn=重生

View file

@ -23,6 +23,13 @@ mt_color_dark_green = "#25C191"
mt_color_orange = "#FF8800"
mt_color_red = "#FF3300"
MAIN_TAB_W = 15.5
MAIN_TAB_H = 7.1
TABHEADER_H = 0.85
GAMEBAR_H = 1.25
GAMEBAR_OFFSET_DESKTOP = 0.375
GAMEBAR_OFFSET_TOUCH = 0.15
local menupath = core.get_mainmenu_path()
local basepath = core.get_builtin_path()
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
@ -89,7 +96,7 @@ local function init_globals()
mm_game_theme.set_engine() -- This is just a fallback.
-- Create main tabview
local tv_main = tabview_create("maintab", {x = 15.5, y = 7.1}, {x = 0, y = 0})
local tv_main = tabview_create("maintab", {x = MAIN_TAB_W, y = MAIN_TAB_H}, {x = 0, y = 0})
tv_main:set_autosave_tab(true)
tv_main:add(tabs.local_game)

View file

@ -92,10 +92,16 @@ function singleplayer_refresh_gamebar()
end
end
local ENABLE_TOUCH = core.settings:get_bool("enable_touch")
local gamebar_pos_y = MAIN_TAB_H
+ TABHEADER_H -- tabheader included in formspec size
+ (ENABLE_TOUCH and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP)
local btnbar = buttonbar_create(
"game_button_bar",
core.settings:get_bool("touch_gui") and {x = 0, y = 7.25} or {x = 0, y = 7.475},
{x = 15.5, y = 1.25},
{x = 0, y = gamebar_pos_y},
{x = MAIN_TAB_W, y = GAMEBAR_H},
"#000000",
game_buttonbar_button_handler)

View file

@ -217,8 +217,9 @@ local function init()
-- Wrap register_lbm() to automatically instrument lbms.
local orig_register_lbm = core.register_lbm
core.register_lbm = function(spec)
spec.action = instrument {
func = spec.action,
local k = spec.bulk_action ~= nil and "bulk_action" or "action"
spec[k] = instrument {
func = spec[k],
class = "LBM",
label = spec.label or spec.name,
}

View file

@ -403,6 +403,11 @@ enable_clouds (Clouds) bool true
# Requires: enable_clouds
enable_3d_clouds (3D clouds) bool true
# Use smooth cloud shading.
#
# Requires: enable_3d_clouds, enable_clouds
soft_clouds (Soft clouds) bool false
[**Filtering and Antialiasing]
# Use mipmaps when scaling textures. May slightly increase performance,
@ -505,6 +510,11 @@ water_wave_length (Waving liquids wavelength) float 20.0 0.1
# Requires: shaders, enable_waving_water
water_wave_speed (Waving liquids wave speed) float 5.0
# When enabled, liquid reflections are simulated.
#
# Requires: shaders, enable_waving_water, enable_dynamic_shadows
enable_water_reflections (Liquid reflections) bool false
[**Dynamic shadows]
# Set to true to enable Shadow Mapping.
@ -661,6 +671,18 @@ bloom_radius (Bloom Radius) float 1 0.1 8
# Requires: shaders, enable_post_processing, enable_bloom
enable_volumetric_lighting (Volumetric lighting) bool false
[**Other Effects]
# Simulate translucency when looking at foliage in the sunlight.
#
# Requires: shaders, enable_dynamic_shadows
enable_translucent_foliage (Translucent foliage) bool false
# Apply specular shading to nodes.
#
# Requires: shaders, enable_dynamic_shadows
enable_node_specular (Node specular) bool false
[*Audio]
# Volume of all sounds.
@ -1030,7 +1052,7 @@ mapgen_limit (Map generation limit) int 31007 0 31007
# Global map generation attributes.
# In Mapgen v6 the 'decorations' flag controls all decorations except trees
# and jungle grass, in all other mapgens this flag controls all decorations.
mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores
mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores
[*Biome API]
@ -1049,7 +1071,7 @@ mg_biome_np_humidity_blend (Humidity blend noise) noise_params_2d 0, 1.5, (8, 8,
[*Mapgen V5]
# Map generation attributes specific to Mapgen v5.
mgv5_spflags (Mapgen V5 specific flags) flags caverns caverns,nocaverns
mgv5_spflags (Mapgen V5 specific flags) flags caverns caverns
# Controls width of tunnels, a smaller value creates wider tunnels.
# Value >= 10.0 completely disables generation of tunnels and avoids the
@ -1123,7 +1145,7 @@ mgv5_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2
# When the 'snowbiomes' flag is enabled jungles are automatically enabled and
# the 'jungles' flag is ignored.
# The 'temples' flag disables generation of desert temples. Normal dungeons will appear instead.
mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees,notemples
mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples
# Deserts occur when np_biome exceeds this value.
# When the 'snowbiomes' flag is enabled, this is ignored.
@ -1179,7 +1201,7 @@ mgv6_np_apple_trees (Apple trees noise) noise_params_2d 0, 1, (100, 100, 100), 3
# 'ridges': Rivers.
# 'floatlands': Floating land masses in the atmosphere.
# 'caverns': Giant caves deep underground.
mgv7_spflags (Mapgen V7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns,nomountains,noridges,nofloatlands,nocaverns
mgv7_spflags (Mapgen V7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns
# Y of mountain density gradient zero level. Used to shift mountains vertically.
mgv7_mount_zero_level (Mountain zero level) int 0 -31000 31000
@ -1313,7 +1335,7 @@ mgv7_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2
[*Mapgen Carpathian]
# Map generation attributes specific to Mapgen Carpathian.
mgcarpathian_spflags (Mapgen Carpathian specific flags) flags caverns,norivers caverns,rivers,nocaverns,norivers
mgcarpathian_spflags (Mapgen Carpathian specific flags) flags caverns,norivers caverns,rivers
# Defines the base ground level.
mgcarpathian_base_level (Base ground level) float 12.0
@ -1422,7 +1444,7 @@ mgcarpathian_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 50
# Map generation attributes specific to Mapgen Flat.
# Occasional lakes and hills can be added to the flat world.
mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns,nolakes,nohills,nocaverns
mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns
# Y of flat ground.
mgflat_ground_level (Ground level) int 8 -31000 31000
@ -1506,7 +1528,7 @@ mgflat_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0,
# Map generation attributes specific to Mapgen Fractal.
# 'terrain' enables the generation of non-fractal terrain:
# ocean, islands and underground.
mgfractal_spflags (Mapgen Fractal specific flags) flags terrain terrain,noterrain
mgfractal_spflags (Mapgen Fractal specific flags) flags terrain terrain
# Controls width of tunnels, a smaller value creates wider tunnels.
# Value >= 10.0 completely disables generation of tunnels and avoids the
@ -1640,7 +1662,7 @@ mgfractal_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500),
# 'vary_river_depth': If enabled, low humidity and high heat causes rivers
# to become shallower and occasionally dry.
# 'altitude_dry': Reduces humidity with altitude.
mgvalleys_spflags (Mapgen Valleys specific flags) flags altitude_chill,humid_rivers,vary_river_depth,altitude_dry altitude_chill,humid_rivers,vary_river_depth,altitude_dry,noaltitude_chill,nohumid_rivers,novary_river_depth,noaltitude_dry
mgvalleys_spflags (Mapgen Valleys specific flags) flags altitude_chill,humid_rivers,vary_river_depth,altitude_dry altitude_chill,humid_rivers,vary_river_depth,altitude_dry
# The vertical distance over which heat drops by 20 if 'altitude_chill' is
# enabled. Also, the vertical distance over which humidity drops by 10 if

View file

@ -11,7 +11,6 @@ attribute vec2 inTexCoord0;
uniform mat4 uWVPMatrix;
uniform mat4 uWVMatrix;
uniform mat4 uNMatrix;
uniform mat4 uTMatrix0;
uniform float uThickness;

View file

@ -1,4 +1,4 @@
uniform lowp vec4 emissiveColor;
uniform lowp vec4 materialColor;
varying lowp vec4 varColor;
@ -14,7 +14,7 @@ void main(void)
vec4 color = inVertexColor;
#endif
color *= emissiveColor;
color *= materialColor;
varColor = color;
eyeVec = -(mWorldView * inVertexPosition).xyz;

View file

@ -1,3 +1,7 @@
#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT)
#define MATERIAL_WAVING_LIQUID 1
#endif
uniform sampler2D baseTexture;
uniform vec3 dayLight;
@ -7,6 +11,7 @@ uniform float fogShadingParameter;
// The cameraOffset is the current center of the visible world.
uniform highp vec3 cameraOffset;
uniform vec3 cameraPosition;
uniform float animationTimer;
#ifdef ENABLE_DYNAMIC_SHADOWS
// shadow texture
@ -20,6 +25,7 @@ uniform float animationTimer;
uniform vec4 CameraPos;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
uniform vec3 shadow_tint;
varying float adj_shadow_strength;
varying float cosLight;
@ -47,6 +53,49 @@ varying highp vec3 eyeVec;
varying float nightRatio;
#ifdef ENABLE_DYNAMIC_SHADOWS
#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER)
vec4 perm(vec4 x)
{
return mod(((x * 34.0) + 1.0) * x, 289.0);
}
// Corresponding gradient of snoise
vec3 gnoise(vec3 p){
vec3 a = floor(p);
vec3 d = p - a;
vec3 dd = 6.0 * d * (1.0 - d);
d = d * d * (3.0 - 2.0 * d);
vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0);
vec4 k1 = perm(b.xyxy);
vec4 k2 = perm(k1.xyxy + b.zzww);
vec4 c = k2 + a.zzzz;
vec4 k3 = perm(c);
vec4 k4 = perm(c + 1.0);
vec4 o1 = fract(k3 * (1.0 / 41.0));
vec4 o2 = fract(k4 * (1.0 / 41.0));
vec4 o3 = o2 * d.z + o1 * (1.0 - d.z);
vec2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x);
vec4 dz1 = (o2 - o1) * dd.z;
vec2 dz2 = dz1.yw * d.x + dz1.xz * (1.0 - d.x);
vec2 dx = (o3.yw - o3.xz) * dd.x;
return vec3(
dx.y * d.y + dx.x * (1. - d.y),
(o4.y - o4.x) * dd.y,
dz2.y * d.y + dz2.x * (1. - d.y)
);
}
vec2 wave_noise(vec3 p, float off) {
return (gnoise(p + vec3(0.0, 0.0, off)) * 0.4 + gnoise(2.0 * p + vec3(0.0, off, off)) * 0.2 + gnoise(3.0 * p + vec3(0.0, off, off)) * 0.225 + gnoise(4.0 * p + vec3(-off, off, 0.0)) * 0.2).xz;
}
#endif
// assuming near is always 1.0
float getLinearDepth()
@ -66,6 +115,14 @@ float mtsmoothstep(in float edge0, in float edge1, in float x)
return t * t * (3.0 - 2.0 * t);
}
float shadowCutoff(float x) {
#if defined(ENABLE_TRANSLUCENT_FOLIAGE) && MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES
return mtsmoothstep(0.0, 0.002, x);
#else
return step(0.0, x);
#endif
}
#ifdef COLORED_SHADOWS
// c_precision of 128 fits within 7 base-10 digits
@ -92,10 +149,10 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
{
vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba;
float visibility = step(0.0, realDistance - texDepth.r);
float visibility = shadowCutoff(realDistance - texDepth.r);
vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g));
if (visibility < 0.1) {
visibility = step(0.0, realDistance - texDepth.b);
visibility = shadowCutoff(realDistance - texDepth.b);
result = vec4(visibility, unpackColor(texDepth.a));
}
return result;
@ -106,7 +163,7 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{
float texDepth = texture2D(shadowsampler, smTexCoord.xy).r;
float visibility = step(0.0, realDistance - texDepth);
float visibility = shadowCutoff(realDistance - texDepth);
return visibility;
}
@ -378,6 +435,9 @@ void main(void)
vec4 col = vec4(color.rgb * varColor.rgb, 1.0);
#ifdef ENABLE_DYNAMIC_SHADOWS
// Fragment normal, can differ from vNormal which is derived from vertex normals.
vec3 fNormal = vNormal;
if (f_shadow_strength > 0.0) {
float shadow_int = 0.0;
vec3 shadow_color = vec3(0.0, 0.0, 0.0);
@ -414,12 +474,19 @@ void main(void)
// Power ratio was measured on torches in MTG (brightness = 14).
float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6);
float shadow_uncorrected = shadow_int;
// Apply self-shadowing when light falls at a narrow angle to the surface
// Cosine of the cut-off angle.
const float self_shadow_cutoff_cosine = 0.035;
if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) {
shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES || MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS)
// Prevents foliage from becoming insanely bright outside the shadow map.
shadow_uncorrected = mix(shadow_int, shadow_uncorrected, clamp(distance_rate * 4.0 - 3.0, 0.0, 1.0));
#endif
}
shadow_int *= f_adj_shadow_strength;
@ -428,8 +495,60 @@ void main(void)
col.rgb =
adjusted_night_ratio * col.rgb + // artificial light
(1.0 - adjusted_night_ratio) * ( // natural light
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color) * (1.0 - shadow_tint)) + // filtered texture color
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
vec3 reflect_ray = -normalize(v_LightDirection - fNormal * dot(v_LightDirection, fNormal) * 2.0);
vec3 viewVec = normalize(worldPosition + cameraOffset - cameraPosition);
// Water reflections
#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER)
vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0);
float off = animationTimer * WATER_WAVE_SPEED * 10.0;
wavePos.x /= WATER_WAVE_LENGTH * 3.0;
wavePos.z /= WATER_WAVE_LENGTH * 2.0;
// This is an analogous method to the bumpmap, except we get the gradient information directly from gnoise.
vec2 gradient = wave_noise(wavePos, off);
fNormal = normalize(normalize(fNormal) + vec3(gradient.x, 0., gradient.y) * WATER_WAVE_HEIGHT * abs(fNormal.y) * 0.25);
reflect_ray = -normalize(v_LightDirection - fNormal * dot(v_LightDirection, fNormal) * 2.0);
float fresnel_factor = dot(fNormal, viewVec);
float brightness_factor = 1.0 - adjusted_night_ratio;
// A little trig hack. We go from the dot product of viewVec and normal to the dot product of viewVec and tangent to apply a fresnel effect.
fresnel_factor = clamp(pow(1.0 - fresnel_factor * fresnel_factor, 8.0), 0.0, 1.0) * 0.8 + 0.2;
col.rgb *= 0.5;
vec3 reflection_color = mix(vec3(max(fogColor.r, max(fogColor.g, fogColor.b))), fogColor.rgb, f_shadow_strength);
// Sky reflection
col.rgb += reflection_color * pow(fresnel_factor, 2.0) * 0.5 * brightness_factor;
vec3 water_reflect_color = 12.0 * dayLight * fresnel_factor * mtsmoothstep(0.85, 0.9, pow(clamp(dot(reflect_ray, viewVec), 0.0, 1.0), 32.0)) * max(1.0 - shadow_uncorrected, 0.0);
// This line exists to prevent ridiculously bright reflection colors.
water_reflect_color /= clamp(max(water_reflect_color.r, max(water_reflect_color.g, water_reflect_color.b)) * 0.375, 1.0, 400.0);
col.rgb += water_reflect_color * f_adj_shadow_strength * brightness_factor;
#endif
#if (defined(ENABLE_NODE_SPECULAR) && !defined(MATERIAL_WAVING_LIQUID))
// Apply specular to blocks.
if (dot(v_LightDirection, vNormal) < 0.0) {
float intensity = 2.0 * (1.0 - (base.r * varColor.r));
const float specular_exponent = 5.0;
const float fresnel_exponent = 4.0;
col.rgb +=
intensity * dayLight * (1.0 - nightRatio) * (1.0 - shadow_uncorrected) * f_adj_shadow_strength *
pow(max(dot(reflect_ray, viewVec), 0.0), fresnel_exponent) * pow(1.0 - abs(dot(viewVec, fNormal)), specular_exponent);
}
#endif
#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES) && defined(ENABLE_TRANSLUCENT_FOLIAGE)
// Simulate translucent foliage.
col.rgb += 4.0 * dayLight * base.rgb * normalize(base.rgb * varColor.rgb * varColor.rgb) * f_adj_shadow_strength * pow(max(-dot(v_LightDirection, viewVec), 0.0), 4.0) * max(1.0 - shadow_uncorrected, 0.0);
#endif
}
#endif
@ -444,7 +563,13 @@ void main(void)
// Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(fogColor, col, clarity);
float fogColorMax = max(max(fogColor.r, fogColor.g), fogColor.b);
// Prevent zero division.
if (fogColorMax < 0.0000001) fogColorMax = 1.0;
// For high clarity (light fog) we tint the fog color.
// For this to not make the fog color artificially dark we need to normalize using the
// fog color's brightest value. We then blend our base color with this to make the fog.
col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity);
col = vec4(col.rgb, base.a);
gl_FragData[0] = col;

View file

@ -256,7 +256,9 @@ void main(void)
z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar;
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (shadow_pos + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
#if !defined(ENABLE_TRANSLUCENT_FOLIAGE) || MATERIAL_TYPE != TILE_MATERIAL_WAVING_LEAVES
shadow_position.z -= z_bias;
#endif
perspective_factor = pFactor;
if (f_timeofday < 0.2) {

View file

@ -20,6 +20,7 @@ uniform float animationTimer;
uniform vec4 CameraPos;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
uniform vec3 shadow_tint;
varying float adj_shadow_strength;
varying float cosLight;
@ -432,7 +433,7 @@ void main(void)
col.rgb =
adjusted_night_ratio * col.rgb + // artificial light
(1.0 - adjusted_night_ratio) * ( // natural light
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color) * (1.0 - shadow_tint)) + // filtered texture color
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
}
#endif
@ -448,7 +449,13 @@ void main(void)
// Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(fogColor, col, clarity);
float fogColorMax = max(max(fogColor.r, fogColor.g), fogColor.b);
// Prevent zero division.
if (fogColorMax < 0.0000001) fogColorMax = 1.0;
// For high clarity (light fog) we tint the fog color.
// For this to not make the fog color artificially dark we need to normalize using the
// fog color's brightest value. We then blend our base color with this to make the fog.
col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity);
col = vec4(col.rgb, base.a);
gl_FragData[0] = col;

View file

@ -1,7 +1,7 @@
uniform mat4 mWorld;
uniform vec3 dayLight;
uniform float animationTimer;
uniform lowp vec4 emissiveColor;
uniform lowp vec4 materialColor;
varying vec3 vNormal;
varying vec3 vPosition;
@ -91,7 +91,7 @@ float directional_ambient(vec3 normal)
void main(void)
{
varTexCoord = (mTexture * inTexCoord0).st;
varTexCoord = (mTexture * vec4(inTexCoord0.xy, 1.0, 1.0)).st;
gl_Position = mWorldViewProj * inVertexPosition;
vPosition = gl_Position.xyz;
@ -115,7 +115,7 @@ void main(void)
vec4 color = inVertexColor;
#endif
color *= emissiveColor;
color *= materialColor;
// The alpha gives the ratio of sunlight in the incoming light.
nightRatio = 1.0 - color.a;

View file

@ -1,6 +1,6 @@
uniform lowp vec4 emissiveColor;
uniform lowp vec4 materialColor;
void main(void)
{
gl_FragColor = emissiveColor;
gl_FragColor = materialColor;
}

View file

@ -46,7 +46,9 @@ float sampleVolumetricLight(vec2 uv, vec3 lightVec, float rawDepth)
if (min(samplepos.x, samplepos.y) > 0. && max(samplepos.x, samplepos.y) < 1.)
result += texture2D(depthmap, samplepos).r < 1. ? 0.0 : 1.0;
}
return result / samples;
// We use the depth map to approximate the effect of depth on the light intensity.
// The exponent was chosen based on aesthetic preference.
return result / samples * pow(texture2D(depthmap, uv).r, 128.0);
}
vec3 getDirectLightScatteringAtGround(vec3 v_LightDirection)

View file

@ -338,8 +338,6 @@ Call these functions only at load time!
is checked to see if the command exists, but after the input is parsed.
* Return `true` to mark the command as handled, which means that the default
handlers will be prevented.
* `minetest.register_on_death(function())`
* Called when the local player dies
* `minetest.register_on_hp_modification(function(hp))`
* Called when server modified player's HP
* `minetest.register_on_damage_taken(function(hp))`
@ -487,8 +485,6 @@ Call these functions only at load time!
* Returns `false` if the client is already disconnecting otherwise returns `true`.
* `minetest.get_server_info()`
* Returns [server info](#server-info).
* `minetest.send_respawn()`
* Sends a respawn request to the server.
### Storage API
* `minetest.get_mod_storage()`:

View file

@ -22,6 +22,7 @@ General options and their default values:
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
PRECOMPILE_HEADERS=FALSE - Precompile some headers (experimental; requires CMake 3.16 or later)
PRECOMPILED_HEADERS_PATH= - Path to a file listing all headers to precompile (default points to src/precompiled_headers.txt)
USE_SDL2=TRUE - Build with SDL2; Enables IrrlichtMt device SDL2
ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
@ -39,10 +40,15 @@ General options and their default values:
ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default
INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest
USE_GPROF=FALSE - Enable profiling using GProf
BUILD_WITH_TRACY=FALSE - Fetch and build with the Tracy profiler client
FETCH_TRACY_GIT_TAG=master - Git tag for fetching Tracy client. Match with your server (gui) version
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
Library specific options:
SDL2_DLL - Only if building with SDL2 on Windows; path to libSDL2.dll
SDL2_INCLUDE_DIRS - Only if building with SDL2; directory where SDL.h is located
SDL2_LIBRARIES - Only if building with SDL2; path to libSDL2.a/libSDL2.so/libSDL2.lib
CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib

View file

@ -1,6 +1,6 @@
# Miscellaneous
## Profiling Minetest on Linux
## Profiling Minetest on Linux with perf
We will be using a tool called "perf", which you can get by installing `perf` or `linux-perf` or `linux-tools-common`.
@ -36,3 +36,54 @@ Give both files to the developer and also provide:
* commit the source was built from and/or modified source code (if applicable)
Hotspot will resolve symbols correctly when pointing the sysroot option at the collected libs.
## Profiling with Tracy
[Tracy](https://github.com/wolfpld/tracy) is
> A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling
> profiler for games and other applications.
It allows one to annotate important functions and generate traces, where one can
see when each individual function call happened, and how long it took.
Tracy can also record when frames, e.g. server step, start and end, and inspect
frames that took longer than usual. Minetest already contains annotations for
its frames.
See also [Tracy's official documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf).
### Installing
Tracy consists of a client (Minetest) and a server (the gui).
Install the server, e.g. using your package manager.
### Building
Build Minetest with `-DDBUILD_WITH_TRACY=1`, this will fetch Tracy for building
the Tracy client. And use `FETCH_TRACY_GIT_TAG` to get a version matching your
Tracy server, e.g. `-DFETCH_TRACY_GIT_TAG=v0.11.0` if it's `0.11.0`.
To actually use Tracy, you also have to enable it with Tracy's build options:
```
-DTRACY_ENABLE=1 -DTRACY_ONLY_LOCALHOST=1
```
See Tracy's documentation for more build options.
### Using in C++
Start the Tracy server and Minetest. You should see Minetest in the menu.
To actually get useful traces, you have to annotate functions with `ZoneScoped`
macros and recompile. Please refer to Tracy's official documentation.
### Using in Lua
Tracy also supports Lua.
If built with Tracy, Minetest loads its API in the global `tracy` table.
See Tracy's official documentation for more information.
Note: The whole Tracy Lua API is accessible to all mods. And we don't check if it
is or becomes insecure. Run untrusted mods at your own risk.

View file

@ -1470,7 +1470,8 @@ Look for examples in `games/devtest` or `games/minetest_game`.
'Connected Glass'.
* `allfaces`
* Often used for partially-transparent nodes.
* External and internal sides of textures are visible.
* External sides of textures, and unlike other drawtypes, the external sides
of other blocks, are visible from the inside.
* `allfaces_optional`
* Often used for leaves nodes.
* This switches between `normal`, `glasslike` and `allfaces` according to
@ -5522,6 +5523,10 @@ Utilities
override_item_remove_fields = true,
-- The predefined hotbar is a Lua HUD element of type `hotbar` (5.10.0)
hotbar_hud_element = true,
-- Bulk LBM support (5.10.0)
bulk_lbms = true,
-- ABM supports field without_neighbors (5.10.0)
abm_without_neighbors = true,
}
```
@ -5580,8 +5585,8 @@ Utilities
},
-- Estimated maximum formspec size before Minetest will start shrinking the
-- formspec to fit. For a fullscreen formspec, use a size 10-20% larger than
-- this and `padding[-0.01,-0.01]`.
-- formspec to fit. For a fullscreen formspec, use this formspec size and
-- `padding[0,0]`. `bgcolor[;true]` is also recommended.
max_formspec_size = {
x = 20,
y = 11.25
@ -5655,6 +5660,13 @@ Utilities
* `minetest.colorspec_to_bytes(colorspec)`: Converts a ColorSpec to a raw
string of four bytes in an RGBA layout, returned as a string.
* `colorspec`: The ColorSpec to convert
* `minetest.colorspec_to_table(colorspec)`: Converts a ColorSpec into RGBA table
form. If the ColorSpec is invalid, returns `nil`. You can use this to parse
ColorStrings.
* `colorspec`: The ColorSpec to convert
* `minetest.time_to_day_night_ratio(time_of_day)`: Returns a "day-night ratio" value
(as accepted by `ObjectRef:override_day_night_ratio`) that is equivalent to
the given "time of day" value (as returned by `minetest.get_timeofday`).
* `minetest.encode_png(width, height, data, [compression])`: Encode a PNG
image and return it in string form.
* `width`: Width of the image
@ -5838,8 +5850,13 @@ Call these functions only at load time!
* `clicker`: ObjectRef - Object that acted upon `player`, may or may not be a player
* `minetest.register_on_player_hpchange(function(player, hp_change, reason), modifier)`
* Called when the player gets damaged or healed
* When `hp == 0`, damage doesn't trigger this callback.
* When `hp == hp_max`, healing does still trigger this callback.
* `player`: ObjectRef of the player
* `hp_change`: the amount of change. Negative when it is damage.
* Historically, the new HP value was clamped to [0, 65535] before
calculating the HP change. This clamping has been removed as of
Minetest 5.10.0
* `reason`: a PlayerHPChangeReason table.
* The `type` field will have one of the following values:
* `set_hp`: A mod or the engine called `set_hp` without
@ -5860,6 +5877,7 @@ Call these functions only at load time!
* `minetest.register_on_dieplayer(function(ObjectRef, reason))`
* Called when a player dies
* `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange
* For customizing the death screen, see `minetest.show_death_screen`.
* `minetest.register_on_respawnplayer(function(ObjectRef))`
* Called when player is to be respawned
* Called _before_ repositioning of player occurs
@ -6136,6 +6154,8 @@ Environment access
* `minetest.swap_node(pos, node)`
* Swap node at position with another.
* This keeps the metadata intact and will not run con-/destructor callbacks.
* `minetest.bulk_swap_node({pos1, pos2, pos3, ...}, node)`
* Equivalent to `minetest.swap_node` but in bulk.
* `minetest.remove_node(pos)`: Remove a node
* Equivalent to `minetest.set_node(pos, {name="air"})`, but a bit faster.
* `minetest.get_node(pos)`
@ -6562,6 +6582,13 @@ Formspec
* `"INV"`: something failed
* `"CHG"`: has been changed
* `"VAL"`: not changed
* `minetest.show_death_screen(player, reason)`
* Called when the death screen should be shown.
* `player` is an ObjectRef, `reason` is a PlayerHPChangeReason table or nil.
* By default, this shows a simple formspec with the option to respawn.
Respawning is done via `ObjectRef:respawn`.
* You can override this to show a custom death screen.
* For general death handling, use `minetest.register_on_dieplayer` instead.
Item handling
-------------
@ -6999,10 +7026,11 @@ Bans
* Returns boolean indicating success
* `minetest.unban_player_or_ip(ip_or_name)`: remove ban record matching
IP address or name
* `minetest.kick_player(name, [reason])`: disconnect a player with an optional
* `minetest.kick_player(name[, reason[, reconnect]])`: disconnect a player with an optional
reason.
* Returns boolean indicating success (false if player nonexistent)
* `minetest.disconnect_player(name, [reason])`: disconnect a player with an
* If `reconnect` is true, allow the user to reconnect.
* `minetest.disconnect_player(name[, reason[, reconnect]])`: disconnect a player with an
optional reason, this will not prefix with 'Kicked: ' like kick_player.
If no reason is given, it will default to 'Disconnected.'
* Returns boolean indicating success (false if player nonexistent)
@ -8508,12 +8536,15 @@ child will follow movement and rotation of that bone.
if set to zero the clouds are rendered flat.
* `speed`: 2D cloud speed + direction in nodes per second
(default `{x=0, z=-2}`).
* `shadow`: shadow color, applied to the base of the cloud
(default `#cccccc`).
* `get_clouds()`: returns a table with the current cloud parameters as in
`set_clouds`.
* `override_day_night_ratio(ratio or nil)`
* `0`...`1`: Overrides day-night ratio, controlling sunlight to a specific
amount.
* Passing no arguments disables override, defaulting to sunlight based on day-night cycle
* See also `minetest.time_to_day_night_ratio`,
* `get_day_night_ratio()`: returns the ratio or nil if it isn't overridden
* `set_local_animation(idle, walk, dig, walk_while_dig, frame_speed)`:
set animation for player model in third person view.
@ -8540,11 +8571,23 @@ child will follow movement and rotation of that bone.
* Passing no arguments resets lighting to its default values.
* `light_definition` is a table with the following optional fields:
* `saturation` sets the saturation (vividness; default: `1.0`).
* values > 1 increase the saturation
* values in [0,1] decrease the saturation
* It is applied according to the function `result = b*(1-s) + c*s`, where:
* `c` is the original color
* `b` is the greyscale version of the color with the same luma
* `s` is the saturation set here
* The resulting color always has the same luma (perceived brightness) as the original.
* This means that:
* values > 1 oversaturate
* values < 1 down to 0 desaturate, 0 being entirely greyscale
* values < 0 cause an effect similar to inversion,
but keeping original luma and being symmetrical in terms of saturation
(eg. -1 and 1 is the same saturation and luma, but different hues)
* `shadows` is a table that controls ambient shadows
* `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness)
* This value has no effect on clients who have the "Dynamic Shadows" shader disabled.
* `tint` tints the shadows with the provided color, with RGB values ranging from 0 to 255.
(default `{r=0, g=0, b=0}`)
* This value has no effect on clients who have the "Dynamic Shadows" shader disabled.
* `exposure` is a table that controls automatic exposure.
The basic exposure factor equation is `e = 2^exposure_correction / clamp(luminance, 2^luminance_min, 2^luminance_max)`
* `luminance_min` set the lower luminance boundary to use in the calculation (default: `-3.0`)
@ -9071,6 +9114,11 @@ Used by `minetest.register_abm`.
-- If left out or empty, any neighbor will do.
-- `group:groupname` can also be used here.
without_neighbors = {"default:lava_source", "default:lava_flowing"},
-- Only apply `action` to nodes that have no one of these neighbors.
-- If left out or empty, it has no effect.
-- `group:groupname` can also be used here.
interval = 10.0,
-- Operation interval in seconds
@ -9106,7 +9154,12 @@ Used by `minetest.register_lbm`.
A loading block modifier (LBM) is used to define a function that is called for
specific nodes (defined by `nodenames`) when a mapblock which contains such nodes
gets activated (not loaded!)
gets activated (not loaded!).
Note: LBMs operate on a "snapshot" of node positions taken once before they are triggered.
That means if an LBM callback adds a node, it won't be taken into account.
However the engine guarantees that when the callback is called that all given position(s)
contain a matching node.
```lua
{
@ -9130,7 +9183,13 @@ gets activated (not loaded!)
action = function(pos, node, dtime_s) end,
-- Function triggered for each qualifying node.
-- `dtime_s` is the in-game time (in seconds) elapsed since the block
-- was last active
-- was last active.
bulk_action = function(pos_list, dtime_s) end,
-- Function triggered with a list of all applicable node positions at once.
-- This can be provided as an alternative to `action` (not both).
-- Available since `minetest.features.bulk_lbms` (5.10.0)
-- `dtime_s`: as above
}
```
@ -9337,9 +9396,17 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- If specified as a table, the field to be used is selected according to
-- the current `pointed_thing`.
-- There are three possible TouchInteractionMode values:
-- * "user" (meaning depends on client-side settings)
-- * "long_dig_short_place" (long tap = dig, short tap = place)
-- * "short_dig_long_place" (short tap = dig, long tap = place)
-- * "user":
-- * For `pointed_object`: Equivalent to "short_dig_long_place" if the
-- client-side setting "touch_punch_gesture" is "short_tap" (the
-- default value) and the item is able to punch (i.e. has no on_use
-- callback defined).
-- Equivalent to "long_dig_short_place" otherwise.
-- * For `pointed_node` and `pointed_nothing`:
-- Equivalent to "long_dig_short_place".
-- * The behavior of "user" may change in the future.
-- The default value is "user".
sound = {
@ -11368,6 +11435,16 @@ Functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshi
See http://bitop.luajit.org/ for advanced information.
Tracy Profiler
--------------
Minetest can be built with support for the Tracy profiler, which can also be
useful for profiling mods and is exposed to Lua as the global `tracy`.
See doc/developing/misc.md for details.
Note: This is a development feature and not covered by compatibility promises.
Error Handling
--------------

View file

@ -253,8 +253,8 @@ GUI
},
-- Estimated maximum formspec size before Minetest will start shrinking the
-- formspec to fit. For a fullscreen formspec, use a size 10-20% larger than
-- this and `padding[-0.01,-0.01]`.
-- formspec to fit. For a fullscreen formspec, use this formspec size and
-- `padding[0,0]`. `bgcolor[;true]` is also recommended.
max_formspec_size = {
x = 20,
y = 11.25

View file

@ -154,3 +154,36 @@ minetest.register_chatcommand("bench_bulk_get_node", {
return true, msg
end,
})
minetest.register_chatcommand("bench_bulk_swap_node", {
params = "",
description = "Benchmark: Bulk-swap 99×99×99 stone nodes",
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not player then
return false, "No player."
end
local pos_list = get_positions_cube(player:get_pos())
minetest.chat_send_player(name, "Benchmarking minetest.bulk_swap_node. Warming up ...")
-- warm up because first execution otherwise becomes
-- significantly slower
minetest.bulk_swap_node(pos_list, {name = "mapgen_stone"})
minetest.chat_send_player(name, "Warming up finished, now benchmarking ...")
local start_time = minetest.get_us_time()
for i=1,#pos_list do
minetest.swap_node(pos_list[i], {name = "mapgen_stone"})
end
local middle_time = minetest.get_us_time()
minetest.bulk_swap_node(pos_list, {name = "mapgen_stone"})
local end_time = minetest.get_us_time()
local msg = string.format("Benchmark results: minetest.swap_node loop: %.2f ms; minetest.bulk_swap_node: %.2f ms",
((middle_time - start_time)) / 1000,
((end_time - middle_time)) / 1000
)
return true, msg
end,
})

View file

@ -0,0 +1,6 @@
# Test ABMs
This mod contains a nodes and related ABM actions.
By placing these nodes, you can test basic ABM behaviours.
There are separate tests for ABM `chance`, `interval`, `min_y`, `max_y`, `neighbor` and `without_neighbor` fields.

View file

@ -0,0 +1,12 @@
local S = minetest.get_translator("testnodes")
-- After ABM node
minetest.register_node("testabms:after_abm", {
description = S("After ABM processed node."),
drawtype = "normal",
tiles = { "testabms_after_node.png" },
groups = { dig_immediate = 3 },
})

View file

@ -0,0 +1,56 @@
-- test ABMs with different chances
local S = minetest.get_translator("testnodes")
-- ABM chance 5 node
minetest.register_node("testabms:chance_5", {
description = S("Node for test ABM chance_5"),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Waiting for ABM testabms:chance_5")
end,
})
minetest.register_abm({
label = "testabms:chance_5",
nodenames = "testabms:chance_5",
interval = 10,
chance = 5,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "ABM testabsm:chance_5 changed this node.")
end
})
-- ABM chance 20 node
minetest.register_node("testabms:chance_20", {
description = S("Node for test ABM chance_20"),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Waiting for ABM testabms:chance_20")
end,
})
minetest.register_abm({
label = "testabms:chance_20",
nodenames = "testabms:chance_20",
interval = 10,
chance = 20,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "ABM testabsm:chance_20 changed this node.")
end
})

View file

@ -0,0 +1,7 @@
local path = minetest.get_modpath(minetest.get_current_modname())
dofile(path.."/after_node.lua")
dofile(path.."/chances.lua")
dofile(path.."/intervals.lua")
dofile(path.."/min_max.lua")
dofile(path.."/neighbors.lua")

View file

@ -0,0 +1,56 @@
-- test ABMs with different interval
local S = minetest.get_translator("testnodes")
-- ABM inteval 1 node
minetest.register_node("testabms:interval_1", {
description = S("Node for test ABM interval_1"),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Waiting for ABM testabms:interval_1")
end,
})
minetest.register_abm({
label = "testabms:interval_1",
nodenames = "testabms:interval_1",
interval = 1,
chance = 1,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "ABM testabsm:interval_1 changed this node.")
end
})
-- ABM interval 60 node
minetest.register_node("testabms:interval_60", {
description = S("Node for test ABM interval_60"),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Waiting for ABM testabms:interval_60")
end,
})
minetest.register_abm({
label = "testabms:interval_60",
nodenames = "testabms:interval_60",
interval = 60,
chance = 1,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "ABM testabsm:interval_60 changed this node.")
end
})

View file

@ -0,0 +1,58 @@
-- test ABMs with min_y and max_y
local S = minetest.get_translator("testnodes")
-- ABM min_y node
minetest.register_node("testabms:min_y", {
description = S("Node for test ABM min_y."),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Waiting for ABM testabms:min_y at y "..pos.y.." with min_y = 0")
end,
})
minetest.register_abm({
label = "testabms:min_y",
nodenames = "testabms:min_y",
interval = 10,
chance = 1,
min_y = 0,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "ABM testabsm:min_y changed this node.")
end
})
-- ABM max_y node
minetest.register_node("testabms:max_y", {
description = S("Node for test ABM max_y."),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Waiting for ABM testabms:max_y at y "..pos.y.." with max_y = 0")
end,
})
minetest.register_abm({
label = "testabms:max_y",
nodenames = "testabms:max_y",
interval = 10,
chance = 1,
max_y = 0,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "ABM testabsm:max_y changed this node.")
end
})

View file

@ -0,0 +1,2 @@
name = testabms
description = Contains some nodes for test ABMs.

View file

@ -0,0 +1,99 @@
-- test ABMs with neighbor and without_neighbor
local S = minetest.get_translator("testnodes")
-- ABM required neighbor
minetest.register_node("testabms:required_neighbor", {
description = S("Node for test ABM required_neighbor.") .. "\n"
.. S("Sensitive neighbor node is testnodes:normal."),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext",
"Waiting for ABM testabms:required_neighbor "
.. "(normal drawtype testnode sensitive)")
end,
})
minetest.register_abm({
label = "testabms:required_neighbor",
nodenames = "testabms:required_neighbor",
neighbors = {"testnodes:normal"},
interval = 1,
chance = 1,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext",
"ABM testabsm:required_neighbor changed this node.")
end
})
-- ABM missing neighbor node
minetest.register_node("testabms:missing_neighbor", {
description = S("Node for test ABM missing_neighbor.") .. "\n"
.. S("Sensitive neighbor node is testnodes:normal."),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext",
"Waiting for ABM testabms:missing_neighbor"
.. " (normal drawtype testnode sensitive)")
end,
})
minetest.register_abm({
label = "testabms:missing_neighbor",
nodenames = "testabms:missing_neighbor",
without_neighbors = {"testnodes:normal"},
interval = 1,
chance = 1,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext",
"ABM testabsm:missing_neighbor changed this node.")
end
})
-- ABM required and missing neighbor node
minetest.register_node("testabms:required_missing_neighbor", {
description = S("Node for test ABM required_missing_neighbor.") .. "\n"
.. S("Sensitive neighbor nodes are testnodes:normal and testnodes:glasslike."),
drawtype = "normal",
tiles = { "testabms_wait_node.png" },
groups = { dig_immediate = 3 },
on_construct = function (pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext",
"Waiting for ABM testabms:required_missing_neighbor"
.. " (wint normal drawtype testnode and no glasslike"
.. " drawtype testnode sensitive)")
end,
})
minetest.register_abm({
label = "testabms:required_missing_neighbor",
nodenames = "testabms:required_missing_neighbor",
neighbors = {"testnodes:normal"},
without_neighbors = {"testnodes:glasslike"},
interval = 1,
chance = 1,
action = function (pos)
minetest.swap_node(pos, {name="testabms:after_abm"})
local meta = minetest.get_meta(pos)
meta:set_string("infotext",
"ABM testabsm:required_missing_neighbor changed this node.")
end
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

View file

@ -66,7 +66,7 @@ local inv_style_fs = [[
-- Some textures from textures/base/pack and Devtest, with many different sizes
-- and aspect ratios.
local image_column = "image,0=logo.png,1=rare_controls.png,2=checkbox_16.png," ..
local image_column = "image,0=logo.png,1=crack_anylength.png^[invert:rgb,2=checkbox_16.png," ..
"3=checkbox_32.png,4=checkbox_64.png,5=default_lava.png," ..
"6=progress_bar.png,7=progress_bar_bg.png"
local words = {

View file

@ -1,18 +1,30 @@
local function show_fullscreen_fs(name)
local window = minetest.get_player_window_information(name)
if not window then
return false, "Unable to get window info"
end
local function window_info_equal(a, b)
return
-- size
a.size.x == b.size.x and a.size.y == b.size.y and
-- real_gui_scaling, real_hud_scaling
a.real_gui_scaling == b.real_gui_scaling and
a.real_hud_scaling == b.real_hud_scaling and
-- max_formspec_size
a.max_formspec_size.x == b.max_formspec_size.x and
a.max_formspec_size.y == b.max_formspec_size.y and
-- touch_controls
a.touch_controls == b.touch_controls
end
local last_window_info = {}
local function show_fullscreen_fs(name, window)
print(dump(window))
local size = { x = window.max_formspec_size.x * 1.1, y = window.max_formspec_size.y * 1.1 }
local size = window.max_formspec_size
local touch_text = window.touch_controls and "Touch controls enabled" or
"Touch controls disabled"
local fs = {
"formspec_version[4]",
("size[%f,%f]"):format(size.x, size.y),
"padding[-0.01,-0.01]",
"padding[0,0]",
"bgcolor[;true]",
("button[%f,%f;1,1;%s;%s]"):format(0, 0, "tl", "TL"),
("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, 0, "tr", "TR"),
("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, size.y - 1, "br", "BR"),
@ -23,10 +35,37 @@ local function show_fullscreen_fs(name)
}
minetest.show_formspec(name, "testfullscreenfs:fs", table.concat(fs))
return true, ("Calculated size of %f, %f"):format(size.x, size.y)
minetest.chat_send_player(name, ("Calculated size of %f, %f"):format(size.x, size.y))
last_window_info[name] = window
end
minetest.register_chatcommand("testfullscreenfs", {
func = show_fullscreen_fs,
func = function(name)
local window = minetest.get_player_window_information(name)
if not window then
return false, "Unable to get window info"
end
show_fullscreen_fs(name, window)
return true
end,
})
minetest.register_globalstep(function()
for name, last_window in pairs(last_window_info) do
local window = minetest.get_player_window_information(name)
if window and not window_info_equal(last_window, window) then
show_fullscreen_fs(name, window)
end
end
end)
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "testfullscreenfs:fs" and fields.quit then
last_window_info[player:get_player_name()] = nil
end
end)
minetest.register_on_leaveplayer(function(player)
last_window_info[player:get_player_name()] = nil
end)

View file

@ -98,6 +98,23 @@ minetest.register_node("testnodes:allfaces", {
groups = { dig_immediate = 3 },
})
minetest.register_node("testnodes:allfaces_6", {
description = S("\"allfaces 6 Textures\" Drawtype Test Node").."\n"..
S("Transparent node with visible internal backfaces"),
drawtype = "allfaces",
paramtype = "light",
tiles = {
"testnodes_allfaces.png^[colorize:red",
"testnodes_allfaces.png^[colorize:orange",
"testnodes_allfaces.png^[colorize:yellow",
"testnodes_allfaces.png^[colorize:green",
"testnodes_allfaces.png^[colorize:blue",
"testnodes_allfaces.png^[colorize:purple"
},
groups = { dig_immediate = 3 },
})
local allfaces_optional_tooltip = ""..
S("Rendering depends on 'leaves_style' setting:").."\n"..
S("* 'fancy': transparent with visible internal backfaces").."\n"..

View file

@ -0,0 +1,17 @@
local function assert_colors_equal(c1, c2)
if type(c1) == "table" and type(c2) == "table" then
assert(c1.r == c2.r and c1.g == c2.g and c1.b == c2.b and c1.a == c2.a)
else
assert(c1 == c2)
end
end
local function test_color_conversion()
assert_colors_equal(core.colorspec_to_table("#fff"), {r = 255, g = 255, b = 255, a = 255})
assert_colors_equal(core.colorspec_to_table(0xFF00FF00), {r = 0, g = 255, b = 0, a = 255})
assert_colors_equal(core.colorspec_to_table("#00000000"), {r = 0, g = 0, b = 0, a = 0})
assert_colors_equal(core.colorspec_to_table("green"), {r = 0, g = 128, b = 0, a = 255})
assert_colors_equal(core.colorspec_to_table("gren"), nil)
end
unittests.register("test_color_conversion", test_color_conversion)

Some files were not shown because too many files have changed in this diff Show more