resolve merge conflict in game.cpp

This commit is contained in:
chmodsayshello 2024-10-13 19:26:26 +02:00
commit c4bf5d0678
615 changed files with 16915 additions and 15658 deletions

2
.gitattributes vendored
View File

@ -3,3 +3,5 @@
*.cpp diff=cpp
*.h diff=cpp
*.gltf binary

View File

@ -88,7 +88,7 @@ jobs:
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-7 llvm
install_linux_deps clang-7 llvm-7
- name: Build
run: |
@ -102,6 +102,11 @@ jobs:
run: |
./bin/minetest --run-unittests
# Do this here because we have ASan and error paths are sensitive to dangling pointers
- name: Test error cases
run: |
./util/test_error_cases.sh
# Current clang version
clang_18:
runs-on: ubuntu-24.04

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,7 +29,7 @@ on:
jobs:
build:
# use macOS 13 since it's the last one that still runs on x86
# use lowest possible macOS running on x86_64 supported by brew to support more users
runs-on: macos-13
steps:
- uses: actions/checkout@v4

45
.github/workflows/whitespace_checks.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: whitespace_checks
# Check whitespaces of the following file types
# Not checked: .lua, .yml, .properties, .conf, .java, .py, .svg, .gradle, .xml, ...
# (luacheck already checks .lua files)
on:
push:
paths:
- '**.txt'
- '**.md'
- '**.[ch]'
- '**.cpp'
- '**.hpp'
- '**.sh'
- '**.cmake'
- '**.glsl'
pull_request:
paths:
- '**.txt'
- '**.md'
- '**.[ch]'
- '**.cpp'
- '**.hpp'
- '**.sh'
- '**.cmake'
- '**.glsl'
jobs:
trailing_whitespaces:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Line endings are already ensured by .gitattributes
- name: Check trailing whitespaces
run: if git ls-files | grep -E '\.txt$|\.md$|\.[ch]$|\.cpp$|\.hpp$|\.sh$|\.cmake$|\.glsl$' | xargs grep -n '\s$'; then echo -e "\033[0;31mFound trailing whitespace"; (exit 1); else (exit 0); fi
tabs_lua_api_files:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Some files should not contain tabs
- name: Check tabs in Lua API files
run: if grep -n $'\t' doc/lua_api.md doc/client_lua_api.md; then echo -e "\033[0;31mFound tab in markdown file"; (exit 1); else (exit 0); fi

3
.gitignore vendored
View File

@ -26,6 +26,7 @@ tags
!tags/
gtags.files
.idea
.qtcreator/
# Codelite
*.project
# Visual Studio Code & plugins
@ -109,6 +110,8 @@ src/cmake_config_githash.h
*.layout
*.o
*.a
*.dump
*.dmp
*.ninja
.ninja*
*.gch

View File

@ -37,6 +37,12 @@ files["builtin/client/register.lua"] = {
}
}
files["builtin/common/math.lua"] = {
globals = {
"math",
},
}
files["builtin/common/misc_helpers.lua"] = {
globals = {
"dump", "dump2", "table", "math", "string",
@ -46,7 +52,7 @@ files["builtin/common/misc_helpers.lua"] = {
}
files["builtin/common/vector.lua"] = {
globals = { "vector" },
globals = { "vector", "math" },
}
files["builtin/game/voxelarea.lua"] = {

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)
@ -283,6 +288,8 @@ if(BUILD_UNITTESTS OR BUILD_BENCHMARKS)
add_subdirectory(lib/catch2)
endif()
add_subdirectory(lib/tiniergltf)
# Subdirectories
# Be sure to add all relevant definitions above this
add_subdirectory(src)
@ -368,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

@ -57,12 +57,10 @@ srifqi:
textures/base/pack/minimap_btn.png
Zughy:
textures/base/pack/cdb_add.png
textures/base/pack/cdb_downloading.png
textures/base/pack/cdb_queued.png
textures/base/pack/cdb_update.png
textures/base/pack/cdb_update_cropped.png
textures/base/pack/cdb_viewonline.png
textures/base/pack/settings_btn.png
textures/base/pack/settings_info.png
textures/base/pack/settings_reset.png
@ -79,7 +77,6 @@ kilbith:
textures/base/pack/progress_bar_bg.png
SmallJoker:
textures/base/pack/cdb_clear.png
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
DS:

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

@ -8,7 +8,7 @@ android {
compileSdk 34
targetSdkVersion 34
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
versionCode project.versionCode
versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
}
buildFeatures {
@ -116,18 +116,6 @@ clean {
delete new File("src/main/assets", "Minetest.zip")
}
// Map for the version code that gives each ABI a value.
import com.android.build.OutputFile
def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1]
android.applicationVariants.all { variant ->
variant.outputs.each {
output ->
def abiName = output.getFilter(OutputFile.ABI)
output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode
}
}
dependencies {
implementation project(':native')
implementation 'androidx.appcompat:appcompat:1.6.1'

View File

@ -1,13 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
project.ext.set("versionMajor", 5) // Version Major
project.ext.set("versionMinor", 10) // Version Minor
project.ext.set("versionMinor", 10) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch
// ^ keep in sync with cmake
project.ext.set("versionCode", 48) // Android Version Code
// NOTE: +2 after each release!
// +1 for ARM and +1 for ARM64 APK's, because
// each APK must have a larger `versionCode` than the previous
project.ext.set("versionBuild", 0) // Version Build
// ^ fourth version number to allow releasing Android-only fixes and beta versions
buildscript {
ext.ndk_version = '26.2.11394342'

View File

@ -8,7 +8,7 @@ android {
compileSdk 34
targetSdkVersion 34
externalNativeBuild {
cmake {
cmake {
arguments "-DANDROID_STL=c++_shared",
"-DENABLE_CURL=1", "-DENABLE_SOUND=1",
"-DENABLE_GETTEXT=1",

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

@ -69,7 +69,7 @@ local function build_chatcommands_formspec(name, sel, copy)
description = cmds[2].description
if copy then
local msg = S("Command: @1 @2",
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)
core.colorize("#0FF", (INIT == "client" and "." or "/") .. cmds[1]), cmds[2].params)
if INIT == "client" then
core.display_chat_message(msg)
else

View File

@ -166,20 +166,19 @@ function core.is_colored_paramtype(ptype)
end
function core.strip_param2_color(param2, paramtype2)
if not core.is_colored_paramtype(paramtype2) then
if paramtype2 == "color" then
return param2
elseif paramtype2 == "colorfacedir" then
return math.floor(param2 / 32) * 32
elseif paramtype2 == "color4dir" then
return math.floor(param2 / 4) * 4
elseif paramtype2 == "colorwallmounted" then
return math.floor(param2 / 8) * 8
elseif paramtype2 == "colordegrotate" then
return math.floor(param2 / 32) * 32
else
return nil
end
if paramtype2 == "colorfacedir" then
param2 = math.floor(param2 / 32) * 32
elseif paramtype2 == "color4dir" then
param2 = math.floor(param2 / 4) * 4
elseif paramtype2 == "colorwallmounted" then
param2 = math.floor(param2 / 8) * 8
elseif paramtype2 == "colordegrotate" then
param2 = math.floor(param2 / 32) * 32
end
-- paramtype2 == "color" requires no modification.
return param2
end
-- Content ID caching

41
builtin/common/math.lua Normal file
View File

@ -0,0 +1,41 @@
--[[
Math utils.
--]]
function math.hypot(x, y)
return math.sqrt(x * x + y * y)
end
function math.sign(x, tolerance)
tolerance = tolerance or 0
if x > tolerance then
return 1
elseif x < -tolerance then
return -1
end
return 0
end
function math.factorial(x)
assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer")
if x >= 171 then
-- 171! is greater than the biggest double, no need to calculate
return math.huge
end
local v = 1
for k = 2, x do
v = v * k
end
return v
end
function math.round(x)
if x < 0 then
local int = math.ceil(x)
local frac = x - int
return int - ((frac <= -0.5) and 1 or 0)
end
local int = math.floor(x)
local frac = x - int
return int + ((frac >= 0.5) and 1 or 0)
end

View File

@ -3,6 +3,7 @@
--------------------------------------------------------------------------------
-- Localize functions to avoid table lookups (better performance).
local string_sub, string_find = string.sub, string.find
local math = math
--------------------------------------------------------------------------------
local function basic_dump(o)
@ -220,47 +221,6 @@ function string:trim()
return self:match("^%s*(.-)%s*$")
end
--------------------------------------------------------------------------------
function math.hypot(x, y)
return math.sqrt(x * x + y * y)
end
--------------------------------------------------------------------------------
function math.sign(x, tolerance)
tolerance = tolerance or 0
if x > tolerance then
return 1
elseif x < -tolerance then
return -1
end
return 0
end
--------------------------------------------------------------------------------
function math.factorial(x)
assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer")
if x >= 171 then
-- 171! is greater than the biggest double, no need to calculate
return math.huge
end
local v = 1
for k = 2, x do
v = v * k
end
return v
end
function math.round(x)
if x < 0 then
local int = math.ceil(x)
local frac = x - int
return int - ((frac <= -0.5) and 1 or 0)
end
local int = math.floor(x)
local frac = x - int
return int + ((frac >= 0.5) and 1 or 0)
end
local formspec_escapes = {
["\\"] = "\\\\",
["["] = "\\[",
@ -275,6 +235,16 @@ function core.formspec_escape(text)
end
local hypertext_escapes = {
["\\"] = "\\\\",
["<"] = "\\<",
[">"] = "\\>",
}
function core.hypertext_escape(text)
return text and text:gsub("[\\<>]", hypertext_escapes)
end
function core.wrap_text(text, max_length, as_table)
local result = {}
local line = {}
@ -604,12 +574,14 @@ function core.strip_colors(str)
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
end
function core.translate(textdomain, str, ...)
local function translate(textdomain, str, num, ...)
local start_seq
if textdomain == "" then
if textdomain == "" and num == "" then
start_seq = ESCAPE_CHAR .. "T"
else
elseif num == "" then
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
else
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. "@" .. num .. ")"
end
local arg = {n=select('#', ...), ...}
local end_seq = ESCAPE_CHAR .. "E"
@ -640,8 +612,31 @@ function core.translate(textdomain, str, ...)
return start_seq .. translated .. end_seq
end
function core.translate(textdomain, str, ...)
return translate(textdomain, str, "", ...)
end
function core.translate_n(textdomain, str, str_plural, n, ...)
assert (type(n) == "number")
assert (n >= 0)
assert (math.floor(n) == n)
-- Truncate n if too large
local max = 1000000
if n >= 2 * max then
n = n % max + max
end
if n == 1 then
return translate(textdomain, str, "1", ...)
else
return translate(textdomain, str_plural, tostring(n), ...)
end
end
function core.get_translator(textdomain)
return function(str, ...) return core.translate(textdomain or "", str, ...) end
return
(function(str, ...) return core.translate(textdomain or "", str, ...) end),
(function(str, str_plural, n, ...) return core.translate_n(textdomain or "", str, str_plural, n, ...) end)
end
--------------------------------------------------------------------------------
@ -702,6 +697,7 @@ function core.privs_to_string(privs, delim)
list[#list + 1] = priv
end
end
table.sort(list)
return table.concat(list, delim)
end

View File

@ -1,6 +1,7 @@
_G.core = {}
_G.vector = {metatable = {}}
dofile("builtin/common/math.lua")
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")

View File

@ -0,0 +1,16 @@
_G.core = {}
dofile("builtin/common/math.lua")
describe("math", function()
it("round()", function()
assert.equal(0, math.round(0))
assert.equal(10, math.round(10.3))
assert.equal(11, math.round(10.5))
assert.equal(11, math.round(10.7))
assert.equal(-10, math.round(-10.3))
assert.equal(-11, math.round(-10.5))
assert.equal(-11, math.round(-10.7))
assert.equal(0, math.round(0.49999999999999994))
assert.equal(0, math.round(-0.49999999999999994))
end)
end)

View File

@ -1,4 +1,5 @@
_G.core = {}
dofile("builtin/common/math.lua")
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")

View File

@ -1,4 +1,5 @@
_G.vector = {}
dofile("builtin/common/math.lua")
dofile("builtin/common/vector.lua")
describe("vector", function()
@ -113,12 +114,35 @@ describe("vector", function()
assert.equal(vector.new(0, 1, -1), a:round())
end)
it("ceil()", function()
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(1, 1, 0), vector.ceil(a))
assert.equal(vector.new(1, 1, 0), a:ceil())
end)
it("sign()", function()
local a = vector.new(-120.3, 0, 231.5)
assert.equal(vector.new(-1, 0, 1), vector.sign(a))
assert.equal(vector.new(-1, 0, 1), a:sign())
assert.equal(vector.new(0, 0, 1), vector.sign(a, 200))
assert.equal(vector.new(0, 0, 1), a:sign(200))
end)
it("abs()", function()
local a = vector.new(-123.456, 0, 13)
assert.equal(vector.new(123.456, 0, 13), vector.abs(a))
assert.equal(vector.new(123.456, 0, 13), a:abs())
end)
it("apply()", function()
local i = 0
local f = function(x)
i = i + 1
return x + i
end
local f2 = function(x, opt1, opt2, opt3)
return x + opt1 + opt2 + opt3
end
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil))
assert.equal(vector.new(1, 1, 0), a:apply(math.ceil))
@ -126,6 +150,9 @@ describe("vector", function()
assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs))
assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f))
assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
local b = vector.new(1, 2, 3)
assert.equal(vector.new(4, 5, 6), vector.apply(b, f2, 1, 1, 1))
assert.equal(vector.new(4, 5, 6), b:apply(f2, 1, 1, 1))
end)
it("combine()", function()
@ -469,4 +496,13 @@ describe("vector", function()
assert.True(vector.in_area(vector.new(-10, -10, -10), vector.new(-10, -10, -10), vector.new(10, 10, 10)))
assert.False(vector.in_area(vector.new(-10, -10, -10), vector.new(10, 10, 10), vector.new(-11, -10, -10)))
end)
it("random_in_area()", function()
local min = vector.new(-100, -100, -100)
local max = vector.new(100, 100, 100)
for i = 1, 1000 do
local random = vector.random_in_area(min, max)
assert.True(vector.in_area(random, min, max))
end
end)
end)

View File

@ -5,6 +5,7 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta
-- localize functions
local setmetatable = setmetatable
local math = math
vector = {}
@ -97,18 +98,26 @@ function vector.floor(v)
end
function vector.round(v)
return fast_new(
math.round(v.x),
math.round(v.y),
math.round(v.z)
)
return vector.apply(v, math.round)
end
function vector.apply(v, func)
function vector.ceil(v)
return vector.apply(v, math.ceil)
end
function vector.sign(v, tolerance)
return vector.apply(v, math.sign, tolerance)
end
function vector.abs(v)
return vector.apply(v, math.abs)
end
function vector.apply(v, func, ...)
return fast_new(
func(v.x),
func(v.y),
func(v.z)
func(v.x, ...),
func(v.y, ...),
func(v.z, ...)
)
end
@ -387,6 +396,14 @@ function vector.random_direction()
return fast_new(x/l, y/l, z/l)
end
function vector.random_in_area(min, max)
return fast_new(
math.random(min.x, max.x),
math.random(min.y, max.y),
math.random(min.z, max.z)
)
end
if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then
local function read_vector(v)
return v.x, v.y, v.z

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

@ -19,7 +19,7 @@
local BASE_SPACING = 0.1
local function get_scroll_btn_width()
return core.settings:get_bool("enable_touch") and 0.8 or 0.5
return core.settings:get_bool("touch_gui") and 0.8 or 0.5
end
local function buttonbar_formspec(self)
@ -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 TOUCH_GUI = core.settings:get_bool("touch_gui")
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
+ (TOUCH_GUI 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

@ -221,6 +221,7 @@ core.register_chatcommand("haspriv", {
return true, S("No online player has the \"@1\" privilege.",
param)
else
table.sort(players_with_priv)
return true, S("Players online with the \"@1\" privilege: @2",
param,
table.concat(players_with_priv, ", "))

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

@ -42,6 +42,9 @@ core.features = {
node_interaction_actor = true,
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

@ -251,11 +251,31 @@ register_builtin_hud_element("minimap", {
position = {x = 1, y = 0},
alignment = {x = -1, y = 1},
offset = {x = -10, y = 10},
size = {x = 256, y = 256},
size = {x = 0, y = -25},
},
show_elem = function(player, flags)
local proto_ver = core.get_player_information(player:get_player_name()).protocol_version
-- Don't add a minimap for clients which already have it hardcoded in C++.
return flags.minimap and
core.get_player_information(player:get_player_name()).protocol_version >= 44
return flags.minimap and proto_ver >= 44
end,
update_def = function(player, elem_def)
local proto_ver = core.get_player_information(player:get_player_name()).protocol_version
-- Only use percentage when the client supports it.
elem_def.size = proto_ver >= 45 and {x = 0, y = -25} or {x = 256, y = 256}
end,
})
--- Hotbar
register_builtin_hud_element("hotbar", {
elem_def = {
type = "hotbar",
position = {x = 0.5, y = 1},
direction = 0,
alignment = {x = 0, y = -1},
offset = {x = 0, y = -4}, -- Extra padding below.
},
show_elem = function(player, flags)
return flags.hotbar
end,
})

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

@ -42,6 +42,7 @@ local scriptdir = core.get_builtin_path()
local commonpath = scriptdir .. "common" .. DIR_DELIM
local asyncpath = scriptdir .. "async" .. DIR_DELIM
dofile(commonpath .. "math.lua")
dofile(commonpath .. "vector.lua")
dofile(commonpath .. "strict.lua")
dofile(commonpath .. "serialize.lua")

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

@ -182,6 +182,23 @@ function contentdb.get_package_by_id(id)
end
function contentdb.calculate_package_id(type, author, name)
local id = author:lower() .. "/"
if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
id = id .. name:sub(1, #name - 5)
else
id = id .. name
end
return id
end
function contentdb.get_package_by_info(author, name)
local id = contentdb.calculate_package_id(nil, author, name)
return contentdb.package_by_id[id]
end
-- Create a coroutine from `fn` and provide results to `callback` when complete (dead).
-- Returns a resumer function.
local function make_callback_coroutine(fn, callback)
@ -415,15 +432,7 @@ local function fetch_pkgs(params)
local aliases = {}
for _, package in pairs(packages) do
local name_len = #package.name
-- This must match what contentdb.update_paths() does!
package.id = package.author:lower() .. "/"
if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
package.id = package.id .. package.name:sub(1, name_len - 5)
else
package.id = package.id .. package.name
end
package.id = params.calculate_package_id(package.type, package.author, package.name)
package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name)
if package.aliases then
@ -443,7 +452,7 @@ end
function contentdb.fetch_pkgs(callback)
contentdb.loading = true
core.handle_async(fetch_pkgs, nil, function(result)
core.handle_async(fetch_pkgs, { calculate_package_id = contentdb.calculate_package_id }, function(result)
if result then
contentdb.load_ok = true
contentdb.load_error = false
@ -581,3 +590,78 @@ function contentdb.filter_packages(query, by_type)
end
end
end
function contentdb.get_full_package_info(package, callback)
assert(package)
if package.full_info then
callback(package.full_info)
return
end
local function fetch(params)
local version = core.get_version()
local base_url = core.settings:get("contentdb_url")
local languages
local current_language = core.get_language()
if current_language ~= "" then
languages = { current_language, "en;q=0.8" }
else
languages = { "en" }
end
local url = base_url ..
"/api/packages/" .. params.package.url_part .. "/for-client/?" ..
"protocol_version=" .. core.urlencode(core.get_max_supp_proto()) ..
"&engine_version=" .. core.urlencode(version.string) ..
"&formspec_version=" .. core.urlencode(core.get_formspec_version()) ..
"&include_images=false"
local http = core.get_http_api()
local response = http.fetch_sync({
url = url,
extra_headers = {
"Accept-Language: " .. table.concat(languages, ", ")
},
})
if not response.succeeded then
return nil
end
return core.parse_json(response.data)
end
local function my_callback(value)
package.full_info = value
callback(value)
end
if not core.handle_async(fetch, { package = package }, my_callback) then
core.log("error", "ERROR: async event failed")
callback(nil)
end
end
function contentdb.get_formspec_padding()
-- Padding is increased on Android to account for notches
-- TODO: use Android API to determine size of cut outs
return { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 }
end
function contentdb.get_formspec_size()
local window = core.get_window_info()
local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y }
-- Minimum formspec size
local min_x = 15.5
local min_y = 10
if size.x < min_x or size.y < min_y then
local scale = math.max(min_x / size.x, min_y / size.y)
size.x = size.x * scale
size.y = size.y * scale
end
return size
end

View File

@ -26,68 +26,20 @@ end
-- Filter
local search_string = ""
local cur_page = 1
local num_per_page = 5
local filter_type = 1
local filter_types_titles = {
fgettext("All packages"),
fgettext("Games"),
fgettext("Mods"),
fgettext("Texture packs"),
}
local filter_type
-- Automatic package installation
local auto_install_spec = nil
local filter_types_type = {
nil,
"game",
"mod",
"txp",
local filter_type_names = {
{ "type_all", nil },
{ "type_game", "game" },
{ "type_mod", "mod" },
{ "type_txp", "txp" },
}
local function install_or_update_package(this, package)
local install_parent
if package.type == "mod" then
install_parent = core.get_modpath()
elseif package.type == "game" then
install_parent = core.get_gamepath()
elseif package.type == "txp" then
install_parent = core.get_texturepath()
else
error("Unknown package type: " .. package.type)
end
if package.queued or package.downloading then
return
end
local function on_confirm()
local dlg = create_install_dialog(package)
dlg:set_parent(this)
this:hide()
dlg:show()
dlg:load_deps()
end
if package.type == "mod" and #pkgmgr.games == 0 then
local dlg = messagebox("install_game",
fgettext("You need to install a game before you can install a mod"))
dlg:set_parent(this)
this:hide()
dlg:show()
elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
local dlg = create_confirm_overwrite(package, on_confirm)
dlg:set_parent(this)
this:hide()
dlg:show()
else
on_confirm()
end
end
-- Resolves the package specification stored in auto_install_spec into an actual package.
-- May only be called after the package list has been loaded successfully.
local function resolve_auto_install_spec()
@ -145,7 +97,7 @@ end
local function sort_and_filter_pkgs()
contentdb.update_paths()
contentdb.sort_packages()
contentdb.filter_packages(search_string, filter_types_type[filter_type])
contentdb.filter_packages(search_string, filter_type)
local auto_install_pkg = resolve_auto_install_spec()
if auto_install_pkg then
@ -176,72 +128,151 @@ local function load()
end
local function get_info_formspec(text)
local H = 9.5
local function get_info_formspec(size, padding, text)
return table.concat({
"formspec_version[6]",
"size[15.75,9.5]",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
"size[", size.x, ",", size.y, "]",
"padding[0,0]",
"bgcolor[;true]",
"label[4,4.35;", text, "]",
"container[0,", H - 0.8 - 0.375, "]",
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
"label[", padding.x + 3.625, ",4.35;", text, "]",
"container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
"button[0,0;2,0.8;back;", fgettext("Back"), "]",
"container_end[]",
})
end
-- Determines how to fit `num_per_page` into `size` space
local function fit_cells(num_per_page, size)
local cell_spacing = 0.5
local columns = 1
local cell_w, cell_h
-- Fit cells into the available height
while true do
cell_w = (size.x - (columns-1)*cell_spacing) / columns
cell_h = cell_w / 4
local required_height = math.ceil(num_per_page / columns) * (cell_h + cell_spacing) - cell_spacing
-- Add 0.1 to be more lenient
if required_height <= size.y + 0.1 then
break
end
columns = columns + 1
end
return cell_spacing, columns, cell_w, cell_h
end
local function calculate_num_per_page()
local size = contentdb.get_formspec_size()
local padding = contentdb.get_formspec_padding()
local window = core.get_window_info()
size.x = size.x - padding.x * 2
size.y = size.y - padding.y * 2 - 1.425 - 0.25 - 0.8
local coordToPx = window.size.x / window.max_formspec_size.x / window.real_gui_scaling
local num_per_page = 12
while num_per_page > 2 do
local _, _, cell_w, _ = fit_cells(num_per_page, size)
if cell_w * coordToPx > 350 then
break
end
num_per_page = num_per_page - 1
end
return num_per_page
end
local function get_formspec(dlgdata)
local window_padding = contentdb.get_formspec_padding()
local size = contentdb.get_formspec_size()
if contentdb.loading then
return get_info_formspec(fgettext("Loading..."))
return get_info_formspec(size, window_padding, fgettext("Loading..."))
end
if contentdb.load_error then
return get_info_formspec(fgettext("No packages could be retrieved"))
return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
end
assert(contentdb.load_ok)
contentdb.update_paths()
local num_per_page = dlgdata.num_per_page
dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1)
if cur_page > dlgdata.pagemax then
cur_page = 1
end
local W = 15.75
local H = 9.5
local W = size.x - window_padding.x * 2
local H = size.y - window_padding.y * 2
local category_x = 0
local number_category_buttons = 4
local max_button_w = (W - 0.375 - 0.25 - 7) / number_category_buttons
local category_button_w = math.min(max_button_w, 3)
local function make_category_button(name, label, selected)
category_x = category_x + 1
local color = selected and mt_color_green or ""
return ("style[%s;bgcolor=%s]button[%f,0;%f,0.8;%s;%s]"):format(name, color,
(category_x - 1) * category_button_w, category_button_w, name, label)
end
local selected_type = filter_type
local search_box_width = W - 0.375 - 0.25 - 2*0.8
- number_category_buttons * category_button_w
local formspec = {
"formspec_version[6]",
"size[15.75,9.5]",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
"formspec_version[7]",
"size[", size.x, ",", size.y, "]",
"padding[0,0]",
"bgcolor[;true]",
"style[status,downloading,queued;border=false]",
"container[", window_padding.x, ",", window_padding.y, "]",
"container[0.375,0.375]",
"field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
-- Top-left: categories
make_category_button("type_all", fgettext("All"), selected_type == nil),
make_category_button("type_game", fgettext("Games"), selected_type == "game"),
make_category_button("type_mod", fgettext("Mods"), selected_type == "mod"),
make_category_button("type_txp", fgettext("Texture Packs"), selected_type == "txp"),
-- Top-right: Search
"container[", W - search_box_width - 0.8*2, ",0]",
"field[0,0;", search_box_width, ",0.8;search_string;;", core.formspec_escape(search_string), "]",
"field_enter_after_edit[search_string;true]",
"image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
"image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
"dropdown[9.175,0;2.7875,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
"image_button[", search_box_width, ",0;0.8,0.8;",
core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
"image_button[", search_box_width + 0.8, ",0;0.8,0.8;",
core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
"container_end[]",
-- Page nav buttons
"container[0,", H - 0.8 - 0.375, "]",
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
-- Bottom strip start
"container[0,", H - 0.8, "]",
"button[0,0;2,0.8;back;", fgettext("Back"), "]",
"container[", W - 0.375 - 0.8*4 - 2, ",0]",
"image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
"image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
-- Bottom-center: Page nav buttons
"container[", (W - 1*4 - 2) / 2, ",0]",
"image_button[0,0;1,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
"image_button[1,0;1,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
"style[pagenum;border=false]",
"button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
"image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
"image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
"container_end[]",
"button[2,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
"image_button[4,0;1,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
"image_button[5,0;1,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
"container_end[]", -- page nav end
"container_end[]",
-- Bottom-right: updating
"container[", W - 3, ",0]",
"style[status,downloading,queued;border=false]",
}
if contentdb.number_downloading > 0 then
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;downloading;"
formspec[#formspec + 1] = "button[0,0;3,0.8;downloading;"
if #contentdb.download_queue > 0 then
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued",
contentdb.number_downloading, #contentdb.download_queue)
@ -260,16 +291,19 @@ local function get_formspec(dlgdata)
end
if num_avail_updates == 0 then
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;status;"
formspec[#formspec + 1] = "button[0,0;3,0.8;status;"
formspec[#formspec + 1] = fgettext("No updates")
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;update_all;"
formspec[#formspec + 1] = "button[0,0;3,0.8;update_all;"
formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
formspec[#formspec + 1] = "]"
end
end
formspec[#formspec + 1] = "container_end[]" -- updating end
formspec[#formspec + 1] = "container_end[]" -- bottom strip end
if #contentdb.packages == 0 then
formspec[#formspec + 1] = "label[4,4.75;"
formspec[#formspec + 1] = fgettext("No results")
@ -281,81 +315,85 @@ local function get_formspec(dlgdata)
formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors
formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors
formspec[#formspec + 1] = "container[0,1.425]"
local cell_spacing, columns, cell_w, cell_h = fit_cells(num_per_page, {
x = W,
y = H - 1.425 - 0.25 - 0.8
})
local img_w = cell_h * 3 / 2
local start_idx = (cur_page - 1) * num_per_page + 1
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i]
local container_y = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
formspec[#formspec + 1] = "container[0.375,"
formspec[#formspec + 1] = container_y
formspec[#formspec + 1] = "]"
-- image
formspec[#formspec + 1] = "image[0,0;1.5,1;"
formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
formspec[#formspec + 1] = "]"
table.insert_all(formspec, {
"container[",
(cell_w + cell_spacing) * ((i - start_idx) % columns),
",",
(cell_h + cell_spacing) * math.floor((i - start_idx) / columns),
"]",
-- title
formspec[#formspec + 1] = "label[1.875,0.1;"
formspec[#formspec + 1] = core.formspec_escape(
core.colorize(mt_color_green, package.title) ..
core.colorize("#BFBFBF", " by " .. package.author))
formspec[#formspec + 1] = "]"
"box[0,0;", cell_w, ",", cell_h, ";#ffffff11]",
-- buttons
local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
-- image,
"image[0,0;", img_w, ",", cell_h, ";",
core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]",
local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "container["
formspec[#formspec + 1] = W - 0.375*2
formspec[#formspec + 1] = ",0.1]"
"label[", img_w + 0.25 + 0.05, ",0.5;",
core.formspec_escape(
core.colorize(mt_color_green, package.title) ..
core.colorize("#BFBFBF", " by " .. package.author)), "]",
if package.downloading then
formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;"
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif package.queued then
formspec[#formspec + 1] = second_base
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
elseif not package.path then
local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
formspec[#formspec + 1] = second_base .. "cdb_add.png;" .. elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors
else
if package.installed_release < package.release then
-- The install_ action also handles updating
local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
"textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;",
core.formspec_escape(package.short_description), "]",
description_width = description_width - 0.7 - 0.15
end
"style[view_", i, ";border=false]",
"style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]",
"style[view_", i, ":pressed;bgimg=", core.formspec_escape(defaulttexturedir .. "button_press_semitrans.png"), "]",
"button[0,0;", cell_w, ",", cell_h, ";view_", i, ";]",
})
local elem_name = "uninstall_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
if package.featured then
table.insert_all(formspec, {
"tooltip[0,0;0.8,0.8;", fgettext("Featured"), "]",
"image[0.2,0.2;0.4,0.4;", defaulttexturedir, "server_favorite.png]",
})
end
local web_elem_name = "view_" .. i .. ";"
formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" ..
core.formspec_escape(defaulttexturedir) .. "cdb_viewonline.png;" .. web_elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. web_elem_name ..
fgettext("View more information in a web browser") .. tooltip_colors
formspec[#formspec + 1] = "container_end[]"
table.insert_all(formspec, {
"container[", cell_w - 0.625,",", 0.25, "]",
})
-- description
formspec[#formspec + 1] = "textarea[1.855,0.3;"
formspec[#formspec + 1] = tostring(description_width)
formspec[#formspec + 1] = ",0.8;;;"
formspec[#formspec + 1] = core.formspec_escape(package.short_description)
formspec[#formspec + 1] = "]"
if package.downloading then
table.insert_all(formspec, {
"animated_image[0,0;0.5,0.5;downloading;", defaulttexturedir, "cdb_downloading.png;3;400;;]",
})
elseif package.queued then
table.insert_all(formspec, {
"image[0,0;0.5,0.5;", defaulttexturedir, "cdb_queued.png]",
})
elseif package.path then
if package.installed_release < package.release then
table.insert_all(formspec, {
"image[0,0;0.5,0.5;", defaulttexturedir, "cdb_update.png]",
})
else
table.insert_all(formspec, {
"image[0.1,0.1;0.3,0.3;", defaulttexturedir, "checkbox_64.png]",
})
end
end
formspec[#formspec + 1] = "container_end[]"
table.insert_all(formspec, {
"container_end[]",
"container_end[]",
})
end
formspec[#formspec + 1] = "container_end[]"
formspec[#formspec + 1] = "container_end[]"
return table.concat(formspec)
end
@ -364,14 +402,14 @@ local function handle_submit(this, fields)
if fields.search or fields.key_enter_field == "search_string" then
search_string = fields.search_string:trim()
cur_page = 1
contentdb.filter_packages(search_string, filter_types_type[filter_type])
contentdb.filter_packages(search_string, filter_type)
return true
end
if fields.clear then
search_string = ""
cur_page = 1
contentdb.filter_packages("", filter_types_type[filter_type])
contentdb.filter_packages("", filter_type)
return true
end
@ -407,12 +445,11 @@ local function handle_submit(this, fields)
return true
end
if fields.type then
local new_type = table.indexof(filter_types_titles, fields.type)
if new_type ~= filter_type then
filter_type = new_type
for _, pair in ipairs(filter_type_names) do
if fields[pair[1]] then
filter_type = pair[2]
cur_page = 1
contentdb.filter_packages(search_string, filter_types_type[filter_type])
contentdb.filter_packages(search_string, filter_type)
return true
end
end
@ -428,32 +465,20 @@ local function handle_submit(this, fields)
return true
end
local num_per_page = this.data.num_per_page
local start_idx = (cur_page - 1) * num_per_page + 1
assert(start_idx ~= nil)
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i]
assert(package)
if fields["install_" .. i] then
install_or_update_package(this, package)
return true
end
if fields["uninstall_" .. i] then
local dlg = create_delete_content_dlg(package)
if fields["view_" .. i] or fields["title_" .. i] or fields["author_" .. i] then
local dlg = create_package_dialog(package)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
if fields["view_" .. i] then
local url = ("%s/packages/%s?protocol_version=%d"):format(
core.settings:get("contentdb_url"), package.url_part,
core.get_max_supp_proto())
core.open_url(url)
return true
end
end
return false
@ -462,8 +487,8 @@ end
local function handle_events(event)
if event == "DialogShow" then
-- On touchscreen, don't show the "MINETEST" header behind the dialog.
mm_game_theme.set_engine(core.settings:get_bool("enable_touch"))
-- Don't show the "MINETEST" header behind the dialog.
mm_game_theme.set_engine(true)
-- If ContentDB is already loaded, auto-install packages here.
do_auto_install()
@ -471,6 +496,11 @@ local function handle_events(event)
return true
end
if event == "WindowInfoChange" then
ui.update()
return true
end
return false
end
@ -485,17 +515,7 @@ end
function create_contentdb_dlg(type, install_spec)
search_string = ""
cur_page = 1
if type then
-- table.indexof does not work on tables that contain `nil`
for i, v in pairs(filter_types_type) do
if v == type then
filter_type = i
break
end
end
else
filter_type = 1
end
filter_type = type
-- Keep the old auto_install_spec if the caller doesn't specify one.
if install_spec then
@ -504,8 +524,10 @@ function create_contentdb_dlg(type, install_spec)
load()
return dialog_create("contentdb",
local dlg = dialog_create("contentdb",
get_formspec,
handle_submit,
handle_events)
dlg.data.num_per_page = calculate_num_per_page()
return dlg
end

View File

@ -22,13 +22,13 @@ end
local function get_loading_formspec()
local ENABLE_TOUCH = core.settings:get_bool("enable_touch")
local w = ENABLE_TOUCH and 14 or 7
local TOUCH_GUI = core.settings:get_bool("touch_gui")
local w = TOUCH_GUI and 14 or 7
local formspec = {
"formspec_version[3]",
"size[", w, ",9.05]",
ENABLE_TOUCH and "padding[0.01,0.01]" or "position[0.5,0.55]",
TOUCH_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
"label[3,4.525;", fgettext("Loading..."), "]",
}
return table.concat(formspec)
@ -110,18 +110,18 @@ local function get_formspec(data)
message_bg = mt_color_orange
end
local ENABLE_TOUCH = core.settings:get_bool("enable_touch")
local TOUCH_GUI = core.settings:get_bool("touch_gui")
local w = ENABLE_TOUCH and 14 or 7
local w = TOUCH_GUI and 14 or 7
local padded_w = w - 2*0.375
local dropdown_w = ENABLE_TOUCH and 10.2 or 4.25
local dropdown_w = TOUCH_GUI and 10.2 or 4.25
local button_w = (padded_w - 0.25) / 3
local button_pad = button_w / 2
local formspec = {
"formspec_version[3]",
"size[", w, ",9.05]",
ENABLE_TOUCH and "padding[0.01,0.01]" or "position[0.5,0.55]",
TOUCH_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
"style[title;border=false]",
"box[0,0;", w, ",0.8;#3333]",
"button[0,0;", w, ",0.8;title;", fgettext("Install $1", package.title) , "]",
@ -244,3 +244,45 @@ function create_install_dialog(package)
return dlg
end
function install_or_update_package(parent, package)
local install_parent
if package.type == "mod" then
install_parent = core.get_modpath()
elseif package.type == "game" then
install_parent = core.get_gamepath()
elseif package.type == "txp" then
install_parent = core.get_texturepath()
else
error("Unknown package type: " .. package.type)
end
if package.queued or package.downloading then
return
end
local function on_confirm()
local dlg = create_install_dialog(package)
dlg:set_parent(parent)
parent:hide()
dlg:show()
dlg:load_deps()
end
if package.type == "mod" and #pkgmgr.games == 0 then
local dlg = messagebox("install_game",
fgettext("You need to install a game before you can install a mod"))
dlg:set_parent(parent)
parent:hide()
dlg:show()
elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
local dlg = create_confirm_overwrite(package, on_confirm)
dlg:set_parent(parent)
parent:hide()
dlg:show()
else
on_confirm()
end
end

View File

@ -0,0 +1,333 @@
--Minetest
--Copyright (C) 2018-24 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function get_info_formspec(size, padding, text)
return table.concat({
"formspec_version[6]",
"size[", size.x, ",", size.y, "]",
"padding[0,0]",
"bgcolor[;true]",
"label[4,4.35;", text, "]",
"container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
"button[0,0;2,0.8;back;", fgettext("Back"), "]",
"container_end[]",
})
end
local function get_formspec(data)
local window_padding = contentdb.get_formspec_padding()
local size = contentdb.get_formspec_size()
size.x = math.min(size.x, 20)
local W = size.x - window_padding.x * 2
local H = size.y - window_padding.y * 2
if not data.info then
if not data.loading and not data.loading_error then
data.loading = true
contentdb.get_full_package_info(data.package, function(info)
data.loading = false
if info == nil then
data.loading_error = true
ui.update()
return
end
if info.forums then
info.forums = "https://forum.minetest.net/viewtopic.php?t=" .. info.forums
end
assert(data.package.name == info.name)
data.info = info
ui.update()
end)
end
-- get_full_package_info can return cached info immediately, so
-- check to see if that happened
if not data.info then
if data.loading_error then
return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
end
return get_info_formspec(size, window_padding, fgettext("Loading..."))
end
end
-- Check installation status
contentdb.update_paths()
local info = data.info
local info_line =
fgettext("by $1 — $2 downloads — +$3 / $4 / -$5",
info.author, info.downloads,
info.reviews.positive, info.reviews.neutral, info.reviews.negative)
local bottom_buttons_y = H - 0.8
local formspec = {
"formspec_version[7]",
"size[", size.x, ",", size.y, "]",
"padding[0,0]",
"bgcolor[;true]",
"container[", window_padding.x, ",", window_padding.y, "]",
"button[0,", bottom_buttons_y, ";2,0.8;back;", fgettext("Back"), "]",
"button[", W - 3, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]",
"style_type[label;font_size=+24;font=bold]",
"label[0,0.4;", core.formspec_escape(info.title), "]",
"style_type[label;font_size=;font=]",
"label[0,1.2;", core.formspec_escape(info_line), "]",
}
table.insert_all(formspec, {
"container[", W - 6, ",0]"
})
local left_button_rect = "0,0;2.875,1"
local right_button_rect = "3.125,0;2.875,1"
if data.package.downloading then
formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;"
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif data.package.queued then
formspec[#formspec + 1] = "style[queued;border=false]"
formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
elseif not data.package.path then
formspec[#formspec + 1] = "style[install;bgcolor=green]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = right_button_rect
formspec[#formspec + 1] =";install;"
formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size)
formspec[#formspec + 1] = "]"
else
if data.package.installed_release < data.package.release then
-- The install_ action also handles updating
formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = left_button_rect
formspec[#formspec + 1] = ";install;"
formspec[#formspec + 1] = fgettext("Update")
formspec[#formspec + 1] = "]"
end
formspec[#formspec + 1] = "style[uninstall;bgcolor=#a93b3b]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = right_button_rect
formspec[#formspec + 1] = ";uninstall;"
formspec[#formspec + 1] = fgettext("Uninstall")
formspec[#formspec + 1] = "]"
end
local current_tab = data.current_tab or 1
local tab_titles = {
fgettext("Description"),
fgettext("Information"),
}
local tab_body_height = bottom_buttons_y - 2.8
table.insert_all(formspec, {
"container_end[]",
"box[0,2.55;", W, ",", tab_body_height, ";#ffffff11]",
"tabheader[0,2.55;", W, ",0.8;tabs;",
table.concat(tab_titles, ","), ";", current_tab, ";true;true]",
"container[0,2.8]",
})
if current_tab == 1 then
-- Screenshots and description
local hypertext = "<big><b>" .. core.hypertext_escape(info.short_description) .. "</b></big>\n"
local winfo = core.get_window_info()
local fs_to_px = winfo.size.x / winfo.max_formspec_size.x
for i, ss in ipairs(info.screenshots) do
local path = get_screenshot(data.package, ss.url, 2)
hypertext = hypertext .. "<action name=\"ss_" .. i .. "\"><img name=\"" ..
core.hypertext_escape(path) .. "\" width=" .. (3 * fs_to_px) ..
" height=" .. (2 * fs_to_px) .. "></action>"
if i ~= #info.screenshots then
hypertext = hypertext .. "<img name=\"blank.png\" width=" .. (0.25 * fs_to_px) ..
" height=" .. (2.25 * fs_to_px).. ">"
end
end
hypertext = hypertext .. "\n" .. info.long_description.head
local first = true
local function add_link_button(label, name)
if info[name] then
if not first then
hypertext = hypertext .. " | "
end
hypertext = hypertext .. "<action name=link_" .. name .. ">" .. core.hypertext_escape(label) .. "</action>"
info.long_description.links["link_" .. name] = info[name]
first = false
end
end
add_link_button(fgettext("Donate"), "donate_url")
add_link_button(fgettext("Website"), "website")
add_link_button(fgettext("Source"), "repo")
add_link_button(fgettext("Issue Tracker"), "issue_tracker")
add_link_button(fgettext("Translate"), "translation_url")
add_link_button(fgettext("Forum Topic"), "forums")
hypertext = hypertext .. "\n\n" .. info.long_description.body
hypertext = hypertext:gsub("<img name=\"?blank.png\"? ",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "blank.png\" ")
table.insert_all(formspec, {
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
";desc;", core.formspec_escape(hypertext), "]",
})
elseif current_tab == 2 then
local hypertext = info.info_hypertext.head .. info.info_hypertext.body
table.insert_all(formspec, {
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
";info;", core.formspec_escape(hypertext), "]",
})
else
error("Unknown tab " .. current_tab)
end
formspec[#formspec + 1] = "container_end[]"
formspec[#formspec + 1] = "container_end[]"
return table.concat(formspec)
end
local function handle_hypertext_event(this, event, hypertext_object)
if not (event and event:sub(1, 7) == "action:") then
return
end
for i, ss in ipairs(this.data.info.screenshots) do
if event == "action:ss_" .. i then
core.open_url(ss.url)
return true
end
end
local base_url = core.settings:get("contentdb_url"):gsub("(%W)", "%%%1")
for key, url in pairs(hypertext_object.links) do
if event == "action:" .. key then
local author, name = url:match("^" .. base_url .. "/?packages/([A-Za-z0-9 _-]+)/([a-z0-9_]+)/?$")
if author and name then
local package2 = contentdb.get_package_by_info(author, name)
if package2 then
local dlg = create_package_dialog(package2)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
end
core.open_url_dialog(url)
return true
end
end
end
local function handle_submit(this, fields)
local info = this.data.info
local package = this.data.package
if fields.back then
this:delete()
return true
end
if not info then
return false
end
if fields.open_contentdb then
local url = ("%s/packages/%s/?protocol_version=%d"):format(
core.settings:get("contentdb_url"), package.url_part,
core.get_max_supp_proto())
core.open_url(url)
return true
end
if fields.install then
install_or_update_package(this, package)
return true
end
if fields.uninstall then
local dlg = create_delete_content_dlg(package)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
if fields.tabs then
this.data.current_tab = tonumber(fields.tabs)
return true
end
if handle_hypertext_event(this, fields.desc, info.long_description) or
handle_hypertext_event(this, fields.info, info.info_hypertext) then
return true
end
end
local function handle_events(event)
if event == "WindowInfoChange" then
ui.update()
return true
end
return false
end
function create_package_dialog(package)
assert(package)
local dlg = dialog_create("package_dialog_" .. package.id,
get_formspec,
handle_submit,
handle_events)
local data = dlg.data
data.package = package
data.info = nil
data.loading = false
data.loading_error = nil
data.current_tab = 1
return dlg
end

View File

@ -23,4 +23,5 @@ dofile(path .. DIR_DELIM .. "update_detector.lua")
dofile(path .. DIR_DELIM .. "screenshots.lua")
dofile(path .. DIR_DELIM .. "dlg_install.lua")
dofile(path .. DIR_DELIM .. "dlg_overwrite.lua")
dofile(path .. DIR_DELIM .. "dlg_package.lua")
dofile(path .. DIR_DELIM .. "dlg_contentdb.lua")

View File

@ -23,23 +23,40 @@ local screenshot_downloading = {}
local screenshot_downloaded = {}
local function get_filename(path)
local parts = path:split("/")
return parts[#parts]
end
local function get_file_extension(path)
local parts = path:split(".")
return parts[#parts]
end
function get_screenshot(package)
if not package.thumbnail then
function get_screenshot(package, screenshot_url, level)
if not screenshot_url then
return defaulttexturedir .. "no_screenshot.png"
elseif screenshot_downloading[package.thumbnail] then
end
-- Minetest only supports png and jpg
local ext = get_file_extension(screenshot_url)
if ext ~= "png" and ext ~= "jpg" then
screenshot_url = screenshot_url:sub(0, -#ext - 1) .. "png"
end
-- Set thumbnail level
screenshot_url = screenshot_url:gsub("/thumbnails/[0-9]+/", "/thumbnails/" .. level .. "/")
screenshot_url = screenshot_url:gsub("/uploads/", "/thumbnails/" .. level .. "/")
if screenshot_downloading[screenshot_url] then
return defaulttexturedir .. "loading_screenshot.png"
end
-- Get tmp screenshot path
local ext = get_file_extension(package.thumbnail)
local filepath = screenshot_dir .. DIR_DELIM ..
("%s-%s-%s.%s"):format(package.type, package.author, package.name, ext)
("%s-%s-%s-l%d-%s"):format(package.type, package.author, package.name,
level, get_filename(screenshot_url))
-- Return if already downloaded
local file = io.open(filepath, "r")
@ -49,7 +66,7 @@ function get_screenshot(package)
end
-- Show error if we've failed to download before
if screenshot_downloaded[package.thumbnail] then
if screenshot_downloaded[screenshot_url] then
return defaulttexturedir .. "error_screenshot.png"
end
@ -59,16 +76,16 @@ function get_screenshot(package)
return core.download_file(params.url, params.dest)
end
local function callback(success)
screenshot_downloading[package.thumbnail] = nil
screenshot_downloaded[package.thumbnail] = true
screenshot_downloading[screenshot_url] = nil
screenshot_downloaded[screenshot_url] = true
if not success then
core.log("warning", "Screenshot download failed for some reason")
end
ui.update()
end
if core.handle_async(download_screenshot,
{ dest = filepath, url = package.thumbnail }, callback) then
screenshot_downloading[package.thumbnail] = true
{ dest = filepath, url = screenshot_url }, callback) then
screenshot_downloading[screenshot_url] = true
else
core.log("error", "ERROR: async event failed")
return defaulttexturedir .. "error_screenshot.png"

View File

@ -0,0 +1,85 @@
{
"#": "https://github.com/orgs/minetest/teams/engine/members",
"core_developers": [
"Perttu Ahola (celeron55) <celeron55@gmail.com> [Project founder]",
"sfan5 <sfan5@live.de>",
"ShadowNinja <shadowninja@minetest.net>",
"Nathanaëlle Courant (Nore/Ekdohibs) <nore@mesecons.net>",
"Loic Blot (nerzhul/nrz) <loic.blot@unix-experience.fr>",
"Andrew Ward (rubenwardy) <rw@rubenwardy.com>",
"Krock/SmallJoker <mk939@ymail.com>",
"Lars Hofhansl <larsh@apache.org>",
"v-rob <robinsonvincent89@gmail.com>",
"Desour/DS",
"srifqi",
"Gregor Parzefall (grorp)",
"Lars Müller (luatic)"
],
"previous_core_developers": [
"BlockMen",
"Maciej Kasatkin (RealBadAngel) [RIP]",
"Lisa Milne (darkrose) <lisa@ltmnet.com>",
"proller",
"Ilya Zhuravlev (xyz) <xyz@minetest.net>",
"PilzAdam <pilzadam@minetest.net>",
"est31 <MTest31@outlook.com>",
"kahrl <kahrl@gmx.net>",
"Ryan Kwolek (kwolekr) <kwolekr@minetest.net>",
"sapier",
"Zeno",
"Auke Kok (sofar) <sofar@foo-projects.org>",
"Aaron Suen <warr1024@gmail.com>",
"paramat",
"Pierre-Yves Rollo <dev@pyrollo.com>",
"hecks",
"Jude Melton-Houghton (TurkeyMcMac) [RIP]",
"Hugues Ross <hugues.ross@gmail.com>",
"Dmitry Kostenko (x2048) <codeforsmile@gmail.com>"
],
"#": "Currently only https://github.com/orgs/minetest/teams/triagers/members",
"core_team": [
"Zughy [Issue triager]",
"wsor [Issue triager]",
"Hugo Locurcio (Calinou) [Issue triager]"
],
"#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py",
"contributors": [
"cx384",
"numzero",
"AFCMS",
"sfence",
"Wuzzy",
"ROllerozxa",
"JosiahWI",
"OgelGames",
"David Heidelberg",
"1F616EMO",
"HybridDog",
"Bradley Pierce (Thresher)",
"savilli",
"Stvk imension",
"y5nw",
"chmodsayshello",
"jordan4ibanez",
"superfloh247"
],
"previous_contributors": [
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest logo]",
"red-001 <red-001@outlook.ie>",
"Giuseppe Bilotta",
"HybridDog",
"ClobberXD",
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>",
"Jean-Patrick Guerrero (kilbith)",
"MoNTE48",
"Constantin Wenger (SpeedProg)",
"Ciaran Gultnieks (CiaranG)",
"Paul Ouellette (pauloue)",
"stujones11",
"Rogier <rogier777@gmail.com>",
"Gregory Currie (gregorycu)",
"JacobF",
"Jeija <jeija@mesecons.net>"
]
}

View File

@ -126,7 +126,7 @@ local function get_formspec(data)
local retval =
"size[11.5,7.5,true]" ..
"label[0.5,0;" .. fgettext("World:") .. "]" ..
"label[1.75,0;" .. data.worldspec.name .. "]"
"label[1.75,0;" .. core.formspec_escape(data.worldspec.name) .. "]"
if mod.is_modpack or mod.type == "game" then
local info = core.formspec_escape(

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

@ -67,6 +67,19 @@ function make.heading(text)
end
function make.note(text)
return {
full_width = true,
get_formspec = function(self, avail_w)
-- Assuming label height 0.4:
-- Position at y=0 to eat 0.2 of the padding above, leave 0.05.
-- The returned used_height doesn't include padding.
return ("label[0,0;%s]"):format(core.colorize("#bbb", core.formspec_escape(text))), 0.2
end,
}
end
--- Used for string and numeric style fields
---
--- @param converter Function to coerce values from strings.

View File

@ -110,7 +110,7 @@ local function load()
local change_keys = {
query_text = "Controls",
requires = {
keyboard_mouse = true,
touch_controls = false,
},
get_formspec = function(self, avail_w)
local btn_w = math.min(avail_w, 3)
@ -152,9 +152,24 @@ local function load()
table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys)
do
local content = page_by_id.graphics_and_audio_shaders.content
local content = page_by_id.graphics_and_audio_effects.content
local idx = table.indexof(content, "enable_dynamic_shadows")
table.insert(content, idx, shadows_component)
idx = table.indexof(content, "enable_auto_exposure") + 1
local note = component_funcs.note(fgettext_ne("(The game will need to enable automatic exposure as well)"))
note.requires = get_setting_info("enable_auto_exposure").requires
table.insert(content, idx, note)
idx = table.indexof(content, "enable_bloom") + 1
note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)"))
note.requires = get_setting_info("enable_bloom").requires
table.insert(content, idx, note)
idx = table.indexof(content, "enable_volumetric_lighting") + 1
note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)"))
note.requires = get_setting_info("enable_volumetric_lighting").requires
table.insert(content, idx, note)
end
-- These must not be translated, as they need to show in the local
@ -222,6 +237,12 @@ local function load()
zh_CN = "中文 (简体) [zh_CN]",
zh_TW = "正體中文 (繁體) [zh_TW]",
}
get_setting_info("touch_controls").option_labels = {
["auto"] = fgettext_ne("Auto"),
["true"] = fgettext_ne("Enabled"),
["false"] = fgettext_ne("Disabled"),
}
end
@ -321,11 +342,14 @@ local function check_requirements(name, requires)
local video_driver = core.get_active_driver()
local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2"
local touch_controls = core.settings:get("touch_controls")
local special = {
android = PLATFORM == "Android",
desktop = PLATFORM ~= "Android",
touchscreen_gui = core.settings:get_bool("enable_touch"),
keyboard_mouse = not core.settings:get_bool("enable_touch"),
-- When touch_controls is "auto", we don't which input method will be used,
-- so we show settings for both.
touchscreen = touch_controls == "auto" or core.is_yes(touch_controls),
keyboard_mouse = touch_controls == "auto" or not core.is_yes(touch_controls),
shaders_support = shaders_support,
shaders = core.settings:get_bool("enable_shaders") and shaders_support,
opengl = video_driver == "opengl",
@ -435,19 +459,6 @@ local function build_page_components(page)
end
--- Creates a scrollbaroptions for a scroll_container
--
-- @param visible_l the length of the scroll_container and scrollbar
-- @param total_l length of the scrollable area
-- @param scroll_factor as passed to scroll_container
local function make_scrollbaroptions_for_scroll_container(visible_l, total_l, scroll_factor)
assert(total_l >= visible_l)
local max = total_l - visible_l
local thumb_size = (visible_l / total_l) * max
return ("scrollbaroptions[min=0;max=%f;thumbsize=%f]"):format(max / scroll_factor, thumb_size / scroll_factor)
end
local formspec_show_hack = false
@ -457,13 +468,13 @@ local function get_formspec(dialogdata)
local extra_h = 1 -- not included in tabsize.height
local tabsize = {
width = core.settings:get_bool("enable_touch") and 16.5 or 15.5,
height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12,
width = core.settings:get_bool("touch_gui") and 16.5 or 15.5,
height = core.settings:get_bool("touch_gui") and (10 - extra_h) or 12,
}
local scrollbar_w = core.settings:get_bool("enable_touch") and 0.6 or 0.4
local scrollbar_w = core.settings:get_bool("touch_gui") and 0.6 or 0.4
local left_pane_width = core.settings:get_bool("enable_touch") and 4.5 or 4.25
local left_pane_width = core.settings:get_bool("touch_gui") and 4.5 or 4.25
local left_pane_padding = 0.25
local search_width = left_pane_width + scrollbar_w - (0.75 * 2)
@ -477,7 +488,7 @@ local function get_formspec(dialogdata)
local fs = {
"formspec_version[6]",
"size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "",
core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "",
"bgcolor[#0000]",
-- HACK: this is needed to allow resubmitting the same formspec
@ -509,8 +520,8 @@ local function get_formspec(dialogdata)
"tooltip[search;", fgettext("Search"), "]",
"tooltip[search_clear;", fgettext("Clear"), "]",
"container_end[]",
"scroll_container[0.25,1.25;", tostring(left_pane_width), ",",
tostring(tabsize.height - 1.5), ";leftscroll;vertical;0.1]",
("scroll_container[0.25,1.25;%f,%f;leftscroll;vertical;0.1;0]"):format(
left_pane_width, tabsize.height - 1.5),
"style_type[button;border=false;bgcolor=#3333]",
"style_type[button:hover;border=false;bgcolor=#6663]",
}
@ -540,7 +551,6 @@ local function get_formspec(dialogdata)
fs[#fs + 1] = "scroll_container_end[]"
if y >= tabsize.height - 1.25 then
fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height - 1.5, y, 0.1)
fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format(
left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0)
end
@ -552,7 +562,7 @@ local function get_formspec(dialogdata)
end
local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25
fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1]"):format(
fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1;0.25]"):format(
tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height)
y = 0.25
@ -608,7 +618,6 @@ local function get_formspec(dialogdata)
fs[#fs + 1] = "scroll_container_end[]"
if y >= tabsize.height then
fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height, y + 0.375, 0.1)
fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format(
tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0)
end
@ -626,6 +635,18 @@ function write_settings_early()
end
end
local function regenerate_page_list(dialogdata)
local suggested_page_id = update_filtered_pages(dialogdata.query)
dialogdata.components = nil
if not filtered_page_by_id[dialogdata.page_id] then
dialogdata.leftscroll = 0
dialogdata.rightscroll = 0
dialogdata.page_id = suggested_page_id
end
end
local function buttonhandler(this, fields)
local dialogdata = this.data
@ -650,27 +671,7 @@ local function buttonhandler(this, fields)
local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value)
write_settings_early()
end
-- enable_touch is a checkbox in a setting component. We handle this
-- setting differently so we can hide/show pages using the next if-statement
if fields.enable_touch ~= nil then
local value = core.is_yes(fields.enable_touch)
core.settings:set_bool("enable_touch", value)
write_settings_early()
end
if fields.show_advanced ~= nil or fields.enable_touch ~= nil then
local suggested_page_id = update_filtered_pages(dialogdata.query)
dialogdata.components = nil
if not filtered_page_by_id[dialogdata.page_id] then
dialogdata.leftscroll = 0
dialogdata.rightscroll = 0
dialogdata.page_id = suggested_page_id
end
regenerate_page_list(dialogdata)
return true
end
@ -703,20 +704,26 @@ local function buttonhandler(this, fields)
end
end
for i, comp in ipairs(dialogdata.components) do
if comp.on_submit and comp:on_submit(fields, this) then
write_settings_early()
local function after_setting_change(comp)
write_settings_early()
if comp.setting.name == "touch_controls" then
-- Changing the "touch_controls" setting may result in a different
-- page list.
regenerate_page_list(dialogdata)
else
-- Clear components so they regenerate
dialogdata.components = nil
end
end
for i, comp in ipairs(dialogdata.components) do
if comp.on_submit and comp:on_submit(fields, this) then
after_setting_change(comp)
return true
end
if comp.setting and fields["reset_" .. i] then
core.settings:remove(comp.setting.name)
write_settings_early()
-- Clear components so they regenerate
dialogdata.components = nil
after_setting_change(comp)
return true
end
end

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