resolve merge conflict in game.cpp
This commit is contained in:
commit
c4bf5d0678
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -3,3 +3,5 @@
|
|||||||
|
|
||||||
*.cpp diff=cpp
|
*.cpp diff=cpp
|
||||||
*.h diff=cpp
|
*.h diff=cpp
|
||||||
|
|
||||||
|
*.gltf binary
|
||||||
|
7
.github/workflows/linux.yml
vendored
7
.github/workflows/linux.yml
vendored
@ -88,7 +88,7 @@ jobs:
|
|||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
source ./util/ci/common.sh
|
source ./util/ci/common.sh
|
||||||
install_linux_deps clang-7 llvm
|
install_linux_deps clang-7 llvm-7
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
@ -102,6 +102,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./bin/minetest --run-unittests
|
./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
|
# Current clang version
|
||||||
clang_18:
|
clang_18:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
2
.github/workflows/lua.yml
vendored
2
.github/workflows/lua.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Integration test + devtest
|
- name: Integration test + devtest
|
||||||
run: |
|
run: |
|
||||||
./util/test_multiplayer.sh
|
serverconf="profiler.load=true" ./util/test_multiplayer.sh
|
||||||
|
|
||||||
luacheck:
|
luacheck:
|
||||||
name: "Builtin Luacheck and Unit Tests"
|
name: "Builtin Luacheck and Unit Tests"
|
||||||
|
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@ -29,7 +29,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
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
|
runs-on: macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
45
.github/workflows/whitespace_checks.yml
vendored
Normal file
45
.github/workflows/whitespace_checks.yml
vendored
Normal 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
3
.gitignore
vendored
@ -26,6 +26,7 @@ tags
|
|||||||
!tags/
|
!tags/
|
||||||
gtags.files
|
gtags.files
|
||||||
.idea
|
.idea
|
||||||
|
.qtcreator/
|
||||||
# Codelite
|
# Codelite
|
||||||
*.project
|
*.project
|
||||||
# Visual Studio Code & plugins
|
# Visual Studio Code & plugins
|
||||||
@ -109,6 +110,8 @@ src/cmake_config_githash.h
|
|||||||
*.layout
|
*.layout
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
|
*.dump
|
||||||
|
*.dmp
|
||||||
*.ninja
|
*.ninja
|
||||||
.ninja*
|
.ninja*
|
||||||
*.gch
|
*.gch
|
||||||
|
@ -37,6 +37,12 @@ files["builtin/client/register.lua"] = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
files["builtin/common/math.lua"] = {
|
||||||
|
globals = {
|
||||||
|
"math",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
files["builtin/common/misc_helpers.lua"] = {
|
files["builtin/common/misc_helpers.lua"] = {
|
||||||
globals = {
|
globals = {
|
||||||
"dump", "dump2", "table", "math", "string",
|
"dump", "dump2", "table", "math", "string",
|
||||||
@ -46,7 +52,7 @@ files["builtin/common/misc_helpers.lua"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
files["builtin/common/vector.lua"] = {
|
files["builtin/common/vector.lua"] = {
|
||||||
globals = { "vector" },
|
globals = { "vector", "math" },
|
||||||
}
|
}
|
||||||
|
|
||||||
files["builtin/game/voxelarea.lua"] = {
|
files["builtin/game/voxelarea.lua"] = {
|
||||||
|
@ -56,6 +56,11 @@ if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE)
|
|||||||
endif()
|
endif()
|
||||||
set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization")
|
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)
|
set(DEFAULT_RUN_IN_PLACE FALSE)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(DEFAULT_RUN_IN_PLACE TRUE)
|
set(DEFAULT_RUN_IN_PLACE TRUE)
|
||||||
@ -283,6 +288,8 @@ if(BUILD_UNITTESTS OR BUILD_BENCHMARKS)
|
|||||||
add_subdirectory(lib/catch2)
|
add_subdirectory(lib/catch2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(lib/tiniergltf)
|
||||||
|
|
||||||
# Subdirectories
|
# Subdirectories
|
||||||
# Be sure to add all relevant definitions above this
|
# Be sure to add all relevant definitions above this
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
@ -368,3 +375,19 @@ if(BUILD_DOCUMENTATION)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
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()
|
||||||
|
@ -57,12 +57,10 @@ srifqi:
|
|||||||
textures/base/pack/minimap_btn.png
|
textures/base/pack/minimap_btn.png
|
||||||
|
|
||||||
Zughy:
|
Zughy:
|
||||||
textures/base/pack/cdb_add.png
|
|
||||||
textures/base/pack/cdb_downloading.png
|
textures/base/pack/cdb_downloading.png
|
||||||
textures/base/pack/cdb_queued.png
|
textures/base/pack/cdb_queued.png
|
||||||
textures/base/pack/cdb_update.png
|
textures/base/pack/cdb_update.png
|
||||||
textures/base/pack/cdb_update_cropped.png
|
textures/base/pack/cdb_update_cropped.png
|
||||||
textures/base/pack/cdb_viewonline.png
|
|
||||||
textures/base/pack/settings_btn.png
|
textures/base/pack/settings_btn.png
|
||||||
textures/base/pack/settings_info.png
|
textures/base/pack/settings_info.png
|
||||||
textures/base/pack/settings_reset.png
|
textures/base/pack/settings_reset.png
|
||||||
@ -79,7 +77,6 @@ kilbith:
|
|||||||
textures/base/pack/progress_bar_bg.png
|
textures/base/pack/progress_bar_bg.png
|
||||||
|
|
||||||
SmallJoker:
|
SmallJoker:
|
||||||
textures/base/pack/cdb_clear.png
|
|
||||||
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
|
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
|
||||||
|
|
||||||
DS:
|
DS:
|
||||||
|
@ -119,6 +119,7 @@ Command-line options
|
|||||||
Compiling
|
Compiling
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
- [Compiling - common information](doc/compiling/README.md)
|
||||||
- [Compiling on GNU/Linux](doc/compiling/linux.md)
|
- [Compiling on GNU/Linux](doc/compiling/linux.md)
|
||||||
- [Compiling on Windows](doc/compiling/windows.md)
|
- [Compiling on Windows](doc/compiling/windows.md)
|
||||||
- [Compiling on MacOS](doc/compiling/macos.md)
|
- [Compiling on MacOS](doc/compiling/macos.md)
|
||||||
|
@ -8,7 +8,7 @@ android {
|
|||||||
compileSdk 34
|
compileSdk 34
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
|
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||||
versionCode project.versionCode
|
versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@ -116,18 +116,6 @@ clean {
|
|||||||
delete new File("src/main/assets", "Minetest.zip")
|
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 {
|
dependencies {
|
||||||
implementation project(':native')
|
implementation project(':native')
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
@ -4,10 +4,9 @@ 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
|
project.ext.set("versionPatch", 0) // Version Patch
|
||||||
// ^ keep in sync with cmake
|
// ^ keep in sync with cmake
|
||||||
project.ext.set("versionCode", 48) // Android Version Code
|
|
||||||
// NOTE: +2 after each release!
|
project.ext.set("versionBuild", 0) // Version Build
|
||||||
// +1 for ARM and +1 for ARM64 APK's, because
|
// ^ fourth version number to allow releasing Android-only fixes and beta versions
|
||||||
// each APK must have a larger `versionCode` than the previous
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.ndk_version = '26.2.11394342'
|
ext.ndk_version = '26.2.11394342'
|
||||||
|
@ -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)
|
|
@ -9,6 +9,5 @@ dofile(commonpath .. "mod_storage.lua")
|
|||||||
dofile(commonpath .. "chatcommands.lua")
|
dofile(commonpath .. "chatcommands.lua")
|
||||||
dofile(commonpath .. "information_formspecs.lua")
|
dofile(commonpath .. "information_formspecs.lua")
|
||||||
dofile(clientpath .. "chatcommands.lua")
|
dofile(clientpath .. "chatcommands.lua")
|
||||||
dofile(clientpath .. "death_formspec.lua")
|
|
||||||
dofile(clientpath .. "misc.lua")
|
dofile(clientpath .. "misc.lua")
|
||||||
assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions
|
assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions
|
||||||
|
@ -69,7 +69,7 @@ local function build_chatcommands_formspec(name, sel, copy)
|
|||||||
description = cmds[2].description
|
description = cmds[2].description
|
||||||
if copy then
|
if copy then
|
||||||
local msg = S("Command: @1 @2",
|
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
|
if INIT == "client" then
|
||||||
core.display_chat_message(msg)
|
core.display_chat_message(msg)
|
||||||
else
|
else
|
||||||
|
@ -166,20 +166,19 @@ function core.is_colored_paramtype(ptype)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function core.strip_param2_color(param2, paramtype2)
|
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
|
return nil
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- Content ID caching
|
-- Content ID caching
|
||||||
|
41
builtin/common/math.lua
Normal file
41
builtin/common/math.lua
Normal 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
|
@ -3,6 +3,7 @@
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Localize functions to avoid table lookups (better performance).
|
-- Localize functions to avoid table lookups (better performance).
|
||||||
local string_sub, string_find = string.sub, string.find
|
local string_sub, string_find = string.sub, string.find
|
||||||
|
local math = math
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
local function basic_dump(o)
|
local function basic_dump(o)
|
||||||
@ -220,47 +221,6 @@ function string:trim()
|
|||||||
return self:match("^%s*(.-)%s*$")
|
return self:match("^%s*(.-)%s*$")
|
||||||
end
|
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 = {
|
local formspec_escapes = {
|
||||||
["\\"] = "\\\\",
|
["\\"] = "\\\\",
|
||||||
["["] = "\\[",
|
["["] = "\\[",
|
||||||
@ -275,6 +235,16 @@ function core.formspec_escape(text)
|
|||||||
end
|
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)
|
function core.wrap_text(text, max_length, as_table)
|
||||||
local result = {}
|
local result = {}
|
||||||
local line = {}
|
local line = {}
|
||||||
@ -604,12 +574,14 @@ function core.strip_colors(str)
|
|||||||
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
|
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.translate(textdomain, str, ...)
|
local function translate(textdomain, str, num, ...)
|
||||||
local start_seq
|
local start_seq
|
||||||
if textdomain == "" then
|
if textdomain == "" and num == "" then
|
||||||
start_seq = ESCAPE_CHAR .. "T"
|
start_seq = ESCAPE_CHAR .. "T"
|
||||||
else
|
elseif num == "" then
|
||||||
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
|
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
|
||||||
|
else
|
||||||
|
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. "@" .. num .. ")"
|
||||||
end
|
end
|
||||||
local arg = {n=select('#', ...), ...}
|
local arg = {n=select('#', ...), ...}
|
||||||
local end_seq = ESCAPE_CHAR .. "E"
|
local end_seq = ESCAPE_CHAR .. "E"
|
||||||
@ -640,8 +612,31 @@ function core.translate(textdomain, str, ...)
|
|||||||
return start_seq .. translated .. end_seq
|
return start_seq .. translated .. end_seq
|
||||||
end
|
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)
|
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
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
@ -702,6 +697,7 @@ function core.privs_to_string(privs, delim)
|
|||||||
list[#list + 1] = priv
|
list[#list + 1] = priv
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
table.sort(list)
|
||||||
return table.concat(list, delim)
|
return table.concat(list, delim)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
_G.core = {}
|
_G.core = {}
|
||||||
_G.vector = {metatable = {}}
|
_G.vector = {metatable = {}}
|
||||||
|
|
||||||
|
dofile("builtin/common/math.lua")
|
||||||
dofile("builtin/common/vector.lua")
|
dofile("builtin/common/vector.lua")
|
||||||
dofile("builtin/common/misc_helpers.lua")
|
dofile("builtin/common/misc_helpers.lua")
|
||||||
|
|
||||||
|
16
builtin/common/tests/math_spec.lua
Normal file
16
builtin/common/tests/math_spec.lua
Normal 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)
|
@ -1,4 +1,5 @@
|
|||||||
_G.core = {}
|
_G.core = {}
|
||||||
|
dofile("builtin/common/math.lua")
|
||||||
dofile("builtin/common/vector.lua")
|
dofile("builtin/common/vector.lua")
|
||||||
dofile("builtin/common/misc_helpers.lua")
|
dofile("builtin/common/misc_helpers.lua")
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
_G.vector = {}
|
_G.vector = {}
|
||||||
|
dofile("builtin/common/math.lua")
|
||||||
dofile("builtin/common/vector.lua")
|
dofile("builtin/common/vector.lua")
|
||||||
|
|
||||||
describe("vector", function()
|
describe("vector", function()
|
||||||
@ -113,12 +114,35 @@ describe("vector", function()
|
|||||||
assert.equal(vector.new(0, 1, -1), a:round())
|
assert.equal(vector.new(0, 1, -1), a:round())
|
||||||
end)
|
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()
|
it("apply()", function()
|
||||||
local i = 0
|
local i = 0
|
||||||
local f = function(x)
|
local f = function(x)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
return x + i
|
return x + i
|
||||||
end
|
end
|
||||||
|
local f2 = function(x, opt1, opt2, opt3)
|
||||||
|
return x + opt1 + opt2 + opt3
|
||||||
|
end
|
||||||
local a = vector.new(0.1, 0.9, -0.5)
|
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), vector.apply(a, math.ceil))
|
||||||
assert.equal(vector.new(1, 1, 0), a:apply(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(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(1.1, 2.9, 2.5), vector.apply(a, f))
|
||||||
assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(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)
|
end)
|
||||||
|
|
||||||
it("combine()", function()
|
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.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)))
|
assert.False(vector.in_area(vector.new(-10, -10, -10), vector.new(10, 10, 10), vector.new(-11, -10, -10)))
|
||||||
end)
|
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)
|
end)
|
||||||
|
@ -5,6 +5,7 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta
|
|||||||
|
|
||||||
-- localize functions
|
-- localize functions
|
||||||
local setmetatable = setmetatable
|
local setmetatable = setmetatable
|
||||||
|
local math = math
|
||||||
|
|
||||||
vector = {}
|
vector = {}
|
||||||
|
|
||||||
@ -97,18 +98,26 @@ function vector.floor(v)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function vector.round(v)
|
function vector.round(v)
|
||||||
return fast_new(
|
return vector.apply(v, math.round)
|
||||||
math.round(v.x),
|
|
||||||
math.round(v.y),
|
|
||||||
math.round(v.z)
|
|
||||||
)
|
|
||||||
end
|
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(
|
return fast_new(
|
||||||
func(v.x),
|
func(v.x, ...),
|
||||||
func(v.y),
|
func(v.y, ...),
|
||||||
func(v.z)
|
func(v.z, ...)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -387,6 +396,14 @@ function vector.random_direction()
|
|||||||
return fast_new(x/l, y/l, z/l)
|
return fast_new(x/l, y/l, z/l)
|
||||||
end
|
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
|
if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then
|
||||||
local function read_vector(v)
|
local function read_vector(v)
|
||||||
return v.x, v.y, v.z
|
return v.x, v.y, v.z
|
||||||
|
@ -23,6 +23,8 @@ core.add_node = core.set_node
|
|||||||
-- we don't deal with metadata currently
|
-- we don't deal with metadata currently
|
||||||
core.swap_node = core.set_node
|
core.swap_node = core.set_node
|
||||||
|
|
||||||
|
core.bulk_swap_node = core.bulk_set_node
|
||||||
|
|
||||||
function core.remove_node(pos)
|
function core.remove_node(pos)
|
||||||
return core.vmanip:set_node_at(pos, {name="air"})
|
return core.vmanip:set_node_at(pos, {name="air"})
|
||||||
end
|
end
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
local BASE_SPACING = 0.1
|
local BASE_SPACING = 0.1
|
||||||
local function get_scroll_btn_width()
|
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
|
end
|
||||||
|
|
||||||
local function buttonbar_formspec(self)
|
local function buttonbar_formspec(self)
|
||||||
@ -28,10 +28,8 @@ local function buttonbar_formspec(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local formspec = {
|
local formspec = {
|
||||||
"style_type[box;noclip=true]",
|
|
||||||
string.format("box[%f,%f;%f,%f;%s]", self.pos.x, self.pos.y, self.size.x,
|
string.format("box[%f,%f;%f,%f;%s]", self.pos.x, self.pos.y, self.size.x,
|
||||||
self.size.y, self.bgcolor),
|
self.size.y, self.bgcolor),
|
||||||
"style_type[box;noclip=false]",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local btn_size = self.size.y - 2*BASE_SPACING
|
local btn_size = self.size.y - 2*BASE_SPACING
|
||||||
@ -71,7 +69,7 @@ local function buttonbar_formspec(self)
|
|||||||
y = self.pos.y + BASE_SPACING,
|
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_pos.x, btn_pos.y, btn_size, btn_size, btn.image, btn.name,
|
||||||
btn.caption, btn.name, btn.tooltip))
|
btn.caption, btn.name, btn.tooltip))
|
||||||
end
|
end
|
||||||
@ -86,9 +84,6 @@ local function buttonbar_formspec(self)
|
|||||||
y = self.pos.y + BASE_SPACING,
|
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;<]",
|
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,
|
btn_prev_pos.x, btn_prev_pos.y, get_scroll_btn_width(), btn_size,
|
||||||
self.btn_prev_name))
|
self.btn_prev_name))
|
||||||
|
@ -66,11 +66,22 @@ local function get_formspec(self)
|
|||||||
|
|
||||||
local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize)
|
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
|
if self.parent == nil and not prepend then
|
||||||
prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height,
|
prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height,
|
||||||
dump(self.fixed_size))
|
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
|
if tab.formspec_version then
|
||||||
prepend = ("formspec_version[%d]"):format(tab.formspec_version) .. prepend
|
prepend = ("formspec_version[%d]"):format(tab.formspec_version) .. prepend
|
||||||
end
|
end
|
||||||
@ -78,12 +89,15 @@ local function get_formspec(self)
|
|||||||
|
|
||||||
local end_button_size = 0.75
|
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
|
if self.end_button then
|
||||||
tab_header_size.width = tab_header_size.width - end_button_size - 0.1
|
tab_header_size.width = tab_header_size.width - end_button_size - 0.1
|
||||||
end
|
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
|
if self.end_button then
|
||||||
formspec = formspec ..
|
formspec = formspec ..
|
||||||
@ -98,6 +112,8 @@ local function get_formspec(self)
|
|||||||
self.end_button.name)
|
self.end_button.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
formspec = formspec .. "container_end[]"
|
||||||
|
|
||||||
return formspec
|
return formspec
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -221,6 +221,7 @@ core.register_chatcommand("haspriv", {
|
|||||||
return true, S("No online player has the \"@1\" privilege.",
|
return true, S("No online player has the \"@1\" privilege.",
|
||||||
param)
|
param)
|
||||||
else
|
else
|
||||||
|
table.sort(players_with_priv)
|
||||||
return true, S("Players online with the \"@1\" privilege: @2",
|
return true, S("Players online with the \"@1\" privilege: @2",
|
||||||
param,
|
param,
|
||||||
table.concat(players_with_priv, ", "))
|
table.concat(players_with_priv, ", "))
|
||||||
|
31
builtin/game/death_screen.lua
Normal file
31
builtin/game/death_screen.lua
Normal 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)
|
@ -42,6 +42,9 @@ core.features = {
|
|||||||
node_interaction_actor = true,
|
node_interaction_actor = true,
|
||||||
moveresult_new_pos = true,
|
moveresult_new_pos = true,
|
||||||
override_item_remove_fields = true,
|
override_item_remove_fields = true,
|
||||||
|
hotbar_hud_element = true,
|
||||||
|
bulk_lbms = true,
|
||||||
|
abm_without_neighbors = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
function core.has_feature(arg)
|
function core.has_feature(arg)
|
||||||
|
@ -251,11 +251,31 @@ register_builtin_hud_element("minimap", {
|
|||||||
position = {x = 1, y = 0},
|
position = {x = 1, y = 0},
|
||||||
alignment = {x = -1, y = 1},
|
alignment = {x = -1, y = 1},
|
||||||
offset = {x = -10, y = 10},
|
offset = {x = -10, y = 10},
|
||||||
size = {x = 256, y = 256},
|
size = {x = 0, y = -25},
|
||||||
},
|
},
|
||||||
show_elem = function(player, flags)
|
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++.
|
-- Don't add a minimap for clients which already have it hardcoded in C++.
|
||||||
return flags.minimap and
|
return flags.minimap and proto_ver >= 44
|
||||||
core.get_player_information(player:get_player_name()).protocol_version >= 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,
|
end,
|
||||||
})
|
})
|
||||||
|
@ -38,6 +38,7 @@ dofile(gamepath .. "forceloading.lua")
|
|||||||
dofile(gamepath .. "hud.lua")
|
dofile(gamepath .. "hud.lua")
|
||||||
dofile(gamepath .. "knockback.lua")
|
dofile(gamepath .. "knockback.lua")
|
||||||
dofile(gamepath .. "async.lua")
|
dofile(gamepath .. "async.lua")
|
||||||
|
dofile(gamepath .. "death_screen.lua")
|
||||||
|
|
||||||
core.after(0, builtin_shared.cache_content_ids)
|
core.after(0, builtin_shared.cache_content_ids)
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ local S = core.get_translator("__builtin")
|
|||||||
-- Misc. API functions
|
-- Misc. API functions
|
||||||
--
|
--
|
||||||
|
|
||||||
-- @spec core.kick_player(String, String) :: Boolean
|
-- @spec core.kick_player(String, String, Boolean) :: Boolean
|
||||||
function core.kick_player(player_name, reason)
|
function core.kick_player(player_name, reason, reconnect)
|
||||||
if type(reason) == "string" then
|
if type(reason) == "string" then
|
||||||
reason = "Kicked: " .. reason
|
reason = "Kicked: " .. reason
|
||||||
else
|
else
|
||||||
reason = "Kicked."
|
reason = "Kicked."
|
||||||
end
|
end
|
||||||
return core.disconnect_player(player_name, reason)
|
return core.disconnect_player(player_name, reason, reconnect)
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.check_player_privs(name, ...)
|
function core.check_player_privs(name, ...)
|
||||||
@ -298,3 +298,28 @@ do
|
|||||||
return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos))
|
return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos))
|
||||||
end
|
end
|
||||||
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
|
||||||
|
@ -105,7 +105,12 @@ function core.register_lbm(spec)
|
|||||||
-- Add to core.registered_lbms
|
-- Add to core.registered_lbms
|
||||||
check_modname_prefix(spec.name)
|
check_modname_prefix(spec.name)
|
||||||
check_node_list(spec.nodenames, "nodenames")
|
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
|
core.registered_lbms[#core.registered_lbms + 1] = spec
|
||||||
spec.mod_origin = core.get_current_modname() or "??"
|
spec.mod_origin = core.get_current_modname() or "??"
|
||||||
end
|
end
|
||||||
|
@ -42,6 +42,7 @@ local scriptdir = core.get_builtin_path()
|
|||||||
local commonpath = scriptdir .. "common" .. DIR_DELIM
|
local commonpath = scriptdir .. "common" .. DIR_DELIM
|
||||||
local asyncpath = scriptdir .. "async" .. DIR_DELIM
|
local asyncpath = scriptdir .. "async" .. DIR_DELIM
|
||||||
|
|
||||||
|
dofile(commonpath .. "math.lua")
|
||||||
dofile(commonpath .. "vector.lua")
|
dofile(commonpath .. "vector.lua")
|
||||||
dofile(commonpath .. "strict.lua")
|
dofile(commonpath .. "strict.lua")
|
||||||
dofile(commonpath .. "serialize.lua")
|
dofile(commonpath .. "serialize.lua")
|
||||||
|
3
builtin/locale/__builtin.be.tr
Normal file
3
builtin/locale/__builtin.be.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Вы загінулі
|
||||||
|
Respawn=Адрадзіцца
|
3
builtin/locale/__builtin.bg.tr
Normal file
3
builtin/locale/__builtin.bg.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Умряхте
|
||||||
|
Respawn=Прераждане
|
3
builtin/locale/__builtin.ca.tr
Normal file
3
builtin/locale/__builtin.ca.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Has mort
|
||||||
|
Respawn=Reaparèixer
|
3
builtin/locale/__builtin.cs.tr
Normal file
3
builtin/locale/__builtin.cs.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Zemřel jsi
|
||||||
|
Respawn=Oživit
|
3
builtin/locale/__builtin.cy.tr
Normal file
3
builtin/locale/__builtin.cy.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Buest ti farw
|
||||||
|
Respawn=Atgyfodi
|
3
builtin/locale/__builtin.da.tr
Normal file
3
builtin/locale/__builtin.da.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Du døde
|
||||||
|
Respawn=Genopstå
|
@ -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“.
|
The output is limited to '@1'.=Die Ausgabe ist beschränkt auf „@1“.
|
||||||
Saving of profile failed: @1=Speichern des Profils fehlgeschlagen: @1
|
Saving of profile failed: @1=Speichern des Profils fehlgeschlagen: @1
|
||||||
Profile saved to @1=Profil abgespeichert nach @1
|
Profile saved to @1=Profil abgespeichert nach @1
|
||||||
|
You died=Sie sind gestorben
|
||||||
|
Respawn=Wiederbeleben
|
||||||
|
3
builtin/locale/__builtin.el.tr
Normal file
3
builtin/locale/__builtin.el.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Πέθανες
|
||||||
|
Respawn=Επανεμφάνηση
|
@ -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».
|
The output is limited to '@1'.=La eligo estas limigita al «@1».
|
||||||
Saving of profile failed: @1=Konservado de profilo malsukcesis: @1
|
Saving of profile failed: @1=Konservado de profilo malsukcesis: @1
|
||||||
Profile saved to @1=Profilo konservita al @1
|
Profile saved to @1=Profilo konservita al @1
|
||||||
|
You died=Vi mortis
|
||||||
|
Respawn=Renaskiĝi
|
||||||
|
3
builtin/locale/__builtin.es.tr
Normal file
3
builtin/locale/__builtin.es.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Has muerto
|
||||||
|
Respawn=Reaparecer
|
3
builtin/locale/__builtin.et.tr
Normal file
3
builtin/locale/__builtin.et.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Said surma
|
||||||
|
Respawn=Ärka ellu
|
3
builtin/locale/__builtin.eu.tr
Normal file
3
builtin/locale/__builtin.eu.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Hil zara
|
||||||
|
Respawn=Birsortu
|
3
builtin/locale/__builtin.fi.tr
Normal file
3
builtin/locale/__builtin.fi.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Kuolit
|
||||||
|
Respawn=Synny uudelleen
|
3
builtin/locale/__builtin.fil.tr
Normal file
3
builtin/locale/__builtin.fil.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Namatay ka
|
||||||
|
Respawn=Mag-respawn
|
@ -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'.
|
The output is limited to '@1'.=La sortie est limitée à '@1'.
|
||||||
Saving of profile failed: @1=La sauvegarde du profil a échoué : @1
|
Saving of profile failed: @1=La sauvegarde du profil a échoué : @1
|
||||||
Profile saved to @1=Le profil a été sauvegardé dans @1
|
Profile saved to @1=Le profil a été sauvegardé dans @1
|
||||||
|
You died=Vous êtes mort
|
||||||
|
Respawn=Réapparaître
|
||||||
|
3
builtin/locale/__builtin.ga.tr
Normal file
3
builtin/locale/__builtin.ga.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Fuair tú bás
|
||||||
|
Respawn=Athsceith
|
3
builtin/locale/__builtin.gl.tr
Normal file
3
builtin/locale/__builtin.gl.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Morreches
|
||||||
|
Respawn=Reaparecer
|
3
builtin/locale/__builtin.hu.tr
Normal file
3
builtin/locale/__builtin.hu.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Meghaltál
|
||||||
|
Respawn=Újraéledés
|
@ -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'.
|
The output is limited to '@1'.=Keluaran dibatasi ke '@1'.
|
||||||
Saving of profile failed: @1=Penyimpanan profil gagal: @1
|
Saving of profile failed: @1=Penyimpanan profil gagal: @1
|
||||||
Profile saved to @1=Profil disimpan ke @1
|
Profile saved to @1=Profil disimpan ke @1
|
||||||
|
You died=Anda mati
|
||||||
|
Respawn=Bangkit kembali
|
||||||
|
@ -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'.
|
The output is limited to '@1'.=L'output è limitato a '@1'.
|
||||||
Saving of profile failed: @1=Errore nel salvare il profilo: @1
|
Saving of profile failed: @1=Errore nel salvare il profilo: @1
|
||||||
Profile saved to @1=Profilo salvato in @1
|
Profile saved to @1=Profilo salvato in @1
|
||||||
|
You died=Sei morto
|
||||||
|
Respawn=Rinasci
|
||||||
|
3
builtin/locale/__builtin.ja.tr
Normal file
3
builtin/locale/__builtin.ja.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=死んでしまった
|
||||||
|
Respawn=リスポーン
|
3
builtin/locale/__builtin.jbo.tr
Normal file
3
builtin/locale/__builtin.jbo.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=.i do morsi
|
||||||
|
Respawn=tolcanci
|
3
builtin/locale/__builtin.jv.tr
Normal file
3
builtin/locale/__builtin.jv.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Panjenengan pejah
|
||||||
|
Respawn=Bangkit Malilh
|
3
builtin/locale/__builtin.ko.tr
Normal file
3
builtin/locale/__builtin.ko.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=사망했습니다
|
||||||
|
Respawn=리스폰
|
3
builtin/locale/__builtin.kv.tr
Normal file
3
builtin/locale/__builtin.kv.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Кулінныд
|
||||||
|
Respawn=Ловзьыны
|
3
builtin/locale/__builtin.ky.tr
Normal file
3
builtin/locale/__builtin.ky.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Сиз өлдүңүз.
|
||||||
|
Respawn=Кайтадан жаралуу
|
3
builtin/locale/__builtin.lt.tr
Normal file
3
builtin/locale/__builtin.lt.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Jūs numirėte
|
||||||
|
Respawn=Prisikelti
|
3
builtin/locale/__builtin.lv.tr
Normal file
3
builtin/locale/__builtin.lv.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Jūs nomirāt
|
||||||
|
Respawn=Atdzīvoties
|
3
builtin/locale/__builtin.lzh.tr
Normal file
3
builtin/locale/__builtin.lzh.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=尔死矣
|
||||||
|
Respawn=复生
|
3
builtin/locale/__builtin.mn.tr
Normal file
3
builtin/locale/__builtin.mn.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Та үхсэн
|
||||||
|
Respawn=Дахин төрөх
|
3
builtin/locale/__builtin.mr.tr
Normal file
3
builtin/locale/__builtin.mr.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=तू मेलास
|
||||||
|
Respawn=पुनर्जन्म
|
@ -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'.
|
The output is limited to '@1'.=Output dihadkan kepada '@1'.
|
||||||
Saving of profile failed: @1=Penyimpanan profil telah gagal: @1
|
Saving of profile failed: @1=Penyimpanan profil telah gagal: @1
|
||||||
Profile saved to @1=Profil telah disimpan ke @1
|
Profile saved to @1=Profil telah disimpan ke @1
|
||||||
|
You died=Anda telah meninggal
|
||||||
|
Respawn=Jelma semula
|
||||||
|
3
builtin/locale/__builtin.nb.tr
Normal file
3
builtin/locale/__builtin.nb.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Du døde
|
||||||
|
Respawn=Gjenoppstå
|
3
builtin/locale/__builtin.nl.tr
Normal file
3
builtin/locale/__builtin.nl.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Je bent gestorven
|
||||||
|
Respawn=Herboren worden
|
3
builtin/locale/__builtin.nn.tr
Normal file
3
builtin/locale/__builtin.nn.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Du døydde
|
||||||
|
Respawn=Kom opp att
|
3
builtin/locale/__builtin.oc.tr
Normal file
3
builtin/locale/__builtin.oc.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Setz mòrt·a
|
||||||
|
Respawn=Tornar
|
3
builtin/locale/__builtin.pl.tr
Normal file
3
builtin/locale/__builtin.pl.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Nie żyjesz
|
||||||
|
Respawn=Wróć do gry
|
3
builtin/locale/__builtin.pt.tr
Normal file
3
builtin/locale/__builtin.pt.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Você morreu
|
||||||
|
Respawn=Renascer
|
@ -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'.
|
The output is limited to '@1'.=A saída é limitada a '@1'.
|
||||||
Saving of profile failed: @1=Falha ao salvar o perfil: @1
|
Saving of profile failed: @1=Falha ao salvar o perfil: @1
|
||||||
Profile saved to @1=Perfil salvo em @1
|
Profile saved to @1=Perfil salvo em @1
|
||||||
|
You died=Você morreu
|
||||||
|
Respawn=Reviver
|
||||||
|
3
builtin/locale/__builtin.ro.tr
Normal file
3
builtin/locale/__builtin.ro.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Ai murit
|
||||||
|
Respawn=Reînviere
|
@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Всего было взято @1 образ
|
|||||||
The output is limited to '@1'.=Вывод ограничен значением '@1'.
|
The output is limited to '@1'.=Вывод ограничен значением '@1'.
|
||||||
Saving of profile failed: @1=Не удалось сохранить данные профилирования: @1
|
Saving of profile failed: @1=Не удалось сохранить данные профилирования: @1
|
||||||
Profile saved to @1=Данные профилирования сохранены в @1
|
Profile saved to @1=Данные профилирования сохранены в @1
|
||||||
|
You died=Вы умерли
|
||||||
|
Respawn=Возродиться
|
||||||
|
3
builtin/locale/__builtin.sk.tr
Normal file
3
builtin/locale/__builtin.sk.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Zomrel si
|
||||||
|
Respawn=Oživiť
|
3
builtin/locale/__builtin.sl.tr
Normal file
3
builtin/locale/__builtin.sl.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Umrl si
|
||||||
|
Respawn=Ponovno oživi
|
3
builtin/locale/__builtin.sr_Cyrl.tr
Normal file
3
builtin/locale/__builtin.sr_Cyrl.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Умро си
|
||||||
|
Respawn=Врати се у живот
|
3
builtin/locale/__builtin.sr_Latn.tr
Normal file
3
builtin/locale/__builtin.sr_Latn.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Umro/la si.
|
||||||
|
Respawn=Vrati se u zivot
|
3
builtin/locale/__builtin.sv.tr
Normal file
3
builtin/locale/__builtin.sv.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Du dog
|
||||||
|
Respawn=Återuppstå
|
3
builtin/locale/__builtin.sw.tr
Normal file
3
builtin/locale/__builtin.sw.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Umekufa.
|
||||||
|
Respawn=Respawn
|
3
builtin/locale/__builtin.tok.tr
Normal file
3
builtin/locale/__builtin.tok.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=sina moli
|
||||||
|
Respawn=o kama sin
|
3
builtin/locale/__builtin.tr.tr
Normal file
3
builtin/locale/__builtin.tr.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Öldün
|
||||||
|
Respawn=Yeniden Canlan
|
3
builtin/locale/__builtin.tt.tr
Normal file
3
builtin/locale/__builtin.tt.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Сез үлдегез
|
||||||
|
Respawn=Тергезелергә
|
3
builtin/locale/__builtin.uk.tr
Normal file
3
builtin/locale/__builtin.uk.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Ви загинули
|
||||||
|
Respawn=Відродитися
|
3
builtin/locale/__builtin.vi.tr
Normal file
3
builtin/locale/__builtin.vi.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=Bạn đã bị chết
|
||||||
|
Respawn=Hồi sinh
|
3
builtin/locale/__builtin.zh_CN.tr
Normal file
3
builtin/locale/__builtin.zh_CN.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=您已死亡
|
||||||
|
Respawn=重生
|
3
builtin/locale/__builtin.zh_TW.tr
Normal file
3
builtin/locale/__builtin.zh_TW.tr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# textdomain: __builtin
|
||||||
|
You died=您已死亡
|
||||||
|
Respawn=重生
|
@ -182,6 +182,23 @@ function contentdb.get_package_by_id(id)
|
|||||||
end
|
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).
|
-- Create a coroutine from `fn` and provide results to `callback` when complete (dead).
|
||||||
-- Returns a resumer function.
|
-- Returns a resumer function.
|
||||||
local function make_callback_coroutine(fn, callback)
|
local function make_callback_coroutine(fn, callback)
|
||||||
@ -415,15 +432,7 @@ local function fetch_pkgs(params)
|
|||||||
local aliases = {}
|
local aliases = {}
|
||||||
|
|
||||||
for _, package in pairs(packages) do
|
for _, package in pairs(packages) do
|
||||||
local name_len = #package.name
|
package.id = params.calculate_package_id(package.type, package.author, 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.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name)
|
package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name)
|
||||||
|
|
||||||
if package.aliases then
|
if package.aliases then
|
||||||
@ -443,7 +452,7 @@ end
|
|||||||
|
|
||||||
function contentdb.fetch_pkgs(callback)
|
function contentdb.fetch_pkgs(callback)
|
||||||
contentdb.loading = true
|
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
|
if result then
|
||||||
contentdb.load_ok = true
|
contentdb.load_ok = true
|
||||||
contentdb.load_error = false
|
contentdb.load_error = false
|
||||||
@ -581,3 +590,78 @@ function contentdb.filter_packages(query, by_type)
|
|||||||
end
|
end
|
||||||
end
|
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
|
||||||
|
@ -26,68 +26,20 @@ end
|
|||||||
-- Filter
|
-- Filter
|
||||||
local search_string = ""
|
local search_string = ""
|
||||||
local cur_page = 1
|
local cur_page = 1
|
||||||
local num_per_page = 5
|
local filter_type
|
||||||
local filter_type = 1
|
|
||||||
local filter_types_titles = {
|
|
||||||
fgettext("All packages"),
|
|
||||||
fgettext("Games"),
|
|
||||||
fgettext("Mods"),
|
|
||||||
fgettext("Texture packs"),
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Automatic package installation
|
-- Automatic package installation
|
||||||
local auto_install_spec = nil
|
local auto_install_spec = nil
|
||||||
|
|
||||||
local filter_types_type = {
|
|
||||||
nil,
|
local filter_type_names = {
|
||||||
"game",
|
{ "type_all", nil },
|
||||||
"mod",
|
{ "type_game", "game" },
|
||||||
"txp",
|
{ "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.
|
-- 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.
|
-- May only be called after the package list has been loaded successfully.
|
||||||
local function resolve_auto_install_spec()
|
local function resolve_auto_install_spec()
|
||||||
@ -145,7 +97,7 @@ end
|
|||||||
local function sort_and_filter_pkgs()
|
local function sort_and_filter_pkgs()
|
||||||
contentdb.update_paths()
|
contentdb.update_paths()
|
||||||
contentdb.sort_packages()
|
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()
|
local auto_install_pkg = resolve_auto_install_spec()
|
||||||
if auto_install_pkg then
|
if auto_install_pkg then
|
||||||
@ -176,72 +128,151 @@ local function load()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_info_formspec(text)
|
local function get_info_formspec(size, padding, text)
|
||||||
local H = 9.5
|
|
||||||
return table.concat({
|
return table.concat({
|
||||||
"formspec_version[6]",
|
"formspec_version[6]",
|
||||||
"size[15.75,9.5]",
|
"size[", size.x, ",", size.y, "]",
|
||||||
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
|
"padding[0,0]",
|
||||||
|
"bgcolor[;true]",
|
||||||
|
|
||||||
"label[4,4.35;", text, "]",
|
"label[", padding.x + 3.625, ",4.35;", text, "]",
|
||||||
"container[0,", H - 0.8 - 0.375, "]",
|
"container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
|
||||||
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
|
"button[0,0;2,0.8;back;", fgettext("Back"), "]",
|
||||||
"container_end[]",
|
"container_end[]",
|
||||||
})
|
})
|
||||||
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 function get_formspec(dlgdata)
|
||||||
|
local window_padding = contentdb.get_formspec_padding()
|
||||||
|
local size = contentdb.get_formspec_size()
|
||||||
|
|
||||||
if contentdb.loading then
|
if contentdb.loading then
|
||||||
return get_info_formspec(fgettext("Loading..."))
|
return get_info_formspec(size, window_padding, fgettext("Loading..."))
|
||||||
end
|
end
|
||||||
if contentdb.load_error then
|
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
|
end
|
||||||
assert(contentdb.load_ok)
|
assert(contentdb.load_ok)
|
||||||
|
|
||||||
contentdb.update_paths()
|
contentdb.update_paths()
|
||||||
|
|
||||||
|
local num_per_page = dlgdata.num_per_page
|
||||||
dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1)
|
dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1)
|
||||||
if cur_page > dlgdata.pagemax then
|
if cur_page > dlgdata.pagemax then
|
||||||
cur_page = 1
|
cur_page = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
local W = 15.75
|
local W = size.x - window_padding.x * 2
|
||||||
local H = 9.5
|
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 = {
|
local formspec = {
|
||||||
"formspec_version[6]",
|
"formspec_version[7]",
|
||||||
"size[15.75,9.5]",
|
"size[", size.x, ",", size.y, "]",
|
||||||
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
|
"padding[0,0]",
|
||||||
|
"bgcolor[;true]",
|
||||||
|
|
||||||
"style[status,downloading,queued;border=false]",
|
"container[", window_padding.x, ",", window_padding.y, "]",
|
||||||
|
|
||||||
"container[0.375,0.375]",
|
-- Top-left: categories
|
||||||
"field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
|
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]",
|
"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[", search_box_width, ",0;0.8,0.8;",
|
||||||
"image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
|
core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
|
||||||
"dropdown[9.175,0;2.7875,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
|
"image_button[", search_box_width + 0.8, ",0;0.8,0.8;",
|
||||||
|
core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
|
||||||
"container_end[]",
|
"container_end[]",
|
||||||
|
|
||||||
-- Page nav buttons
|
-- Bottom strip start
|
||||||
"container[0,", H - 0.8 - 0.375, "]",
|
"container[0,", H - 0.8, "]",
|
||||||
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
|
"button[0,0;2,0.8;back;", fgettext("Back"), "]",
|
||||||
|
|
||||||
"container[", W - 0.375 - 0.8*4 - 2, ",0]",
|
-- Bottom-center: Page nav buttons
|
||||||
"image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
|
"container[", (W - 1*4 - 2) / 2, ",0]",
|
||||||
"image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
|
"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]",
|
"style[pagenum;border=false]",
|
||||||
"button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
|
"button[2,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,0;1,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;]",
|
"image_button[5,0;1,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
|
||||||
"container_end[]",
|
"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
|
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
|
if #contentdb.download_queue > 0 then
|
||||||
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued",
|
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued",
|
||||||
contentdb.number_downloading, #contentdb.download_queue)
|
contentdb.number_downloading, #contentdb.download_queue)
|
||||||
@ -260,16 +291,19 @@ local function get_formspec(dlgdata)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if num_avail_updates == 0 then
|
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] = fgettext("No updates")
|
||||||
formspec[#formspec + 1] = "]"
|
formspec[#formspec + 1] = "]"
|
||||||
else
|
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] = fgettext("Update All [$1]", num_avail_updates)
|
||||||
formspec[#formspec + 1] = "]"
|
formspec[#formspec + 1] = "]"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
formspec[#formspec + 1] = "container_end[]" -- updating end
|
||||||
|
formspec[#formspec + 1] = "container_end[]" -- bottom strip end
|
||||||
|
|
||||||
if #contentdb.packages == 0 then
|
if #contentdb.packages == 0 then
|
||||||
formspec[#formspec + 1] = "label[4,4.75;"
|
formspec[#formspec + 1] = "label[4,4.75;"
|
||||||
formspec[#formspec + 1] = fgettext("No results")
|
formspec[#formspec + 1] = fgettext("No results")
|
||||||
@ -281,80 +315,84 @@ local function get_formspec(dlgdata)
|
|||||||
formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors
|
formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors
|
||||||
formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. 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
|
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
|
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
|
||||||
local package = contentdb.packages[i]
|
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
|
table.insert_all(formspec, {
|
||||||
formspec[#formspec + 1] = "image[0,0;1.5,1;"
|
"container[",
|
||||||
formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
|
(cell_w + cell_spacing) * ((i - start_idx) % columns),
|
||||||
formspec[#formspec + 1] = "]"
|
",",
|
||||||
|
(cell_h + cell_spacing) * math.floor((i - start_idx) / columns),
|
||||||
|
"]",
|
||||||
|
|
||||||
-- title
|
"box[0,0;", cell_w, ",", cell_h, ";#ffffff11]",
|
||||||
formspec[#formspec + 1] = "label[1.875,0.1;"
|
|
||||||
formspec[#formspec + 1] = core.formspec_escape(
|
-- image,
|
||||||
|
"image[0,0;", img_w, ",", cell_h, ";",
|
||||||
|
core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]",
|
||||||
|
|
||||||
|
"label[", img_w + 0.25 + 0.05, ",0.5;",
|
||||||
|
core.formspec_escape(
|
||||||
core.colorize(mt_color_green, package.title) ..
|
core.colorize(mt_color_green, package.title) ..
|
||||||
core.colorize("#BFBFBF", " by " .. package.author))
|
core.colorize("#BFBFBF", " by " .. package.author)), "]",
|
||||||
formspec[#formspec + 1] = "]"
|
|
||||||
|
|
||||||
-- buttons
|
"textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;",
|
||||||
local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
|
core.formspec_escape(package.short_description), "]",
|
||||||
|
|
||||||
local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
|
"style[view_", i, ";border=false]",
|
||||||
local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
|
"style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]",
|
||||||
formspec[#formspec + 1] = "container["
|
"style[view_", i, ":pressed;bgimg=", core.formspec_escape(defaulttexturedir .. "button_press_semitrans.png"), "]",
|
||||||
formspec[#formspec + 1] = W - 0.375*2
|
"button[0,0;", cell_w, ",", cell_h, ";view_", i, ";]",
|
||||||
formspec[#formspec + 1] = ",0.1]"
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
table.insert_all(formspec, {
|
||||||
|
"container[", cell_w - 0.625,",", 0.25, "]",
|
||||||
|
})
|
||||||
|
|
||||||
if package.downloading then
|
if package.downloading then
|
||||||
formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;"
|
table.insert_all(formspec, {
|
||||||
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
|
"animated_image[0,0;0.5,0.5;downloading;", defaulttexturedir, "cdb_downloading.png;3;400;;]",
|
||||||
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
|
})
|
||||||
elseif package.queued then
|
elseif package.queued then
|
||||||
formspec[#formspec + 1] = second_base
|
table.insert_all(formspec, {
|
||||||
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
|
"image[0,0;0.5,0.5;", defaulttexturedir, "cdb_queued.png]",
|
||||||
elseif not package.path then
|
})
|
||||||
local elem_name = "install_" .. i .. ";"
|
elseif package.path then
|
||||||
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
|
if package.installed_release < package.release then
|
||||||
-- The install_ action also handles updating
|
table.insert_all(formspec, {
|
||||||
local elem_name = "install_" .. i .. ";"
|
"image[0,0;0.5,0.5;", defaulttexturedir, "cdb_update.png]",
|
||||||
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
|
})
|
||||||
formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]"
|
else
|
||||||
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
|
table.insert_all(formspec, {
|
||||||
|
"image[0.1,0.1;0.3,0.3;", defaulttexturedir, "checkbox_64.png]",
|
||||||
description_width = description_width - 0.7 - 0.15
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local elem_name = "uninstall_" .. i .. ";"
|
table.insert_all(formspec, {
|
||||||
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
|
"container_end[]",
|
||||||
formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]"
|
"container_end[]",
|
||||||
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
|
})
|
||||||
end
|
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[]"
|
|
||||||
|
|
||||||
-- 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] = "]"
|
|
||||||
|
|
||||||
formspec[#formspec + 1] = "container_end[]"
|
formspec[#formspec + 1] = "container_end[]"
|
||||||
end
|
formspec[#formspec + 1] = "container_end[]"
|
||||||
|
|
||||||
return table.concat(formspec)
|
return table.concat(formspec)
|
||||||
end
|
end
|
||||||
@ -364,14 +402,14 @@ local function handle_submit(this, fields)
|
|||||||
if fields.search or fields.key_enter_field == "search_string" then
|
if fields.search or fields.key_enter_field == "search_string" then
|
||||||
search_string = fields.search_string:trim()
|
search_string = fields.search_string:trim()
|
||||||
cur_page = 1
|
cur_page = 1
|
||||||
contentdb.filter_packages(search_string, filter_types_type[filter_type])
|
contentdb.filter_packages(search_string, filter_type)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
if fields.clear then
|
if fields.clear then
|
||||||
search_string = ""
|
search_string = ""
|
||||||
cur_page = 1
|
cur_page = 1
|
||||||
contentdb.filter_packages("", filter_types_type[filter_type])
|
contentdb.filter_packages("", filter_type)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -407,12 +445,11 @@ local function handle_submit(this, fields)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
if fields.type then
|
for _, pair in ipairs(filter_type_names) do
|
||||||
local new_type = table.indexof(filter_types_titles, fields.type)
|
if fields[pair[1]] then
|
||||||
if new_type ~= filter_type then
|
filter_type = pair[2]
|
||||||
filter_type = new_type
|
|
||||||
cur_page = 1
|
cur_page = 1
|
||||||
contentdb.filter_packages(search_string, filter_types_type[filter_type])
|
contentdb.filter_packages(search_string, filter_type)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -428,32 +465,20 @@ local function handle_submit(this, fields)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local num_per_page = this.data.num_per_page
|
||||||
local start_idx = (cur_page - 1) * num_per_page + 1
|
local start_idx = (cur_page - 1) * num_per_page + 1
|
||||||
assert(start_idx ~= nil)
|
assert(start_idx ~= nil)
|
||||||
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
|
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
|
||||||
local package = contentdb.packages[i]
|
local package = contentdb.packages[i]
|
||||||
assert(package)
|
assert(package)
|
||||||
|
|
||||||
if fields["install_" .. i] then
|
if fields["view_" .. i] or fields["title_" .. i] or fields["author_" .. i] then
|
||||||
install_or_update_package(this, package)
|
local dlg = create_package_dialog(package)
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if fields["uninstall_" .. i] then
|
|
||||||
local dlg = create_delete_content_dlg(package)
|
|
||||||
dlg:set_parent(this)
|
dlg:set_parent(this)
|
||||||
this:hide()
|
this:hide()
|
||||||
dlg:show()
|
dlg:show()
|
||||||
return true
|
return true
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@ -462,8 +487,8 @@ end
|
|||||||
|
|
||||||
local function handle_events(event)
|
local function handle_events(event)
|
||||||
if event == "DialogShow" then
|
if event == "DialogShow" then
|
||||||
-- On touchscreen, don't show the "MINETEST" header behind the dialog.
|
-- Don't show the "MINETEST" header behind the dialog.
|
||||||
mm_game_theme.set_engine(core.settings:get_bool("enable_touch"))
|
mm_game_theme.set_engine(true)
|
||||||
|
|
||||||
-- If ContentDB is already loaded, auto-install packages here.
|
-- If ContentDB is already loaded, auto-install packages here.
|
||||||
do_auto_install()
|
do_auto_install()
|
||||||
@ -471,6 +496,11 @@ local function handle_events(event)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if event == "WindowInfoChange" then
|
||||||
|
ui.update()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -485,17 +515,7 @@ end
|
|||||||
function create_contentdb_dlg(type, install_spec)
|
function create_contentdb_dlg(type, install_spec)
|
||||||
search_string = ""
|
search_string = ""
|
||||||
cur_page = 1
|
cur_page = 1
|
||||||
if type then
|
filter_type = type
|
||||||
-- 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
|
|
||||||
|
|
||||||
-- Keep the old auto_install_spec if the caller doesn't specify one.
|
-- Keep the old auto_install_spec if the caller doesn't specify one.
|
||||||
if install_spec then
|
if install_spec then
|
||||||
@ -504,8 +524,10 @@ function create_contentdb_dlg(type, install_spec)
|
|||||||
|
|
||||||
load()
|
load()
|
||||||
|
|
||||||
return dialog_create("contentdb",
|
local dlg = dialog_create("contentdb",
|
||||||
get_formspec,
|
get_formspec,
|
||||||
handle_submit,
|
handle_submit,
|
||||||
handle_events)
|
handle_events)
|
||||||
|
dlg.data.num_per_page = calculate_num_per_page()
|
||||||
|
return dlg
|
||||||
end
|
end
|
||||||
|
@ -22,13 +22,13 @@ end
|
|||||||
|
|
||||||
|
|
||||||
local function get_loading_formspec()
|
local function get_loading_formspec()
|
||||||
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 formspec = {
|
local formspec = {
|
||||||
"formspec_version[3]",
|
"formspec_version[3]",
|
||||||
"size[", w, ",9.05]",
|
"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..."), "]",
|
"label[3,4.525;", fgettext("Loading..."), "]",
|
||||||
}
|
}
|
||||||
return table.concat(formspec)
|
return table.concat(formspec)
|
||||||
@ -110,18 +110,18 @@ local function get_formspec(data)
|
|||||||
message_bg = mt_color_orange
|
message_bg = mt_color_orange
|
||||||
end
|
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 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_w = (padded_w - 0.25) / 3
|
||||||
local button_pad = button_w / 2
|
local button_pad = button_w / 2
|
||||||
|
|
||||||
local formspec = {
|
local formspec = {
|
||||||
"formspec_version[3]",
|
"formspec_version[3]",
|
||||||
"size[", w, ",9.05]",
|
"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]",
|
"style[title;border=false]",
|
||||||
"box[0,0;", w, ",0.8;#3333]",
|
"box[0,0;", w, ",0.8;#3333]",
|
||||||
"button[0,0;", w, ",0.8;title;", fgettext("Install $1", package.title) , "]",
|
"button[0,0;", w, ",0.8;title;", fgettext("Install $1", package.title) , "]",
|
||||||
@ -244,3 +244,45 @@ function create_install_dialog(package)
|
|||||||
|
|
||||||
return dlg
|
return dlg
|
||||||
end
|
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
|
||||||
|
333
builtin/mainmenu/content/dlg_package.lua
Normal file
333
builtin/mainmenu/content/dlg_package.lua
Normal 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
|
@ -23,4 +23,5 @@ dofile(path .. DIR_DELIM .. "update_detector.lua")
|
|||||||
dofile(path .. DIR_DELIM .. "screenshots.lua")
|
dofile(path .. DIR_DELIM .. "screenshots.lua")
|
||||||
dofile(path .. DIR_DELIM .. "dlg_install.lua")
|
dofile(path .. DIR_DELIM .. "dlg_install.lua")
|
||||||
dofile(path .. DIR_DELIM .. "dlg_overwrite.lua")
|
dofile(path .. DIR_DELIM .. "dlg_overwrite.lua")
|
||||||
|
dofile(path .. DIR_DELIM .. "dlg_package.lua")
|
||||||
dofile(path .. DIR_DELIM .. "dlg_contentdb.lua")
|
dofile(path .. DIR_DELIM .. "dlg_contentdb.lua")
|
||||||
|
@ -23,23 +23,40 @@ local screenshot_downloading = {}
|
|||||||
local screenshot_downloaded = {}
|
local screenshot_downloaded = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function get_filename(path)
|
||||||
|
local parts = path:split("/")
|
||||||
|
return parts[#parts]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_file_extension(path)
|
local function get_file_extension(path)
|
||||||
local parts = path:split(".")
|
local parts = path:split(".")
|
||||||
return parts[#parts]
|
return parts[#parts]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function get_screenshot(package)
|
function get_screenshot(package, screenshot_url, level)
|
||||||
if not package.thumbnail then
|
if not screenshot_url then
|
||||||
return defaulttexturedir .. "no_screenshot.png"
|
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"
|
return defaulttexturedir .. "loading_screenshot.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get tmp screenshot path
|
|
||||||
local ext = get_file_extension(package.thumbnail)
|
|
||||||
local filepath = screenshot_dir .. DIR_DELIM ..
|
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
|
-- Return if already downloaded
|
||||||
local file = io.open(filepath, "r")
|
local file = io.open(filepath, "r")
|
||||||
@ -49,7 +66,7 @@ function get_screenshot(package)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Show error if we've failed to download before
|
-- 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"
|
return defaulttexturedir .. "error_screenshot.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -59,16 +76,16 @@ function get_screenshot(package)
|
|||||||
return core.download_file(params.url, params.dest)
|
return core.download_file(params.url, params.dest)
|
||||||
end
|
end
|
||||||
local function callback(success)
|
local function callback(success)
|
||||||
screenshot_downloading[package.thumbnail] = nil
|
screenshot_downloading[screenshot_url] = nil
|
||||||
screenshot_downloaded[package.thumbnail] = true
|
screenshot_downloaded[screenshot_url] = true
|
||||||
if not success then
|
if not success then
|
||||||
core.log("warning", "Screenshot download failed for some reason")
|
core.log("warning", "Screenshot download failed for some reason")
|
||||||
end
|
end
|
||||||
ui.update()
|
ui.update()
|
||||||
end
|
end
|
||||||
if core.handle_async(download_screenshot,
|
if core.handle_async(download_screenshot,
|
||||||
{ dest = filepath, url = package.thumbnail }, callback) then
|
{ dest = filepath, url = screenshot_url }, callback) then
|
||||||
screenshot_downloading[package.thumbnail] = true
|
screenshot_downloading[screenshot_url] = true
|
||||||
else
|
else
|
||||||
core.log("error", "ERROR: async event failed")
|
core.log("error", "ERROR: async event failed")
|
||||||
return defaulttexturedir .. "error_screenshot.png"
|
return defaulttexturedir .. "error_screenshot.png"
|
||||||
|
85
builtin/mainmenu/credits.json
Normal file
85
builtin/mainmenu/credits.json
Normal 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>"
|
||||||
|
]
|
||||||
|
}
|
@ -126,7 +126,7 @@ local function get_formspec(data)
|
|||||||
local retval =
|
local retval =
|
||||||
"size[11.5,7.5,true]" ..
|
"size[11.5,7.5,true]" ..
|
||||||
"label[0.5,0;" .. fgettext("World:") .. "]" ..
|
"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
|
if mod.is_modpack or mod.type == "game" then
|
||||||
local info = core.formspec_escape(
|
local info = core.formspec_escape(
|
||||||
|
@ -23,6 +23,13 @@ mt_color_dark_green = "#25C191"
|
|||||||
mt_color_orange = "#FF8800"
|
mt_color_orange = "#FF8800"
|
||||||
mt_color_red = "#FF3300"
|
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 menupath = core.get_mainmenu_path()
|
||||||
local basepath = core.get_builtin_path()
|
local basepath = core.get_builtin_path()
|
||||||
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
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.
|
mm_game_theme.set_engine() -- This is just a fallback.
|
||||||
|
|
||||||
-- Create main tabview
|
-- 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:set_autosave_tab(true)
|
||||||
tv_main:add(tabs.local_game)
|
tv_main:add(tabs.local_game)
|
||||||
|
@ -67,6 +67,19 @@ function make.heading(text)
|
|||||||
end
|
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
|
--- Used for string and numeric style fields
|
||||||
---
|
---
|
||||||
--- @param converter Function to coerce values from strings.
|
--- @param converter Function to coerce values from strings.
|
||||||
|
@ -110,7 +110,7 @@ local function load()
|
|||||||
local change_keys = {
|
local change_keys = {
|
||||||
query_text = "Controls",
|
query_text = "Controls",
|
||||||
requires = {
|
requires = {
|
||||||
keyboard_mouse = true,
|
touch_controls = false,
|
||||||
},
|
},
|
||||||
get_formspec = function(self, avail_w)
|
get_formspec = function(self, avail_w)
|
||||||
local btn_w = math.min(avail_w, 3)
|
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)
|
table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys)
|
||||||
do
|
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")
|
local idx = table.indexof(content, "enable_dynamic_shadows")
|
||||||
table.insert(content, idx, shadows_component)
|
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
|
end
|
||||||
|
|
||||||
-- These must not be translated, as they need to show in the local
|
-- 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_CN = "中文 (简体) [zh_CN]",
|
||||||
zh_TW = "正體中文 (繁體) [zh_TW]",
|
zh_TW = "正體中文 (繁體) [zh_TW]",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_setting_info("touch_controls").option_labels = {
|
||||||
|
["auto"] = fgettext_ne("Auto"),
|
||||||
|
["true"] = fgettext_ne("Enabled"),
|
||||||
|
["false"] = fgettext_ne("Disabled"),
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -321,11 +342,14 @@ local function check_requirements(name, requires)
|
|||||||
|
|
||||||
local video_driver = core.get_active_driver()
|
local video_driver = core.get_active_driver()
|
||||||
local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2"
|
local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2"
|
||||||
|
local touch_controls = core.settings:get("touch_controls")
|
||||||
local special = {
|
local special = {
|
||||||
android = PLATFORM == "Android",
|
android = PLATFORM == "Android",
|
||||||
desktop = PLATFORM ~= "Android",
|
desktop = PLATFORM ~= "Android",
|
||||||
touchscreen_gui = core.settings:get_bool("enable_touch"),
|
-- When touch_controls is "auto", we don't which input method will be used,
|
||||||
keyboard_mouse = not core.settings:get_bool("enable_touch"),
|
-- 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_support = shaders_support,
|
||||||
shaders = core.settings:get_bool("enable_shaders") and shaders_support,
|
shaders = core.settings:get_bool("enable_shaders") and shaders_support,
|
||||||
opengl = video_driver == "opengl",
|
opengl = video_driver == "opengl",
|
||||||
@ -435,19 +459,6 @@ local function build_page_components(page)
|
|||||||
end
|
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
|
local formspec_show_hack = false
|
||||||
|
|
||||||
|
|
||||||
@ -457,13 +468,13 @@ local function get_formspec(dialogdata)
|
|||||||
|
|
||||||
local extra_h = 1 -- not included in tabsize.height
|
local extra_h = 1 -- not included in tabsize.height
|
||||||
local tabsize = {
|
local tabsize = {
|
||||||
width = core.settings:get_bool("enable_touch") and 16.5 or 15.5,
|
width = core.settings:get_bool("touch_gui") and 16.5 or 15.5,
|
||||||
height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12,
|
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 left_pane_padding = 0.25
|
||||||
local search_width = left_pane_width + scrollbar_w - (0.75 * 2)
|
local search_width = left_pane_width + scrollbar_w - (0.75 * 2)
|
||||||
|
|
||||||
@ -477,7 +488,7 @@ local function get_formspec(dialogdata)
|
|||||||
local fs = {
|
local fs = {
|
||||||
"formspec_version[6]",
|
"formspec_version[6]",
|
||||||
"size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]",
|
"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]",
|
"bgcolor[#0000]",
|
||||||
|
|
||||||
-- HACK: this is needed to allow resubmitting the same formspec
|
-- 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;", fgettext("Search"), "]",
|
||||||
"tooltip[search_clear;", fgettext("Clear"), "]",
|
"tooltip[search_clear;", fgettext("Clear"), "]",
|
||||||
"container_end[]",
|
"container_end[]",
|
||||||
"scroll_container[0.25,1.25;", tostring(left_pane_width), ",",
|
("scroll_container[0.25,1.25;%f,%f;leftscroll;vertical;0.1;0]"):format(
|
||||||
tostring(tabsize.height - 1.5), ";leftscroll;vertical;0.1]",
|
left_pane_width, tabsize.height - 1.5),
|
||||||
"style_type[button;border=false;bgcolor=#3333]",
|
"style_type[button;border=false;bgcolor=#3333]",
|
||||||
"style_type[button:hover;border=false;bgcolor=#6663]",
|
"style_type[button:hover;border=false;bgcolor=#6663]",
|
||||||
}
|
}
|
||||||
@ -540,7 +551,6 @@ local function get_formspec(dialogdata)
|
|||||||
fs[#fs + 1] = "scroll_container_end[]"
|
fs[#fs + 1] = "scroll_container_end[]"
|
||||||
|
|
||||||
if y >= tabsize.height - 1.25 then
|
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(
|
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)
|
left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0)
|
||||||
end
|
end
|
||||||
@ -552,7 +562,7 @@ local function get_formspec(dialogdata)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25
|
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)
|
tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height)
|
||||||
|
|
||||||
y = 0.25
|
y = 0.25
|
||||||
@ -608,7 +618,6 @@ local function get_formspec(dialogdata)
|
|||||||
fs[#fs + 1] = "scroll_container_end[]"
|
fs[#fs + 1] = "scroll_container_end[]"
|
||||||
|
|
||||||
if y >= tabsize.height then
|
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(
|
fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format(
|
||||||
tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0)
|
tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0)
|
||||||
end
|
end
|
||||||
@ -626,6 +635,18 @@ function write_settings_early()
|
|||||||
end
|
end
|
||||||
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 function buttonhandler(this, fields)
|
||||||
local dialogdata = this.data
|
local dialogdata = this.data
|
||||||
@ -650,27 +671,7 @@ local function buttonhandler(this, fields)
|
|||||||
local value = core.is_yes(fields.show_advanced)
|
local value = core.is_yes(fields.show_advanced)
|
||||||
core.settings:set_bool("show_advanced", value)
|
core.settings:set_bool("show_advanced", value)
|
||||||
write_settings_early()
|
write_settings_early()
|
||||||
end
|
regenerate_page_list(dialogdata)
|
||||||
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@ -703,20 +704,26 @@ local function buttonhandler(this, fields)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for i, comp in ipairs(dialogdata.components) do
|
local function after_setting_change(comp)
|
||||||
if comp.on_submit and comp:on_submit(fields, this) then
|
|
||||||
write_settings_early()
|
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
|
-- Clear components so they regenerate
|
||||||
dialogdata.components = nil
|
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
|
return true
|
||||||
end
|
end
|
||||||
if comp.setting and fields["reset_" .. i] then
|
if comp.setting and fields["reset_" .. i] then
|
||||||
core.settings:remove(comp.setting.name)
|
core.settings:remove(comp.setting.name)
|
||||||
write_settings_early()
|
after_setting_change(comp)
|
||||||
|
|
||||||
-- Clear components so they regenerate
|
|
||||||
dialogdata.components = nil
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -15,111 +15,23 @@
|
|||||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
-- https://github.com/orgs/minetest/teams/engine/members
|
|
||||||
|
|
||||||
local 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)",
|
|
||||||
}
|
|
||||||
|
|
||||||
-- currently only https://github.com/orgs/minetest/teams/triagers/members
|
|
||||||
|
|
||||||
local 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
|
|
||||||
|
|
||||||
local active_contributors = {
|
|
||||||
"cx384",
|
|
||||||
"numzero",
|
|
||||||
"AFCMS",
|
|
||||||
"sfence",
|
|
||||||
"Wuzzy",
|
|
||||||
"ROllerozxa",
|
|
||||||
"JosiahWI",
|
|
||||||
"OgelGames",
|
|
||||||
"David Heidelberg",
|
|
||||||
"1F616EMO",
|
|
||||||
"HybridDog",
|
|
||||||
"Bradley Pierce (Thresher)",
|
|
||||||
"savilli",
|
|
||||||
"Stvk imension",
|
|
||||||
"y5nw",
|
|
||||||
"chmodsayshello",
|
|
||||||
"jordan4ibanez",
|
|
||||||
"superfloh247",
|
|
||||||
}
|
|
||||||
|
|
||||||
local 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>",
|
|
||||||
}
|
|
||||||
|
|
||||||
local 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>",
|
|
||||||
}
|
|
||||||
|
|
||||||
local function prepare_credits(dest, source)
|
local function prepare_credits(dest, source)
|
||||||
local string = table.concat(source, "\n") .. "\n"
|
local string = table.concat(source, "\n") .. "\n"
|
||||||
|
|
||||||
local hypertext_escapes = {
|
string = core.hypertext_escape(string)
|
||||||
["\\"] = "\\\\",
|
|
||||||
["<"] = "\\<",
|
|
||||||
[">"] = "\\>",
|
|
||||||
}
|
|
||||||
string = string:gsub("[\\<>]", hypertext_escapes)
|
|
||||||
string = string:gsub("%[.-%]", "<gray>%1</gray>")
|
string = string:gsub("%[.-%]", "<gray>%1</gray>")
|
||||||
|
|
||||||
table.insert(dest, string)
|
table.insert(dest, string)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function get_credits()
|
||||||
|
local f = assert(io.open(core.get_mainmenu_path() .. "/credits.json"))
|
||||||
|
local json = core.parse_json(f:read("*all"))
|
||||||
|
f:close()
|
||||||
|
return json
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name = "about",
|
name = "about",
|
||||||
caption = fgettext("About"),
|
caption = fgettext("About"),
|
||||||
@ -133,30 +45,32 @@ return {
|
|||||||
"<tag name=gray color=#aaa>",
|
"<tag name=gray color=#aaa>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local credits = get_credits()
|
||||||
|
|
||||||
table.insert_all(hypertext, {
|
table.insert_all(hypertext, {
|
||||||
"<heading>", fgettext_ne("Core Developers"), "</heading>\n",
|
"<heading>", fgettext_ne("Core Developers"), "</heading>\n",
|
||||||
})
|
})
|
||||||
prepare_credits(hypertext, core_developers)
|
prepare_credits(hypertext, credits.core_developers)
|
||||||
table.insert_all(hypertext, {
|
table.insert_all(hypertext, {
|
||||||
"\n",
|
"\n",
|
||||||
"<heading>", fgettext_ne("Core Team"), "</heading>\n",
|
"<heading>", fgettext_ne("Core Team"), "</heading>\n",
|
||||||
})
|
})
|
||||||
prepare_credits(hypertext, core_team)
|
prepare_credits(hypertext, credits.core_team)
|
||||||
table.insert_all(hypertext, {
|
table.insert_all(hypertext, {
|
||||||
"\n",
|
"\n",
|
||||||
"<heading>", fgettext_ne("Active Contributors"), "</heading>\n",
|
"<heading>", fgettext_ne("Active Contributors"), "</heading>\n",
|
||||||
})
|
})
|
||||||
prepare_credits(hypertext, active_contributors)
|
prepare_credits(hypertext, credits.contributors)
|
||||||
table.insert_all(hypertext, {
|
table.insert_all(hypertext, {
|
||||||
"\n",
|
"\n",
|
||||||
"<heading>", fgettext_ne("Previous Core Developers"), "</heading>\n",
|
"<heading>", fgettext_ne("Previous Core Developers"), "</heading>\n",
|
||||||
})
|
})
|
||||||
prepare_credits(hypertext, previous_core_developers)
|
prepare_credits(hypertext, credits.previous_core_developers)
|
||||||
table.insert_all(hypertext, {
|
table.insert_all(hypertext, {
|
||||||
"\n",
|
"\n",
|
||||||
"<heading>", fgettext_ne("Previous Contributors"), "</heading>\n",
|
"<heading>", fgettext_ne("Previous Contributors"), "</heading>\n",
|
||||||
})
|
})
|
||||||
prepare_credits(hypertext, previous_contributors)
|
prepare_credits(hypertext, credits.previous_contributors)
|
||||||
|
|
||||||
hypertext = table.concat(hypertext):sub(1, -2)
|
hypertext = table.concat(hypertext):sub(1, -2)
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user