Compare commits
412 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
587f6656a4 | ||
|
|
4377c03168 | ||
|
|
1e0520074c | ||
|
|
b9f6832347 | ||
|
|
f8bb0cd3d1 | ||
|
|
129aef758e | ||
|
|
94f55cf406 | ||
|
|
a20b758e19 | ||
|
|
128842becf | ||
|
|
d51331f51f | ||
|
|
4ef221a645 | ||
|
|
79010e972e | ||
|
|
0ca530e251 | ||
|
|
57b4d46dbc | ||
|
|
50df5e2f59 | ||
|
|
b6db2c7262 | ||
|
|
9441b69ad2 | ||
|
|
08e3d16a58 | ||
|
|
3f3049fdba | ||
|
|
41fb7a8a7e | ||
|
|
5b9828e094 | ||
|
|
96e35585b0 | ||
|
|
f035fe9336 | ||
|
|
53dd648c96 | ||
|
|
c4277877b6 | ||
|
|
eb49b6d85c | ||
|
|
b91063daef | ||
|
|
7c14b434e0 | ||
|
|
644f145ff2 | ||
|
|
a81259d19a | ||
|
|
f22d40975e | ||
|
|
4c1ef1b72b | ||
|
|
6ec6acc539 | ||
|
|
839600ed70 | ||
|
|
a2bf3a2aa8 | ||
|
|
9542ab5efb | ||
|
|
68a273102f | ||
|
|
13bb815b32 | ||
|
|
ccb982513a | ||
|
|
ef6ca6319b | ||
|
|
5192ce4330 | ||
|
|
62624722d8 | ||
|
|
b2d82125ec | ||
|
|
e80f695bb9 | ||
|
|
d9d38d2172 | ||
|
|
84e939193e | ||
|
|
56811c63d1 | ||
|
|
a4b1e0d124 | ||
|
|
70b71c5013 | ||
|
|
95d7fcb949 | ||
|
|
de509d05e6 | ||
|
|
c14b7536a4 | ||
|
|
a871115889 | ||
|
|
6a269d58ef | ||
|
|
3b37bcd994 | ||
|
|
99c8295e71 | ||
|
|
6611d7e1ee | ||
|
|
f0703f3c5b | ||
|
|
7e78c0171e | ||
|
|
175e132576 | ||
|
|
71f6a5f44e | ||
|
|
d631f21024 | ||
|
|
2351c95612 | ||
|
|
8dcbca1068 | ||
|
|
2183b35ba4 | ||
|
|
2cf52642fa | ||
|
|
eea2a97475 | ||
|
|
71f083d4ad | ||
|
|
32ef0678e5 | ||
|
|
e719690100 | ||
|
|
53fb958f45 | ||
|
|
ea51073e87 | ||
|
|
116de34438 | ||
|
|
816206321e | ||
|
|
eaa9ab754f | ||
|
|
5bd09907a9 | ||
|
|
7a76a00b62 | ||
|
|
cd96c6934d | ||
|
|
356fdc3cf5 | ||
|
|
7001d3de60 | ||
|
|
b64e030e92 | ||
|
|
9bb2cca842 | ||
|
|
639bcd3cae | ||
|
|
cdb9e3dea6 | ||
|
|
4c15dcbe89 | ||
|
|
cd2d25def7 | ||
|
|
2e816c0c1a | ||
|
|
3888376be3 | ||
|
|
71b8204652 | ||
|
|
62d74507d4 | ||
|
|
5d0235e757 | ||
|
|
29fa11ef4d | ||
|
|
919e5fbc5a | ||
|
|
259ceee478 | ||
|
|
ef9cca2f6e | ||
|
|
ae58485b67 | ||
|
|
731ac55a1b | ||
|
|
804fb825bb | ||
|
|
a0b966c5b4 | ||
|
|
001ad8e51b | ||
|
|
e0259b310b | ||
|
|
010694f64c | ||
|
|
398c7bded1 | ||
|
|
1327039824 | ||
|
|
1870c28397 | ||
|
|
a36d6b3688 | ||
|
|
d066cd1ff6 | ||
|
|
7c4db9f425 | ||
|
|
72a6724a46 | ||
|
|
df9b7f6b70 | ||
|
|
3e122f3eff | ||
|
|
6dac32e165 | ||
|
|
24fd5c39f5 | ||
|
|
9f66974cd4 | ||
|
|
c0e03f7ce7 | ||
|
|
93247a8a35 | ||
|
|
efcd1ff707 | ||
|
|
7b242d787e | ||
|
|
68ca317ec2 | ||
|
|
de76686bdc | ||
|
|
c792f3bf57 | ||
|
|
3903a1b4af | ||
|
|
106f0e3af6 | ||
|
|
f275a66f91 | ||
|
|
37f559b4b1 | ||
|
|
1aec8ed330 | ||
|
|
b6f0ebf968 | ||
|
|
3a60050ddd | ||
|
|
bfdbcf1955 | ||
|
|
e5e94070d6 | ||
|
|
70c54abc2a | ||
|
|
b270a46e53 | ||
|
|
d400a98ef0 | ||
|
|
7b6c4bf2e0 | ||
|
|
f090471022 | ||
|
|
4648d8f499 | ||
|
|
5cc7329717 | ||
|
|
9f41b4f72d | ||
|
|
06de82fd86 | ||
|
|
1d512ef7f4 | ||
|
|
8ff3fadba0 | ||
|
|
137eef6590 | ||
|
|
b204655081 | ||
|
|
f4c6ed863d | ||
|
|
ac4eb746fe | ||
|
|
6df69f9b5b | ||
|
|
20bd6bdb68 | ||
|
|
8724fe6e3f | ||
|
|
fae8726a76 | ||
|
|
e51f474613 | ||
|
|
051181fa6e | ||
|
|
7c261118e0 | ||
|
|
b15393c2a5 | ||
|
|
0e63f18a73 | ||
|
|
fc3460470a | ||
|
|
34f15259fa | ||
|
|
f7bcf7fa46 | ||
|
|
5a562a597c | ||
|
|
3e308584a3 | ||
|
|
9ac3b52fdc | ||
|
|
142928e944 | ||
|
|
b095dc4f2b | ||
|
|
45da0d43fd | ||
|
|
7494ff2917 | ||
|
|
de8ce9a8ff | ||
|
|
17709d7d0f | ||
|
|
a5f385917d | ||
|
|
18fbc0394b | ||
|
|
35ad006234 | ||
|
|
4163c872af | ||
|
|
4b087a0de2 | ||
|
|
a463620edb | ||
|
|
0b41533763 | ||
|
|
e92a217bd1 | ||
|
|
a83d81ff45 | ||
|
|
ae555465ba | ||
|
|
622d857bed | ||
|
|
0530ec11c0 | ||
|
|
46e7b51352 | ||
|
|
e9e721b937 | ||
|
|
992f501159 | ||
|
|
0f3f1a001c | ||
|
|
804c255941 | ||
|
|
c10fe7ec18 | ||
|
|
7ffc0268df | ||
|
|
e7d4ec6834 | ||
|
|
f4a53f7ee6 | ||
|
|
3eafcab64e | ||
|
|
ba65e0ace7 | ||
|
|
213d3562bd | ||
|
|
3ac5a24b12 | ||
|
|
3107c98591 | ||
|
|
edc7df5480 | ||
|
|
951604e29f | ||
|
|
381f84ee27 | ||
|
|
14c283a623 | ||
|
|
4baf56520d | ||
|
|
a69b7abe00 | ||
|
|
03d86ea0b4 | ||
|
|
21323ef1ff | ||
|
|
1f39948bc3 | ||
|
|
e82985c0a1 | ||
|
|
8e5bd82c4d | ||
|
|
575caa8015 | ||
|
|
5f3af7d18b | ||
|
|
6a6b579c54 | ||
|
|
6d163b72dc | ||
|
|
b72932b445 | ||
|
|
9fc018ded1 | ||
|
|
a9a207685a | ||
|
|
c1d03695d4 | ||
|
|
ea74680df4 | ||
|
|
bb671c3089 | ||
|
|
1b68fb7683 | ||
|
|
303329f2d6 | ||
|
|
85c824ed13 | ||
|
|
998e4820c9 | ||
|
|
5cd7b0c6e4 | ||
|
|
8908a91016 | ||
|
|
261a8db9dd | ||
|
|
f195db2d14 | ||
|
|
da71e86633 | ||
|
|
bccaf5fc2d | ||
|
|
0c6a029413 | ||
|
|
25ba9d848d | ||
|
|
76000e676b | ||
|
|
e8b2954586 | ||
|
|
fe299e24d6 | ||
|
|
ef22c0206f | ||
|
|
8b74257bf3 | ||
|
|
9a01581cdd | ||
|
|
5d26ac0088 | ||
|
|
ed26ed5a1f | ||
|
|
16a30556df | ||
|
|
5daafc9d33 | ||
|
|
e16a470d59 | ||
|
|
367a2d4b29 | ||
|
|
0f9c78c3eb | ||
|
|
c660218e43 | ||
|
|
fa682270a9 | ||
|
|
ac5e8176b9 | ||
|
|
9f338f5a56 | ||
|
|
2742fef458 | ||
|
|
bc59fcf5c5 | ||
|
|
2f32044273 | ||
|
|
371f21fb35 | ||
|
|
8edc0fae5f | ||
|
|
e1f707d7e1 | ||
|
|
9ee3dc71f1 | ||
|
|
70dc23f996 | ||
|
|
4e9e230e34 | ||
|
|
dc45b85a54 | ||
|
|
a4ef62f5b2 | ||
|
|
604fb2b738 | ||
|
|
273bfee9a1 | ||
|
|
af37f9dc54 | ||
|
|
db9b3aff75 | ||
|
|
eabf05758e | ||
|
|
8756b7a735 | ||
|
|
ec9f157512 | ||
|
|
7f58887ae3 | ||
|
|
d17d7eba14 | ||
|
|
4fb4991f5e | ||
|
|
0f8c46771e | ||
|
|
f5a8593b11 | ||
|
|
c2898f53bc | ||
|
|
089797dbe6 | ||
|
|
53c70b5f27 | ||
|
|
a66e6d4dff | ||
|
|
1fa4f58080 | ||
|
|
7fff9da71d | ||
|
|
f128f4cba1 | ||
|
|
6f0c966877 | ||
|
|
f10a260301 | ||
|
|
54bc8a7627 | ||
|
|
9824a451bb | ||
|
|
e0e897832c | ||
|
|
87472150bc | ||
|
|
45d318a773 | ||
|
|
4e1de06782 | ||
|
|
8735a85a30 | ||
|
|
e108954633 | ||
|
|
47cf257c40 | ||
|
|
cc56ebd90d | ||
|
|
89c82035d8 | ||
|
|
3ce5a68cd1 | ||
|
|
0704ca0550 | ||
|
|
ae7664597e | ||
|
|
e9e671078c | ||
|
|
71a56c3552 | ||
|
|
e7659883cc | ||
|
|
663c936428 | ||
|
|
56a558baf8 | ||
|
|
e6385e2ab7 | ||
|
|
5362f472ff | ||
|
|
41e79d902d | ||
|
|
c7bcebb628 | ||
|
|
a89afe1229 | ||
|
|
faecff570c | ||
|
|
828461c193 | ||
|
|
ec4a789b4f | ||
|
|
a65f6f07f3 | ||
|
|
00f71c3b9d | ||
|
|
3d2bf8fb02 | ||
|
|
391eec9ee7 | ||
|
|
0d91ef78dd | ||
|
|
7e18a1f1be | ||
|
|
7f4fc6f8a7 | ||
|
|
a2f13e479b | ||
|
|
fccf1e2eac | ||
|
|
480d5f2d51 | ||
|
|
a6170963b8 | ||
|
|
77325b92fb | ||
|
|
b55d7cd45a | ||
|
|
23f981c458 | ||
|
|
48d1bca9b8 | ||
|
|
a13cf0e3ce | ||
|
|
1c8614ac9a | ||
|
|
4558793caf | ||
|
|
583257f093 | ||
|
|
7cea688a1c | ||
|
|
062dd8dabc | ||
|
|
1d07a36552 | ||
|
|
a5d29fa1d4 | ||
|
|
9aabd911eb | ||
|
|
1f27bf6380 | ||
|
|
2d8eac4e0a | ||
|
|
833538cc90 | ||
|
|
80db8804c7 | ||
|
|
d9effbb179 | ||
|
|
24a0f55c9c | ||
|
|
f5e54cd398 | ||
|
|
c9317a16c5 | ||
|
|
dae6fe91a1 | ||
|
|
65fdc7ae50 | ||
|
|
00ebedad93 | ||
|
|
35bfffb556 | ||
|
|
8af332c9a7 | ||
|
|
7993909fab | ||
|
|
88b21a72f1 | ||
|
|
ea2fba877a | ||
|
|
5683bb76cc | ||
|
|
3a87fab6c8 | ||
|
|
23516acd0b | ||
|
|
48f7c5603e | ||
|
|
0b5b2b2633 | ||
|
|
1348d9aaf8 | ||
|
|
21f17e871e | ||
|
|
837cea6b4a | ||
|
|
b0b9732359 | ||
|
|
26c046a563 | ||
|
|
cf650fcaac | ||
|
|
3dd7d7867b | ||
|
|
31578303a4 | ||
|
|
06d197cdd0 | ||
|
|
11aab4198b | ||
|
|
8d387433b1 | ||
|
|
0f25fa7af6 | ||
|
|
8d55702d13 | ||
|
|
e54f5e544f | ||
|
|
289c3ff377 | ||
|
|
11f3f72f1c | ||
|
|
ad7c72c164 | ||
|
|
51294163bb | ||
|
|
598efbf7f9 | ||
|
|
b651bbf446 | ||
|
|
4801bdf45a | ||
|
|
25c1974e0d | ||
|
|
12896b22d8 | ||
|
|
e531c59606 | ||
|
|
8f652f4e31 | ||
|
|
97cb404822 | ||
|
|
d2a3bed240 | ||
|
|
e4583cb9b7 | ||
|
|
a684a91bf5 | ||
|
|
1175f48d05 | ||
|
|
54dccc480e | ||
|
|
f2cccf8da7 | ||
|
|
10be033791 | ||
|
|
4e39cdef94 | ||
|
|
2bba53b2c3 | ||
|
|
b9e886726c | ||
|
|
44fc888bd6 | ||
|
|
f2d1295fe6 | ||
|
|
04bd253390 | ||
|
|
7db751df3b | ||
|
|
f7311e0d97 | ||
|
|
633e23bd65 | ||
|
|
7c227d2a00 | ||
|
|
0a0fb11c21 | ||
|
|
c31b301722 | ||
|
|
5d0b18a0d0 | ||
|
|
258ae99491 | ||
|
|
10cf2f3edd | ||
|
|
0cd9c5b5be | ||
|
|
a8707158a5 | ||
|
|
ad1da994b2 | ||
|
|
ba6fbc417e | ||
|
|
ce199d6f9e | ||
|
|
0dd8e8c242 | ||
|
|
b9ee29a945 | ||
|
|
be05c9022d | ||
|
|
afb061c374 | ||
|
|
1ee37148a8 | ||
|
|
1c73902005 | ||
|
|
d387e9b6d3 | ||
|
|
163d3547e6 | ||
|
|
c61998bd20 | ||
|
|
1e4d6672be | ||
|
|
80812b86d6 | ||
|
|
128f6359e9 | ||
|
|
8c0331d244 |
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,8 +8,8 @@ assignees: ''
|
||||
|
||||
##### Minetest version
|
||||
<!--
|
||||
Paste Minetest version between quotes below
|
||||
If you are on a devel version, please add git commit hash
|
||||
Paste Minetest version between quotes below.
|
||||
If you are on a devel version, please add git commit hash.
|
||||
You can use `minetest --version` to find it.
|
||||
-->
|
||||
```
|
||||
@@ -29,4 +29,4 @@ OpenGL version:
|
||||
<!-- Describe your problem here -->
|
||||
|
||||
##### Steps to reproduce
|
||||
<!-- For bug reports or build issues, explain how the problem happened -->
|
||||
<!-- Explain how the problem has happened, providing a minimal test (i.e. a code snippet reduced to the bone) where possible -->
|
||||
|
||||
16
.github/workflows/android.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -31,12 +31,22 @@ jobs:
|
||||
- name: Build with Gradle
|
||||
run: cd android; ./gradlew assemblerelease
|
||||
- name: Save armeabi artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-armeabi-v7a.apk
|
||||
path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk
|
||||
- name: Save arm64 artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-arm64-v8a.apk
|
||||
path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
|
||||
- name: Save x86 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-x86.apk
|
||||
path: android/app/build/outputs/apk/release/app-x86-release-unsigned.apk
|
||||
- name: Save x86_64 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-x86_64.apk
|
||||
path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk
|
||||
|
||||
90
.github/workflows/build.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
gcc_5:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
@@ -52,21 +52,21 @@ jobs:
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
# Current gcc version
|
||||
gcc_10:
|
||||
runs-on: ubuntu-20.04
|
||||
gcc_12:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-10
|
||||
install_linux_deps g++-12 libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
CC: gcc-12
|
||||
CXX: g++-12
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
@@ -76,11 +76,11 @@ jobs:
|
||||
clang_3_9:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-3.9 gdb
|
||||
install_linux_deps clang-3.9 valgrind
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
@@ -93,46 +93,45 @@ jobs:
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
- name: Integration test + devtest
|
||||
- name: Valgrind
|
||||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
|
||||
|
||||
# Current clang version
|
||||
clang_10:
|
||||
runs-on: ubuntu-20.04
|
||||
clang_14:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-10 valgrind libluajit-5.1-dev
|
||||
install_linux_deps clang-14 gdb
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-10
|
||||
CXX: clang++-10
|
||||
CMAKE_FLAGS: "-DREQUIRE_LUAJIT=1"
|
||||
CC: clang-14
|
||||
CXX: clang++-14
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
- name: Valgrind
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
# Build with prometheus-cpp (server-only)
|
||||
clang_9_prometheus:
|
||||
name: "clang_9 (PROMETHEUS=1)"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps --old-irr clang-9
|
||||
install_linux_deps clang-9
|
||||
|
||||
- name: Build prometheus-cpp
|
||||
run: |
|
||||
@@ -152,9 +151,9 @@ jobs:
|
||||
|
||||
docker:
|
||||
name: "Docker image"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build . -t minetest:latest
|
||||
@@ -162,13 +161,13 @@ jobs:
|
||||
|
||||
win32:
|
||||
name: "MinGW cross-compiler (32-bit)"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update -q && sudo apt-get install gettext -qyy
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
- name: Build
|
||||
@@ -180,13 +179,13 @@ jobs:
|
||||
|
||||
win64:
|
||||
name: "MinGW cross-compiler (64-bit)"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update -q && sudo apt-get install gettext -qyy
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
- name: Build
|
||||
@@ -199,13 +198,10 @@ jobs:
|
||||
msvc:
|
||||
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
runs-on: windows-2019
|
||||
#### Disabled due to Irrlicht switch
|
||||
if: false
|
||||
#### Disabled due to Irrlicht switch
|
||||
env:
|
||||
VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0
|
||||
# 2020.11
|
||||
vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit
|
||||
VCPKG_VERSION: 5cf60186a241e84e8232641ee973395d4fde90e1
|
||||
# 2022.02
|
||||
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -226,11 +222,15 @@ jobs:
|
||||
# Enable it, when working on the installer.
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout IrrlichtMt
|
||||
run: |
|
||||
$ref = @(Get-Content misc\irrlichtmt_tag.txt)
|
||||
git clone https://github.com/minetest/irrlicht lib\irrlichtmt --depth 1 -b $ref[0]
|
||||
|
||||
- name: Restore from cache and run vcpkg
|
||||
uses: lukka/run-vcpkg@v5
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: ${{env.vcpkg_packages}}
|
||||
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
|
||||
@@ -238,7 +238,7 @@ jobs:
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
|
||||
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
|
||||
|
||||
- name: CMake
|
||||
- name: Minetest CMake
|
||||
run: |
|
||||
cmake ${{matrix.config.generator}} `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
|
||||
@@ -246,7 +246,7 @@ jobs:
|
||||
-DENABLE_POSTGRESQL=OFF `
|
||||
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
|
||||
|
||||
- name: Build
|
||||
- name: Build Minetest
|
||||
run: cmake --build . --config Release
|
||||
|
||||
- name: CPack
|
||||
@@ -265,7 +265,7 @@ jobs:
|
||||
- name: Package Clean
|
||||
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
|
||||
|
||||
- uses: actions/upload-artifact@v1
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
path: .\Package\
|
||||
|
||||
14
.github/workflows/cpp_lint.yml
vendored
@@ -26,12 +26,13 @@ on:
|
||||
jobs:
|
||||
|
||||
# clang_format:
|
||||
# runs-on: ubuntu-18.04
|
||||
# runs-on: ubuntu-20.04
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/checkout@v3
|
||||
# - name: Install clang-format
|
||||
# run: |
|
||||
# sudo apt-get install clang-format-9 -qyy
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y clang-format-9
|
||||
#
|
||||
# - name: Run clang-format
|
||||
# run: |
|
||||
@@ -41,14 +42,13 @@ jobs:
|
||||
# CLANG_FORMAT: clang-format-9
|
||||
|
||||
clang_tidy:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get install clang-tidy-9 -qyy
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps
|
||||
install_linux_deps clang-tidy-9
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
|
||||
72
.github/workflows/lua.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: lua_lint
|
||||
|
||||
# Lint on lua changes on builtin or if workflow changed
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- 'games/devtest/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- 'games/devtest/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
jobs:
|
||||
# Note that the integration tests are also run build.yml, but only when C++ code is changed.
|
||||
integration_tests:
|
||||
name: "Compile and run multiplayer tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-10 gdb libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-10
|
||||
CXX: clang++-10
|
||||
CMAKE_FLAGS: "-DENABLE_GETTEXT=0 -DBUILD_SERVER=0"
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
luacheck:
|
||||
name: "Builtin Luacheck and Unit Tests"
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: leafo/gh-actions-lua@v9
|
||||
with:
|
||||
luaVersion: "5.1.5"
|
||||
- uses: leafo/gh-actions-luarocks@v4
|
||||
|
||||
- name: Install LuaJIT
|
||||
run: |
|
||||
cd $HOME
|
||||
git clone https://github.com/LuaJIT/LuaJIT/
|
||||
cd LuaJIT
|
||||
make -j$(nproc)
|
||||
|
||||
- name: Install luarocks tools
|
||||
run: |
|
||||
luarocks install --local luacheck
|
||||
luarocks install --local busted
|
||||
|
||||
- name: Run checks (builtin)
|
||||
run: |
|
||||
$HOME/.luarocks/bin/luacheck builtin
|
||||
$HOME/.luarocks/bin/busted builtin
|
||||
$HOME/.luarocks/bin/busted builtin --lua=$HOME/LuaJIT/src/luajit
|
||||
|
||||
- name: Run checks (devtest)
|
||||
run: |
|
||||
$HOME/.luarocks/bin/luacheck --config=games/devtest/.luacheckrc games/devtest
|
||||
32
.github/workflows/lua_lint.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: lua_lint
|
||||
|
||||
# Lint on lua changes on builtin or if workflow changed
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
jobs:
|
||||
luacheck:
|
||||
name: "Builtin Luacheck and Unit Tests"
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install luarocks
|
||||
run: |
|
||||
sudo apt-get install luarocks -qyy
|
||||
|
||||
- name: Install luarocks tools
|
||||
run: |
|
||||
luarocks install --local luacheck
|
||||
luarocks install --local busted
|
||||
|
||||
- name: Run checks
|
||||
run: |
|
||||
$HOME/.luarocks/bin/luacheck builtin
|
||||
$HOME/.luarocks/bin/busted builtin
|
||||
33
.github/workflows/macos.yml
vendored
@@ -22,37 +22,31 @@ on:
|
||||
- '.github/workflows/macos.yml'
|
||||
|
||||
env:
|
||||
IRRLICHT_TAG: 1.9.0mt4
|
||||
MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git
|
||||
MINETEST_GAME_BRANCH: master
|
||||
MINETEST_GAME_NAME: minetest_game
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd)
|
||||
brew update
|
||||
brew install ${pkgs[@]}
|
||||
brew unlink $(brew ls --formula)
|
||||
brew link ${pkgs[@]}
|
||||
source ./util/ci/common.sh
|
||||
install_macos_deps
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME
|
||||
rm -rvf games/$MINETEST_GAME_NAME/.git
|
||||
git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
|
||||
mkdir cmakebuild
|
||||
cd cmakebuild
|
||||
git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE \
|
||||
-DENABLE_FREETYPE=TRUE -DENABLE_GETTEXT=TRUE
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
|
||||
make -j2
|
||||
make install
|
||||
|
||||
@@ -60,7 +54,14 @@ jobs:
|
||||
run: |
|
||||
./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
# Zipping the built .app preserves permissions on the contained files,
|
||||
# which the GitHub artifact pipeline would otherwise strip away.
|
||||
- name: CPack
|
||||
run: |
|
||||
cd build
|
||||
cpack -G ZIP -B macos
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: minetest-macos
|
||||
path: ./build/macos/
|
||||
path: ./build/macos/*.zip
|
||||
|
||||
@@ -9,7 +9,6 @@ stages:
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
IRRLICHT_TAG: "1.9.0mt4"
|
||||
MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git"
|
||||
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
|
||||
|
||||
@@ -19,11 +18,10 @@ variables:
|
||||
- apt-get update
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev
|
||||
script:
|
||||
- git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
|
||||
- mkdir cmakebuild
|
||||
- cd cmakebuild
|
||||
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE ..
|
||||
- make -j2
|
||||
- git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
- mkdir build && cd build
|
||||
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE ..
|
||||
- make -j $(($(nproc) + 1))
|
||||
- make install
|
||||
artifacts:
|
||||
when: on_success
|
||||
@@ -198,7 +196,7 @@ build:fedora-28:
|
||||
before_script:
|
||||
- apt-get update
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext
|
||||
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
|
||||
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
- tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
.build_win_template:
|
||||
|
||||
8
.mailmap
@@ -49,14 +49,15 @@ ClobberXD <ClobberXD@gmail.com> <36130650+ClobberXD@users.noreply.github.com>
|
||||
Auke Kok <sofar+github@foo-projects.org>
|
||||
Auke Kok <sofar+github@foo-projects.org> <sofar@foo-projects.org>
|
||||
Desour <vorunbekannt75@web.de>
|
||||
Nathanaël Courant <Ekdohibs@users.noreply.github.com> <nathanael.courant@laposte.net>
|
||||
Nathanaëlle Courant <Ekdohibs@users.noreply.github.com> <nathanael.courant@laposte.net>
|
||||
Ezhh <owlecho@live.com>
|
||||
paramat <paramat@users.noreply.github.com>
|
||||
paramat <paramat@users.noreply.github.com> <mat.gregory@virginmedia.com>
|
||||
lhofhansl <lhofhansl@yahoo.com> <larsh@apache.org>
|
||||
red-001 <red-001@outlook.ie> <red-001@openmailbox.org>
|
||||
Wuzzy <wuzzy2@mail.ru> <Wuzzy2@mail.ru>
|
||||
Wuzzy <wuzzy2@mail.ru> <almikes@aol.com>
|
||||
Wuzzy <Wuzzy@disroot.org> <wuzzy2@mail.ru>
|
||||
Wuzzy <Wuzzy@disroot.org> <Wuzzy2@mail.ru>
|
||||
Wuzzy <Wuzzy@disroot.org> <almikes@aol.com>
|
||||
Jordach <jordach.snelling@gmail.com>
|
||||
MoNTE48 <MoNTE48@mail.ua>
|
||||
v-rob <robinsonvincent89@gmail.com>
|
||||
@@ -65,3 +66,4 @@ EvidenceB <49488517+EvidenceBKidscode@users.noreply.github.com>
|
||||
gregorycu <gregory.currie@gmail.com>
|
||||
Rogier <rogier777@gmail.com>
|
||||
Rogier <rogier777@gmail.com> <Rogier-5@users.noreply.github.com>
|
||||
x2048 <codeforsmile@gmail.com>
|
||||
|
||||
@@ -11,13 +11,14 @@ endif()
|
||||
project(minetest)
|
||||
set(PROJECT_NAME_CAPITALIZED "Minetest")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(GCC_MINIMUM_VERSION "5.1")
|
||||
set(CLANG_MINIMUM_VERSION "3.5")
|
||||
|
||||
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
|
||||
set(VERSION_MAJOR 5)
|
||||
set(VERSION_MINOR 5)
|
||||
set(VERSION_MINOR 6)
|
||||
set(VERSION_PATCH 1)
|
||||
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
|
||||
|
||||
@@ -51,6 +52,7 @@ set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL
|
||||
set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
|
||||
set(BUILD_SERVER FALSE CACHE BOOL "Build server")
|
||||
set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
|
||||
set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
|
||||
|
||||
set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
|
||||
|
||||
@@ -59,6 +61,9 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Debug or Release" FORCE)
|
||||
endif()
|
||||
|
||||
set(ENABLE_UPDATE_CHECKER (NOT ${DEVELOPMENT_BUILD}) CACHE BOOL
|
||||
"Whether to enable update checks by default")
|
||||
|
||||
# Included stuff
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
|
||||
|
||||
@@ -68,7 +73,7 @@ if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
|
||||
find_package(IrrlichtMt QUIET
|
||||
PATHS "${IRRLICHTMT_BUILD_DIR}"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
)
|
||||
|
||||
if(NOT TARGET IrrlichtMt::IrrlichtMt)
|
||||
# find_package() searches certain subdirectories. ${PATH}/cmake is not
|
||||
@@ -76,7 +81,6 @@ if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
|
||||
# IrrlichtMtConfig.cmake
|
||||
message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
|
||||
endif()
|
||||
# This is done here so that relative search paths are more reasonable
|
||||
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
|
||||
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
|
||||
if(BUILD_CLIENT)
|
||||
@@ -106,9 +110,9 @@ else()
|
||||
|
||||
include(MinetestFindIrrlichtHeaders)
|
||||
if(NOT IRRLICHT_INCLUDE_DIR)
|
||||
message(FATAL_ERROR "Irrlicht or IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
|
||||
message(FATAL_ERROR "IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
|
||||
endif()
|
||||
message(STATUS "Found Irrlicht headers: ${IRRLICHT_INCLUDE_DIR}")
|
||||
message(STATUS "Found IrrlichtMt headers: ${IRRLICHT_INCLUDE_DIR}")
|
||||
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
|
||||
# Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
|
||||
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
|
||||
@@ -116,8 +120,20 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TARGET IrrlichtMt::IrrlichtMt)
|
||||
if(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
|
||||
# retrieve version somehow
|
||||
if(NOT IrrlichtMt_VERSION)
|
||||
get_target_property(IrrlichtMt_VERSION IrrlichtMt VERSION)
|
||||
endif()
|
||||
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
|
||||
|
||||
set(TARGET_VER_S 1.9.0mt8)
|
||||
string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S})
|
||||
if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER})
|
||||
message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build")
|
||||
elseif(NOT DEVELOPMENT_BUILD AND IrrlichtMt_VERSION VERSION_GREATER ${TARGET_VER})
|
||||
message(FATAL_ERROR "IrrlichtMt ${TARGET_VER_S} is required to build")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -279,6 +295,10 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_BENCHMARKS)
|
||||
add_subdirectory(lib/catch2)
|
||||
endif()
|
||||
|
||||
# Subdirectories
|
||||
# Be sure to add all relevant definitions above this
|
||||
add_subdirectory(src)
|
||||
|
||||
19
Dockerfile
@@ -26,24 +26,21 @@ RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-de
|
||||
rm -fr ./games/minetest_game/.git
|
||||
|
||||
WORKDIR /usr/src/
|
||||
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ -b v1.0.0 && \
|
||||
mkdir prometheus-cpp/build && \
|
||||
cd prometheus-cpp/build && \
|
||||
cmake .. \
|
||||
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
|
||||
cd prometheus-cpp && \
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_TESTING=0 \
|
||||
-GNinja && \
|
||||
ninja && \
|
||||
ninja install
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
|
||||
cp -r irrlicht/include /usr/include/irrlichtmt
|
||||
|
||||
WORKDIR /usr/src/minetest
|
||||
RUN mkdir build && \
|
||||
cd build && \
|
||||
cmake .. \
|
||||
RUN cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SERVER=TRUE \
|
||||
@@ -51,8 +48,8 @@ RUN mkdir build && \
|
||||
-DBUILD_UNITTESTS=FALSE \
|
||||
-DBUILD_CLIENT=FALSE \
|
||||
-GNinja && \
|
||||
ninja && \
|
||||
ninja install
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
ARG DOCKER_IMAGE=alpine:3.14
|
||||
FROM $DOCKER_IMAGE AS runtime
|
||||
|
||||
@@ -63,6 +63,15 @@ Zughy:
|
||||
|
||||
appgurueu:
|
||||
textures/base/pack/server_incompatible.png
|
||||
|
||||
erlehmann, Warr1024, rollerozxa:
|
||||
textures/base/pack/no_screenshot.png
|
||||
|
||||
kilbith:
|
||||
textures/base/pack/server_favorite.png
|
||||
|
||||
SmallJoker
|
||||
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
|
||||
|
||||
License of Minetest source code
|
||||
-------------------------------
|
||||
|
||||
28
README.md
@@ -7,7 +7,7 @@ Minetest
|
||||
|
||||
Minetest is a free open-source voxel game engine with easy modding and game creation.
|
||||
|
||||
Copyright (C) 2010-2020 Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2010-2022 Perttu Ahola <celeron55@gmail.com>
|
||||
and contributors (see source file comments and the version control log)
|
||||
|
||||
In case you downloaded the source code
|
||||
@@ -144,19 +144,19 @@ Compiling
|
||||
|
||||
For Debian/Ubuntu users:
|
||||
|
||||
sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
|
||||
sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxi-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
|
||||
|
||||
For Fedora users:
|
||||
|
||||
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
|
||||
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXi-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
|
||||
|
||||
For Arch users:
|
||||
|
||||
sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
|
||||
sudo pacman -S base-devel libcurl-gnutls cmake libxi libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
|
||||
|
||||
For Alpine users:
|
||||
|
||||
sudo apk add build-base cmake libpng-dev jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev
|
||||
sudo apk add build-base cmake libpng-dev jpeg-dev libxi-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev
|
||||
|
||||
#### Download
|
||||
|
||||
@@ -223,8 +223,8 @@ Run it:
|
||||
- You can disable the client build by specifying `-DBUILD_CLIENT=FALSE`.
|
||||
- You can select between Release and Debug build by `-DCMAKE_BUILD_TYPE=<Debug or Release>`.
|
||||
- Debug build is slower, but gives much more useful output in a debugger.
|
||||
- If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed.
|
||||
- In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`.
|
||||
- If you build a bare server you don't need to compile IrrlichtMt, just the headers suffice.
|
||||
- In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlichtmt/include`.
|
||||
|
||||
- Minetest will use the IrrlichtMt package that is found first, given by the following order:
|
||||
1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
|
||||
@@ -240,6 +240,7 @@ General options and their default values:
|
||||
BUILD_CLIENT=TRUE - Build Minetest client
|
||||
BUILD_SERVER=FALSE - Build Minetest server
|
||||
BUILD_UNITTESTS=TRUE - Build unittest sources
|
||||
BUILD_BENCHMARKS=FALSE - Build benchmark sources
|
||||
CMAKE_BUILD_TYPE=Release - Type of build (Release vs. Debug)
|
||||
Release - Release build
|
||||
Debug - Debug build
|
||||
@@ -249,7 +250,7 @@ General options and their default values:
|
||||
ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
|
||||
ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
|
||||
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
|
||||
ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by IrrlichtMt)
|
||||
ENABLE_GLES=OFF - Enable extra support code for OpenGL ES (requires support by IrrlichtMt)
|
||||
ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend
|
||||
ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
|
||||
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
|
||||
@@ -259,8 +260,8 @@ General options and their default values:
|
||||
ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default)
|
||||
ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp)
|
||||
ENABLE_SYSTEM_JSONCPP=ON - Use JsonCPP from system
|
||||
OPENGL_GL_PREFERENCE=LEGACY - Linux client build only; See CMake Policy CMP0072 for reference
|
||||
RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory)
|
||||
ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default
|
||||
USE_GPROF=FALSE - Enable profiling using GProf
|
||||
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
|
||||
ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt)
|
||||
@@ -300,15 +301,12 @@ Library specific options:
|
||||
OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll
|
||||
OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located
|
||||
OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib
|
||||
OPENGLES2_INCLUDE_DIR - Only if building with GLES; directory that contains gl2.h
|
||||
OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so
|
||||
SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h
|
||||
SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib
|
||||
VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
|
||||
VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs
|
||||
VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
|
||||
VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
|
||||
XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so
|
||||
ZLIB_DLL - Only on Windows; path to zlib1.dll
|
||||
ZLIB_INCLUDE_DIR - Directory that contains zlib.h
|
||||
ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib
|
||||
@@ -421,8 +419,8 @@ git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
|
||||
#### Build
|
||||
|
||||
```bash
|
||||
mkdir cmakebuild
|
||||
cd cmakebuild
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
|
||||
@@ -430,7 +428,7 @@ cmake .. \
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
|
||||
|
||||
make -j$(nproc)
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
```
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ android {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'armeabi-v7a', 'arm64-v8a'
|
||||
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,17 @@
|
||||
android:name=".UnzipService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="net.minetest.minetest.fileprovider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
@@ -34,10 +35,16 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
// Native code finds these methods by name (see porting_android.cpp).
|
||||
// This annotation prevents the minifier/Proguard from mangling them.
|
||||
@Keep
|
||||
public class GameActivity extends NativeActivity {
|
||||
static {
|
||||
System.loadLibrary("c++_shared");
|
||||
@@ -179,4 +186,22 @@ public class GameActivity extends NativeActivity {
|
||||
public String getCachePath() {
|
||||
return Utils.getCacheDirectory(this).getAbsolutePath();
|
||||
}
|
||||
|
||||
public void shareFile(String path) {
|
||||
File file = new File(path);
|
||||
if (!file.exists()) {
|
||||
Log.e("GameActivity", "File " + file.getAbsolutePath() + " doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
Uri fileUri = FileProvider.getUriForFile(this, "net.minetest.minetest.fileprovider", file);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_SEND, fileUri);
|
||||
intent.setDataAndType(fileUri, getContentResolver().getType(fileUri));
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
|
||||
Intent shareIntent = Intent.createChooser(intent, null);
|
||||
startActivity(shareIntent);
|
||||
}
|
||||
}
|
||||
|
||||
3
android/app/src/main/res/xml/filepaths.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<paths>
|
||||
<external-files-path path="Minetest/" name="minetest" />
|
||||
</paths>
|
||||
@@ -1,16 +1,17 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
project.ext.set("versionMajor", 5) // Version Major
|
||||
project.ext.set("versionMinor", 5) // Version Minor
|
||||
project.ext.set("versionPatch", 1) // Version Patch
|
||||
project.ext.set("versionMinor", 6) // Version Minor
|
||||
project.ext.set("versionPatch", 0) // Version Patch
|
||||
project.ext.set("versionExtra", "") // Version Extra
|
||||
project.ext.set("versionCode", 40) // Android Version Code
|
||||
project.ext.set("versionCode", 42) // Android Version Code
|
||||
project.ext.set("developmentBuild", 0) // Whether it is a development build, or a release
|
||||
// NOTE: +2 after each release!
|
||||
// +1 for ARM and +1 for ARM64 APK's, because
|
||||
// each APK must have a larger `versionCode` than the previous
|
||||
|
||||
buildscript {
|
||||
ext.ndk_version = '23.0.7599858'
|
||||
ext.ndk_version = '23.2.8568313'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
@@ -14,7 +14,8 @@ android {
|
||||
"versionMajor=${versionMajor}",
|
||||
"versionMinor=${versionMinor}",
|
||||
"versionPatch=${versionPatch}",
|
||||
"versionExtra=${versionExtra}"
|
||||
"versionExtra=${versionExtra}",
|
||||
"developmentBuild=${developmentBuild}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +31,7 @@ android {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'armeabi-v7a', 'arm64-v8a'//, 'x86'
|
||||
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
LOCAL_PATH := $(call my-dir)/..
|
||||
|
||||
#LOCAL_ADDRESS_SANITIZER:=true
|
||||
#USE_BUILTIN_LUA:=true
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Curl
|
||||
@@ -42,11 +43,25 @@ LOCAL_MODULE := Irrlicht
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libIrrlichtMt.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Irrlicht-libpng
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libpng.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Irrlicht-libjpeg
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libjpeg.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
ifndef USE_BUILTIN_LUA
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := LuaJIT
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := OpenAL
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/libopenal.a
|
||||
@@ -92,14 +107,20 @@ LOCAL_CFLAGS += \
|
||||
-DUSE_CURL=1 \
|
||||
-DUSE_SOUND=1 \
|
||||
-DUSE_LEVELDB=0 \
|
||||
-DUSE_LUAJIT=1 \
|
||||
-DUSE_GETTEXT=1 \
|
||||
-DVERSION_MAJOR=${versionMajor} \
|
||||
-DVERSION_MINOR=${versionMinor} \
|
||||
-DVERSION_PATCH=${versionPatch} \
|
||||
-DVERSION_EXTRA=${versionExtra} \
|
||||
-DDEVELOPMENT_BUILD=${developmentBuild} \
|
||||
$(GPROF_DEF)
|
||||
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_CFLAGS += -DUSE_LUAJIT=0
|
||||
else
|
||||
LOCAL_CFLAGS += -DUSE_LUAJIT=1
|
||||
endif
|
||||
|
||||
ifdef NDEBUG
|
||||
LOCAL_CFLAGS += -DNDEBUG=1
|
||||
endif
|
||||
@@ -120,12 +141,19 @@ LOCAL_C_INCLUDES := \
|
||||
deps/$(APP_ABI)/Irrlicht/include \
|
||||
deps/$(APP_ABI)/Gettext/include \
|
||||
deps/$(APP_ABI)/Iconv/include \
|
||||
deps/$(APP_ABI)/LuaJIT/include \
|
||||
deps/$(APP_ABI)/OpenAL-Soft/include \
|
||||
deps/$(APP_ABI)/SQLite/include \
|
||||
deps/$(APP_ABI)/Vorbis/include \
|
||||
deps/$(APP_ABI)/Zstd/include
|
||||
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_C_INCLUDES += \
|
||||
../../lib/lua/src \
|
||||
../../lib/bitop
|
||||
else
|
||||
LOCAL_C_INCLUDES += deps/$(APP_ABI)/LuaJIT/include
|
||||
endif
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(wildcard ../../src/client/*.cpp) \
|
||||
$(wildcard ../../src/client/*/*.cpp) \
|
||||
@@ -207,6 +235,41 @@ LOCAL_SRC_FILES := \
|
||||
../../src/voxel.cpp \
|
||||
../../src/voxelalgorithms.cpp
|
||||
|
||||
# Built-in Lua
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_SRC_FILES += \
|
||||
../../lib/lua/src/lapi.c \
|
||||
../../lib/lua/src/lauxlib.c \
|
||||
../../lib/lua/src/lbaselib.c \
|
||||
../../lib/lua/src/lcode.c \
|
||||
../../lib/lua/src/ldblib.c \
|
||||
../../lib/lua/src/ldebug.c \
|
||||
../../lib/lua/src/ldo.c \
|
||||
../../lib/lua/src/ldump.c \
|
||||
../../lib/lua/src/lfunc.c \
|
||||
../../lib/lua/src/lgc.c \
|
||||
../../lib/lua/src/linit.c \
|
||||
../../lib/lua/src/liolib.c \
|
||||
../../lib/lua/src/llex.c \
|
||||
../../lib/lua/src/lmathlib.c \
|
||||
../../lib/lua/src/lmem.c \
|
||||
../../lib/lua/src/loadlib.c \
|
||||
../../lib/lua/src/lobject.c \
|
||||
../../lib/lua/src/lopcodes.c \
|
||||
../../lib/lua/src/loslib.c \
|
||||
../../lib/lua/src/lparser.c \
|
||||
../../lib/lua/src/lstate.c \
|
||||
../../lib/lua/src/lstring.c \
|
||||
../../lib/lua/src/lstrlib.c \
|
||||
../../lib/lua/src/ltable.c \
|
||||
../../lib/lua/src/ltablib.c \
|
||||
../../lib/lua/src/ltm.c \
|
||||
../../lib/lua/src/lundump.c \
|
||||
../../lib/lua/src/lvm.c \
|
||||
../../lib/lua/src/lzio.c \
|
||||
../../lib/bitop/bit.c
|
||||
endif
|
||||
|
||||
# GMP
|
||||
LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c
|
||||
|
||||
@@ -217,16 +280,18 @@ LOCAL_STATIC_LIBRARIES += \
|
||||
Curl libmbedcrypto libmbedtls libmbedx509 \
|
||||
Freetype \
|
||||
Iconv libcharset \
|
||||
Irrlicht \
|
||||
LuaJIT \
|
||||
Irrlicht Irrlicht-libpng Irrlicht-libjpeg \
|
||||
OpenAL \
|
||||
Gettext \
|
||||
SQLite3 \
|
||||
Vorbis libvorbisfile libogg \
|
||||
Zstd
|
||||
ifndef USE_BUILTIN_LUA
|
||||
LOCAL_STATIC_LIBRARIES += LuaJIT
|
||||
endif
|
||||
LOCAL_STATIC_LIBRARIES += android_native_app_glue $(PROFILER_LIBS)
|
||||
|
||||
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES
|
||||
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES -lz
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ APP_CPPFLAGS := -g -Og -fno-omit-frame-pointer
|
||||
endif
|
||||
|
||||
APP_CFLAGS := $(APP_CPPFLAGS) -Wno-inconsistent-missing-override -Wno-parentheses-equality
|
||||
APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++17
|
||||
APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++14
|
||||
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe
|
||||
|
||||
ifeq ($(APP_ABI),arm64-v8a)
|
||||
|
||||
59
builtin/async/game.lua
Normal file
@@ -0,0 +1,59 @@
|
||||
core.log("info", "Initializing asynchronous environment (game)")
|
||||
|
||||
local function pack2(...)
|
||||
return {n=select('#', ...), ...}
|
||||
end
|
||||
|
||||
-- Entrypoint to run async jobs, called by C++
|
||||
function core.job_processor(func, params)
|
||||
local retval = pack2(func(unpack(params, 1, params.n)))
|
||||
|
||||
return retval
|
||||
end
|
||||
|
||||
-- Import a bunch of individual files from builtin/game/
|
||||
local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM
|
||||
|
||||
dofile(gamepath .. "constants.lua")
|
||||
dofile(gamepath .. "item_s.lua")
|
||||
dofile(gamepath .. "misc_s.lua")
|
||||
dofile(gamepath .. "features.lua")
|
||||
dofile(gamepath .. "voxelarea.lua")
|
||||
|
||||
-- Transfer of globals
|
||||
do
|
||||
local all = assert(core.transferred_globals)
|
||||
core.transferred_globals = nil
|
||||
|
||||
all.registered_nodes = {}
|
||||
all.registered_craftitems = {}
|
||||
all.registered_tools = {}
|
||||
for k, v in pairs(all.registered_items) do
|
||||
-- Disable further modification
|
||||
setmetatable(v, {__newindex = {}})
|
||||
-- Reassemble the other tables
|
||||
if v.type == "node" then
|
||||
all.registered_nodes[k] = v
|
||||
elseif v.type == "craftitem" then
|
||||
all.registered_craftitems[k] = v
|
||||
elseif v.type == "tool" then
|
||||
all.registered_tools[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(all) do
|
||||
core[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- For tables that are indexed by item name:
|
||||
-- If table[X] does not exist, default to table[core.registered_aliases[X]]
|
||||
local alias_metatable = {
|
||||
__index = function(t, name)
|
||||
return rawget(t, core.registered_aliases[name])
|
||||
end
|
||||
}
|
||||
setmetatable(core.registered_items, alias_metatable)
|
||||
setmetatable(core.registered_nodes, alias_metatable)
|
||||
setmetatable(core.registered_craftitems, alias_metatable)
|
||||
setmetatable(core.registered_tools, alias_metatable)
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
core.log("info", "Initializing Asynchronous environment")
|
||||
core.log("info", "Initializing asynchronous environment")
|
||||
|
||||
function core.job_processor(func, serialized_param)
|
||||
local param = core.deserialize(serialized_param)
|
||||
@@ -8,4 +7,3 @@ function core.job_processor(func, serialized_param)
|
||||
|
||||
return retval or core.serialize(nil)
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ local commonpath = scriptpath.."common"..DIR_DELIM
|
||||
|
||||
dofile(clientpath .. "register.lua")
|
||||
dofile(commonpath .. "after.lua")
|
||||
dofile(commonpath .. "mod_storage.lua")
|
||||
dofile(commonpath .. "chatcommands.lua")
|
||||
dofile(clientpath .. "chatcommands.lua")
|
||||
dofile(clientpath .. "death_formspec.lua")
|
||||
dofile(clientpath .. "misc.lua")
|
||||
|
||||
7
builtin/client/misc.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
function core.setting_get_pos(name)
|
||||
local value = core.settings:get(name)
|
||||
if not value then
|
||||
return nil
|
||||
end
|
||||
return core.string_to_pos(value)
|
||||
end
|
||||
@@ -22,7 +22,6 @@ local LIST_FORMSPEC_DESCRIPTION = [[
|
||||
|
||||
local F = core.formspec_escape
|
||||
local S = core.get_translator("__builtin")
|
||||
local check_player_privs = core.check_player_privs
|
||||
|
||||
|
||||
-- CHAT COMMANDS FORMSPEC
|
||||
@@ -58,10 +57,11 @@ local function build_chatcommands_formspec(name, sel, copy)
|
||||
.. "any entry in the list.").. "\n" ..
|
||||
S("Double-click to copy the entry to the chat history.")
|
||||
|
||||
local privs = core.get_player_privs(name)
|
||||
for i, data in ipairs(mod_cmds) do
|
||||
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
|
||||
for j, cmds in ipairs(data[2]) do
|
||||
local has_priv = check_player_privs(name, cmds[2].privs)
|
||||
local has_priv = privs[cmds[2].privs]
|
||||
rows[#rows + 1] = ("%s,1,%s,%s"):format(
|
||||
has_priv and COLOR_GREEN or COLOR_GRAY,
|
||||
cmds[1], F(cmds[2].params))
|
||||
|
||||
@@ -204,7 +204,7 @@ end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function string:trim()
|
||||
return (self:gsub("^%s*(.-)%s*$", "%1"))
|
||||
return self:match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -245,16 +245,16 @@ function math.round(x)
|
||||
return math.ceil(x - 0.5)
|
||||
end
|
||||
|
||||
|
||||
local formspec_escapes = {
|
||||
["\\"] = "\\\\",
|
||||
["["] = "\\[",
|
||||
["]"] = "\\]",
|
||||
[";"] = "\\;",
|
||||
[","] = "\\,"
|
||||
}
|
||||
function core.formspec_escape(text)
|
||||
if text ~= nil then
|
||||
text = string.gsub(text,"\\","\\\\")
|
||||
text = string.gsub(text,"%]","\\]")
|
||||
text = string.gsub(text,"%[","\\[")
|
||||
text = string.gsub(text,";","\\;")
|
||||
text = string.gsub(text,",","\\,")
|
||||
end
|
||||
return text
|
||||
-- Use explicit character set instead of dot here because it doubles the performance
|
||||
return text and string.gsub(text, "[\\%[%];,]", formspec_escapes)
|
||||
end
|
||||
|
||||
|
||||
@@ -265,18 +265,21 @@ function core.wrap_text(text, max_length, as_table)
|
||||
return as_table and {text} or text
|
||||
end
|
||||
|
||||
for word in text:gmatch('%S+') do
|
||||
local cur_length = #table.concat(line, ' ')
|
||||
if cur_length > 0 and cur_length + #word + 1 >= max_length then
|
||||
local line_length = 0
|
||||
for word in text:gmatch("%S+") do
|
||||
if line_length > 0 and line_length + #word + 1 >= max_length then
|
||||
-- word wouldn't fit on current line, move to next line
|
||||
table.insert(result, table.concat(line, ' '))
|
||||
line = {}
|
||||
table.insert(result, table.concat(line, " "))
|
||||
line = {word}
|
||||
line_length = #word
|
||||
else
|
||||
table.insert(line, word)
|
||||
line_length = line_length + 1 + #word
|
||||
end
|
||||
table.insert(line, word)
|
||||
end
|
||||
|
||||
table.insert(result, table.concat(line, ' '))
|
||||
return as_table and result or table.concat(result, '\n')
|
||||
table.insert(result, table.concat(line, " "))
|
||||
return as_table and result or table.concat(result, "\n")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -425,54 +428,50 @@ function core.string_to_pos(value)
|
||||
return nil
|
||||
end
|
||||
|
||||
local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
|
||||
if x and y and z then
|
||||
x = tonumber(x)
|
||||
y = tonumber(y)
|
||||
z = tonumber(z)
|
||||
return vector.new(x, y, z)
|
||||
end
|
||||
x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
|
||||
value = value:match("^%((.-)%)$") or value -- strip parentheses
|
||||
|
||||
local x, y, z = value:trim():match("^([%d.-]+)[,%s]%s*([%d.-]+)[,%s]%s*([%d.-]+)$")
|
||||
if x and y and z then
|
||||
x = tonumber(x)
|
||||
y = tonumber(y)
|
||||
z = tonumber(z)
|
||||
return vector.new(x, y, z)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function core.string_to_area(value)
|
||||
local p1, p2 = unpack(value:split(") ("))
|
||||
if p1 == nil or p2 == nil then
|
||||
return nil
|
||||
|
||||
do
|
||||
local rel_num_cap = "(~?-?%d*%.?%d*)" -- may be overly permissive as this will be tonumber'ed anyways
|
||||
local num_delim = "[,%s]%s*"
|
||||
local pattern = "^" .. table.concat({rel_num_cap, rel_num_cap, rel_num_cap}, num_delim) .. "$"
|
||||
|
||||
local function parse_area_string(pos, relative_to)
|
||||
local pp = {}
|
||||
pp.x, pp.y, pp.z = pos:trim():match(pattern)
|
||||
return core.parse_coordinates(pp.x, pp.y, pp.z, relative_to)
|
||||
end
|
||||
|
||||
p1 = core.string_to_pos(p1 .. ")")
|
||||
p2 = core.string_to_pos("(" .. p2)
|
||||
if p1 == nil or p2 == nil then
|
||||
return nil
|
||||
function core.string_to_area(value, relative_to)
|
||||
local p1, p2 = value:match("^%((.-)%)%s*%((.-)%)$")
|
||||
if not p1 then
|
||||
return
|
||||
end
|
||||
|
||||
p1 = parse_area_string(p1, relative_to)
|
||||
p2 = parse_area_string(p2, relative_to)
|
||||
|
||||
if p1 == nil or p2 == nil then
|
||||
return
|
||||
end
|
||||
|
||||
return p1, p2
|
||||
end
|
||||
|
||||
return p1, p2
|
||||
end
|
||||
|
||||
local function test_string_to_area()
|
||||
local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)")
|
||||
assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2)
|
||||
assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
|
||||
|
||||
p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end
|
||||
|
||||
test_string_to_area()
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function table.copy(t, seen)
|
||||
local n = {}
|
||||
@@ -701,3 +700,71 @@ end
|
||||
function core.is_nan(number)
|
||||
return number ~= number
|
||||
end
|
||||
|
||||
--[[ Helper function for parsing an optionally relative number
|
||||
of a chat command parameter, using the chat command tilde notation.
|
||||
|
||||
Parameters:
|
||||
* arg: String snippet containing the number; possible values:
|
||||
* "<number>": return as number
|
||||
* "~<number>": return relative_to + <number>
|
||||
* "~": return relative_to
|
||||
* Anything else will return `nil`
|
||||
* relative_to: Number to which the `arg` number might be relative to
|
||||
|
||||
Returns:
|
||||
A number or `nil`, depending on `arg.
|
||||
|
||||
Examples:
|
||||
* `core.parse_relative_number("5", 10)` returns 5
|
||||
* `core.parse_relative_number("~5", 10)` returns 15
|
||||
* `core.parse_relative_number("~", 10)` returns 10
|
||||
]]
|
||||
function core.parse_relative_number(arg, relative_to)
|
||||
if not arg then
|
||||
return nil
|
||||
elseif arg == "~" then
|
||||
return relative_to
|
||||
elseif string.sub(arg, 1, 1) == "~" then
|
||||
local number = tonumber(string.sub(arg, 2))
|
||||
if not number then
|
||||
return nil
|
||||
end
|
||||
if core.is_nan(number) or number == math.huge or number == -math.huge then
|
||||
return nil
|
||||
end
|
||||
return relative_to + number
|
||||
else
|
||||
local number = tonumber(arg)
|
||||
if core.is_nan(number) or number == math.huge or number == -math.huge then
|
||||
return nil
|
||||
end
|
||||
return number
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Helper function to parse coordinates that might be relative
|
||||
to another position; supports chat command tilde notation.
|
||||
Intended to be used in chat command parameter parsing.
|
||||
|
||||
Parameters:
|
||||
* x, y, z: Parsed x, y, and z coordinates as strings
|
||||
* relative_to: Position to which to compare the position
|
||||
|
||||
Syntax of x, y and z:
|
||||
* "<number>": return as number
|
||||
* "~<number>": return <number> + player position on this axis
|
||||
* "~": return player position on this axis
|
||||
|
||||
Returns: a vector or nil for invalid input or if player does not exist
|
||||
]]
|
||||
function core.parse_coordinates(x, y, z, relative_to)
|
||||
if not relative_to then
|
||||
x, y, z = tonumber(x), tonumber(y), tonumber(z)
|
||||
return x and y and z and { x = x, y = y, z = z }
|
||||
end
|
||||
local rx = core.parse_relative_number(x, relative_to.x)
|
||||
local ry = core.parse_relative_number(y, relative_to.y)
|
||||
local rz = core.parse_relative_number(z, relative_to.z)
|
||||
return rx and ry and rz and { x = rx, y = ry, z = rz }
|
||||
end
|
||||
|
||||
19
builtin/common/mod_storage.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Modify core.get_mod_storage to return the storage for the current mod.
|
||||
|
||||
local get_current_modname = core.get_current_modname
|
||||
|
||||
local old_get_mod_storage = core.get_mod_storage
|
||||
|
||||
local storages = setmetatable({}, {
|
||||
__mode = "v", -- values are weak references (can be garbage-collected)
|
||||
__index = function(self, modname)
|
||||
local storage = old_get_mod_storage(modname)
|
||||
self[modname] = storage
|
||||
return storage
|
||||
end,
|
||||
})
|
||||
|
||||
function core.get_mod_storage()
|
||||
local modname = get_current_modname()
|
||||
return modname and storages[modname]
|
||||
end
|
||||
@@ -1,205 +1,238 @@
|
||||
--- Lua module to serialize values as Lua code.
|
||||
-- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
|
||||
-- From: https://github.com/appgurueu/modlib/blob/master/luon.lua
|
||||
-- License: MIT
|
||||
-- @copyright 2006-2997 Fabien Fleutot <metalua@gmail.com>
|
||||
-- @author Fabien Fleutot <metalua@gmail.com>
|
||||
-- @author ShadowNinja <shadowninja@minetest.net>
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--- Serialize an object into a source code string. This string, when passed as
|
||||
-- an argument to deserialize(), returns an object structurally identical to
|
||||
-- the original one. The following are currently supported:
|
||||
-- * Booleans, numbers, strings, and nil.
|
||||
-- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode!
|
||||
-- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved.
|
||||
-- This works in two phases:
|
||||
-- 1. Recursively find and record multiple references and recursion.
|
||||
-- 2. Recursively dump the value into a string.
|
||||
-- @param x Value to serialize (nil is allowed).
|
||||
-- @return load()able string containing the value.
|
||||
function core.serialize(x)
|
||||
local local_index = 1 -- Top index of the "_" local table in the dump
|
||||
-- table->nil/1/2 set of tables seen.
|
||||
-- nil = not seen, 1 = seen once, 2 = seen multiple times.
|
||||
local seen = {}
|
||||
local next, rawget, pairs, pcall, error, type, setfenv, loadstring
|
||||
= next, rawget, pairs, pcall, error, type, setfenv, loadstring
|
||||
|
||||
-- nest_points are places where a table appears within itself, directly
|
||||
-- or not. For instance, all of these chunks create nest points in
|
||||
-- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x",
|
||||
-- "x = {}; x[1] = {y = {x}}".
|
||||
-- To handle those, two tables are used by mark_nest_point:
|
||||
-- * nested - Transient set of tables being currently traversed.
|
||||
-- Used for detecting nested tables.
|
||||
-- * nest_points - parent->{key=value, ...} table cantaining the nested
|
||||
-- keys and values in the parent. They're all dumped after all the
|
||||
-- other table operations have been performed.
|
||||
--
|
||||
-- mark_nest_point(p, k, v) fills nest_points with information required
|
||||
-- to remember that key/value (k, v) creates a nest point in table
|
||||
-- parent. It also marks "parent" and the nested item(s) as occuring
|
||||
-- multiple times, since several references to it will be required in
|
||||
-- order to patch the nest points.
|
||||
local nest_points = {}
|
||||
local nested = {}
|
||||
local function mark_nest_point(parent, k, v)
|
||||
local nk, nv = nested[k], nested[v]
|
||||
local np = nest_points[parent]
|
||||
if not np then
|
||||
np = {}
|
||||
nest_points[parent] = np
|
||||
end
|
||||
np[k] = v
|
||||
seen[parent] = 2
|
||||
if nk then seen[k] = 2 end
|
||||
if nv then seen[v] = 2 end
|
||||
local table_concat, string_dump, string_format, string_match, math_huge
|
||||
= table.concat, string.dump, string.format, string.match, math.huge
|
||||
|
||||
-- Recursively counts occurences of objects (non-primitives including strings) in a table.
|
||||
local function count_objects(value)
|
||||
local counts = {}
|
||||
if value == nil then
|
||||
-- Early return for nil; tables can't contain nil
|
||||
return counts
|
||||
end
|
||||
|
||||
-- First phase, list the tables and functions which appear more than
|
||||
-- once in x.
|
||||
local function mark_multiple_occurences(x)
|
||||
local tp = type(x)
|
||||
if tp ~= "table" and tp ~= "function" then
|
||||
-- No identity (comparison is done by value, not by instance)
|
||||
local function count_values(val)
|
||||
local type_ = type(val)
|
||||
if type_ == "boolean" or type_ == "number" then
|
||||
return
|
||||
end
|
||||
if seen[x] == 1 then
|
||||
seen[x] = 2
|
||||
elseif seen[x] ~= 2 then
|
||||
seen[x] = 1
|
||||
end
|
||||
|
||||
if tp == "table" then
|
||||
nested[x] = true
|
||||
for k, v in pairs(x) do
|
||||
if nested[k] or nested[v] then
|
||||
mark_nest_point(x, k, v)
|
||||
else
|
||||
mark_multiple_occurences(k)
|
||||
mark_multiple_occurences(v)
|
||||
local count = counts[val]
|
||||
counts[val] = (count or 0) + 1
|
||||
if type_ == "table" then
|
||||
if not count then
|
||||
for k, v in pairs(val) do
|
||||
count_values(k)
|
||||
count_values(v)
|
||||
end
|
||||
end
|
||||
nested[x] = nil
|
||||
elseif type_ ~= "string" and type_ ~= "function" then
|
||||
error("unsupported type: " .. type_)
|
||||
end
|
||||
end
|
||||
|
||||
local dumped = {} -- object->varname set
|
||||
local local_defs = {} -- Dumped local definitions as source code lines
|
||||
|
||||
-- Mutually recursive local functions:
|
||||
local dump_val, dump_or_ref_val
|
||||
|
||||
-- If x occurs multiple times, dump the local variable rather than
|
||||
-- the value. If it's the first time it's dumped, also dump the
|
||||
-- content in local_defs.
|
||||
function dump_or_ref_val(x)
|
||||
if seen[x] ~= 2 then
|
||||
return dump_val(x)
|
||||
end
|
||||
local var = dumped[x]
|
||||
if var then -- Already referenced
|
||||
return var
|
||||
end
|
||||
-- First occurence, create and register reference
|
||||
local val = dump_val(x)
|
||||
local i = local_index
|
||||
local_index = local_index + 1
|
||||
var = "_["..i.."]"
|
||||
local_defs[#local_defs + 1] = var.." = "..val
|
||||
dumped[x] = var
|
||||
return var
|
||||
end
|
||||
|
||||
-- Second phase. Dump the object; subparts occuring multiple times
|
||||
-- are dumped in local variables which can be referenced multiple
|
||||
-- times. Care is taken to dump local vars in a sensible order.
|
||||
function dump_val(x)
|
||||
local tp = type(x)
|
||||
if x == nil then return "nil"
|
||||
elseif tp == "string" then return string.format("%q", x)
|
||||
elseif tp == "boolean" then return x and "true" or "false"
|
||||
elseif tp == "function" then
|
||||
return string.format("loadstring(%q)", string.dump(x))
|
||||
elseif tp == "number" then
|
||||
-- Serialize numbers reversibly with string.format
|
||||
return string.format("%.17g", x)
|
||||
elseif tp == "table" then
|
||||
local vals = {}
|
||||
local idx_dumped = {}
|
||||
local np = nest_points[x]
|
||||
for i, v in ipairs(x) do
|
||||
if not np or not np[i] then
|
||||
vals[#vals + 1] = dump_or_ref_val(v)
|
||||
end
|
||||
idx_dumped[i] = true
|
||||
end
|
||||
for k, v in pairs(x) do
|
||||
if (not np or not np[k]) and
|
||||
not idx_dumped[k] then
|
||||
vals[#vals + 1] = "["..dump_or_ref_val(k).."] = "
|
||||
..dump_or_ref_val(v)
|
||||
end
|
||||
end
|
||||
return "{"..table.concat(vals, ", ").."}"
|
||||
else
|
||||
error("Can't serialize data of type "..tp)
|
||||
end
|
||||
end
|
||||
|
||||
local function dump_nest_points()
|
||||
for parent, vals in pairs(nest_points) do
|
||||
for k, v in pairs(vals) do
|
||||
local_defs[#local_defs + 1] = dump_or_ref_val(parent)
|
||||
.."["..dump_or_ref_val(k).."] = "
|
||||
..dump_or_ref_val(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mark_multiple_occurences(x)
|
||||
local top_level = dump_or_ref_val(x)
|
||||
dump_nest_points()
|
||||
|
||||
if next(local_defs) then
|
||||
return "local _ = {}\n"
|
||||
..table.concat(local_defs, "\n")
|
||||
.."\nreturn "..top_level
|
||||
else
|
||||
return "return "..top_level
|
||||
end
|
||||
count_values(value)
|
||||
return counts
|
||||
end
|
||||
|
||||
-- Deserialization
|
||||
-- Build a "set" of Lua keywords. These can't be used as short key names.
|
||||
-- See https://www.lua.org/manual/5.1/manual.html#2.1
|
||||
local keywords = {}
|
||||
for _, keyword in pairs({
|
||||
"and", "break", "do", "else", "elseif",
|
||||
"end", "false", "for", "function", "if",
|
||||
"in", "local", "nil", "not", "or",
|
||||
"repeat", "return", "then", "true", "until", "while",
|
||||
"goto" -- LuaJIT, Lua 5.2+
|
||||
}) do
|
||||
keywords[keyword] = true
|
||||
end
|
||||
|
||||
local function safe_loadstring(...)
|
||||
local func, err = loadstring(...)
|
||||
if func then
|
||||
setfenv(func, {})
|
||||
return func
|
||||
local function quote(string)
|
||||
return string_format("%q", string)
|
||||
end
|
||||
|
||||
local function dump_func(func)
|
||||
return string_format("loadstring(%q)", string_dump(func))
|
||||
end
|
||||
|
||||
-- Serializes Lua nil, booleans, numbers, strings, tables and even functions
|
||||
-- Tables are referenced by reference, strings are referenced by value. Supports circular tables.
|
||||
local function serialize(value, write)
|
||||
local reference, refnum = "1", 1
|
||||
-- [object] = reference
|
||||
local references = {}
|
||||
-- Circular tables that must be filled using `table[key] = value` statements
|
||||
local to_fill = {}
|
||||
for object, count in pairs(count_objects(value)) do
|
||||
local type_ = type(object)
|
||||
-- Object must appear more than once. If it is a string, the reference has to be shorter than the string.
|
||||
if count >= 2 and (type_ ~= "string" or #reference + 5 < #object) then
|
||||
if refnum == 1 then
|
||||
write"local _={};" -- initialize reference table
|
||||
end
|
||||
write"_["
|
||||
write(reference)
|
||||
write("]=")
|
||||
if type_ == "table" then
|
||||
write("{}")
|
||||
elseif type_ == "function" then
|
||||
write(dump_func(object))
|
||||
elseif type_ == "string" then
|
||||
write(quote(object))
|
||||
end
|
||||
write(";")
|
||||
references[object] = reference
|
||||
if type_ == "table" then
|
||||
to_fill[object] = reference
|
||||
end
|
||||
refnum = refnum + 1
|
||||
reference = ("%d"):format(refnum)
|
||||
end
|
||||
end
|
||||
return nil, err
|
||||
-- Used to decide whether we should do "key=..."
|
||||
local function use_short_key(key)
|
||||
return not references[key] and type(key) == "string" and (not keywords[key]) and string_match(key, "^[%a_][%a%d_]*$")
|
||||
end
|
||||
local function dump(value)
|
||||
-- Primitive types
|
||||
if value == nil then
|
||||
return write("nil")
|
||||
end
|
||||
if value == true then
|
||||
return write("true")
|
||||
end
|
||||
if value == false then
|
||||
return write("false")
|
||||
end
|
||||
local type_ = type(value)
|
||||
if type_ == "number" then
|
||||
if value ~= value then -- nan
|
||||
return write"0/0"
|
||||
elseif value == math_huge then
|
||||
return write"1/0"
|
||||
elseif value == -math_huge then
|
||||
return write"-1/0"
|
||||
else
|
||||
return write(string_format("%.17g", value))
|
||||
end
|
||||
end
|
||||
-- Reference types: table, function and string
|
||||
local ref = references[value]
|
||||
if ref then
|
||||
write"_["
|
||||
write(ref)
|
||||
return write"]"
|
||||
end
|
||||
if type_ == "string" then
|
||||
return write(quote(value))
|
||||
end
|
||||
if type_ == "function" then
|
||||
return write(dump_func(value))
|
||||
end
|
||||
if type_ == "table" then
|
||||
write("{")
|
||||
-- First write list keys:
|
||||
-- Don't use the table length #value here as it may horribly fail
|
||||
-- for tables which use large integers as keys in the hash part;
|
||||
-- stop at the first "hole" (nil value) instead
|
||||
local len = 0
|
||||
local first = true -- whether this is the first entry, which may not have a leading comma
|
||||
while true do
|
||||
local v = rawget(value, len + 1) -- use rawget to avoid metatables like the vector metatable
|
||||
if v == nil then break end
|
||||
if first then first = false else write(",") end
|
||||
dump(v)
|
||||
len = len + 1
|
||||
end
|
||||
-- Now write map keys ([key] = value)
|
||||
for k, v in next, value do
|
||||
-- We have written all non-float keys in [1, len] already
|
||||
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
|
||||
if first then first = false else write(",") end
|
||||
if use_short_key(k) then
|
||||
write(k)
|
||||
else
|
||||
write("[")
|
||||
dump(k)
|
||||
write("]")
|
||||
end
|
||||
write("=")
|
||||
dump(v)
|
||||
end
|
||||
end
|
||||
write("}")
|
||||
return
|
||||
end
|
||||
end
|
||||
-- Write the statements to fill circular tables
|
||||
for table, ref in pairs(to_fill) do
|
||||
for k, v in pairs(table) do
|
||||
write("_[")
|
||||
write(ref)
|
||||
write("]")
|
||||
if use_short_key(k) then
|
||||
write(".")
|
||||
write(k)
|
||||
else
|
||||
write("[")
|
||||
dump(k)
|
||||
write("]")
|
||||
end
|
||||
write("=")
|
||||
dump(v)
|
||||
write(";")
|
||||
end
|
||||
end
|
||||
write("return ")
|
||||
dump(value)
|
||||
end
|
||||
|
||||
function core.serialize(value)
|
||||
local rope = {}
|
||||
serialize(value, function(text)
|
||||
-- Faster than table.insert(rope, text) on PUC Lua 5.1
|
||||
rope[#rope + 1] = text
|
||||
end)
|
||||
return table_concat(rope)
|
||||
end
|
||||
|
||||
local function dummy_func() end
|
||||
|
||||
function core.deserialize(str, safe)
|
||||
if type(str) ~= "string" then
|
||||
return nil, "Cannot deserialize type '"..type(str)
|
||||
.."'. Argument must be a string."
|
||||
-- Backwards compatibility
|
||||
if str == nil then
|
||||
core.log("deprecated", "minetest.deserialize called with nil (expected string).")
|
||||
return nil, "Invalid type: Expected a string, got nil"
|
||||
end
|
||||
if str:byte(1) == 0x1B then
|
||||
return nil, "Bytecode prohibited"
|
||||
local t = type(str)
|
||||
if t ~= "string" then
|
||||
error(("minetest.deserialize called with %s (expected string)."):format(t))
|
||||
end
|
||||
local f, err = loadstring(str)
|
||||
if not f then return nil, err end
|
||||
|
||||
-- The environment is recreated every time so deseralized code cannot
|
||||
-- pollute it with permanent references.
|
||||
setfenv(f, {loadstring = safe and dummy_func or safe_loadstring})
|
||||
local func, err = loadstring(str)
|
||||
if not func then return nil, err end
|
||||
|
||||
local good, data = pcall(f)
|
||||
if good then
|
||||
return data
|
||||
-- math.huge was serialized to inf and NaNs to nan by Lua in Minetest 5.6, so we have to support this here
|
||||
local env = {inf = math_huge, nan = 0/0}
|
||||
if safe then
|
||||
env.loadstring = dummy_func
|
||||
else
|
||||
return nil, data
|
||||
env.loadstring = function(str, ...)
|
||||
local func, err = loadstring(str, ...)
|
||||
if func then
|
||||
setfenv(func, env)
|
||||
return func
|
||||
end
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
setfenv(func, env)
|
||||
local success, value_or_err = pcall(func)
|
||||
if success then
|
||||
return value_or_err
|
||||
end
|
||||
return nil, value_or_err
|
||||
end
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
|
||||
-- Always warn when creating a global variable, even outside of a function.
|
||||
-- This ignores mod namespaces (variables with the same name as the current mod).
|
||||
local WARN_INIT = false
|
||||
|
||||
local getinfo = debug.getinfo
|
||||
local getinfo, rawget, rawset = debug.getinfo, rawget, rawset
|
||||
|
||||
function core.global_exists(name)
|
||||
if type(name) ~= "string" then
|
||||
@@ -19,39 +14,33 @@ local declared = {}
|
||||
local warned = {}
|
||||
|
||||
function meta:__newindex(name, value)
|
||||
if declared[name] then
|
||||
return
|
||||
end
|
||||
local info = getinfo(2, "Sl")
|
||||
local desc = ("%s:%d"):format(info.short_src, info.currentline)
|
||||
if not declared[name] then
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source,
|
||||
info.currentline, name)
|
||||
if not warned[warn_key] and info.what ~= "main" and
|
||||
info.what ~= "C" then
|
||||
core.log("warning", ("Assignment to undeclared "..
|
||||
"global %q inside a function at %s.")
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
|
||||
core.log("warning", ("Assignment to undeclared global %q inside a function at %s.")
|
||||
:format(name, desc))
|
||||
warned[warn_key] = true
|
||||
end
|
||||
declared[name] = true
|
||||
end
|
||||
-- Ignore mod namespaces
|
||||
if WARN_INIT and name ~= core.get_current_modname() then
|
||||
core.log("warning", ("Global variable %q created at %s.")
|
||||
:format(name, desc))
|
||||
warned[warn_key] = true
|
||||
end
|
||||
rawset(self, name, value)
|
||||
declared[name] = true
|
||||
end
|
||||
|
||||
|
||||
function meta:__index(name)
|
||||
if declared[name] then
|
||||
return
|
||||
end
|
||||
local info = getinfo(2, "Sl")
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||
if not declared[name] and not warned[warn_key] and info.what ~= "C" then
|
||||
if not warned[warn_key] and info.what ~= "C" then
|
||||
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")
|
||||
:format(name, info.short_src, info.currentline))
|
||||
warned[warn_key] = true
|
||||
end
|
||||
return rawget(self, name)
|
||||
end
|
||||
|
||||
setmetatable(_G, meta)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
_G.core = {}
|
||||
_G.vector = {metatable = {}}
|
||||
dofile("builtin/common/vector.lua")
|
||||
dofile("builtin/common/misc_helpers.lua")
|
||||
|
||||
@@ -66,9 +67,107 @@ describe("pos", function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("area parsing", function()
|
||||
describe("valid inputs", function()
|
||||
it("accepts absolute numbers", function()
|
||||
local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2 4 -12.53)")
|
||||
assert(p1.x == 10 and p1.y == 5 and p1.z == -2)
|
||||
assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
|
||||
end)
|
||||
|
||||
it("accepts relative numbers", function()
|
||||
local p1, p2 = core.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})
|
||||
assert(type(p1) == "table" and type(p2) == "table")
|
||||
assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
|
||||
assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
|
||||
|
||||
p1, p2 = core.string_to_area("(1 2 3) (~5 ~-5 ~)", {x=10,y=10,z=10})
|
||||
assert(type(p1) == "table" and type(p2) == "table")
|
||||
assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
|
||||
assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
|
||||
end)
|
||||
end)
|
||||
describe("invalid inputs", function()
|
||||
it("rejects too few numbers", function()
|
||||
local p1, p2 = core.string_to_area("(1,1) (1,1,1,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("rejects too many numbers", function()
|
||||
local p1, p2 = core.string_to_area("(1,1,1,1) (1,1,1,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("rejects nan & inf", function()
|
||||
local p1, p2 = core.string_to_area("(1,1,1) (1,1,nan)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,1,~nan)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,~nan,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,1,inf)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,1,~inf)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,~inf,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("rejects words", function()
|
||||
local p1, p2 = core.string_to_area("bananas", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("bananas", "foobar")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("bananas")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(bananas,bananas,bananas)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(bananas,bananas,bananas) (bananas,bananas,bananas)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("requires parenthesis & valid numbers", function()
|
||||
local p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("table", function()
|
||||
it("indexof()", function()
|
||||
assert.equal(1, table.indexof({"foo", "bar"}, "foo"))
|
||||
assert.equal(-1, table.indexof({"foo", "bar"}, "baz"))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("formspec_escape", function()
|
||||
it("escapes", function()
|
||||
assert.equal(nil, core.formspec_escape(nil))
|
||||
assert.equal("", core.formspec_escape(""))
|
||||
assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\["))
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -1,42 +1,97 @@
|
||||
_G.core = {}
|
||||
_G.vector = {metatable = {}}
|
||||
|
||||
_G.setfenv = require 'busted.compatibility'.setfenv
|
||||
|
||||
dofile("builtin/common/serialize.lua")
|
||||
dofile("builtin/common/vector.lua")
|
||||
|
||||
describe("serialize", function()
|
||||
it("works", function()
|
||||
local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
-- Supports circular tables; does not support table keys
|
||||
-- Correctly checks whether a mapping of references ("same") exists
|
||||
-- Is significantly more efficient than assert.same
|
||||
local function assert_same(a, b, same)
|
||||
same = same or {}
|
||||
if same[a] or same[b] then
|
||||
assert(same[a] == b and same[b] == a)
|
||||
return
|
||||
end
|
||||
if a == b then
|
||||
return
|
||||
end
|
||||
if type(a) ~= "table" or type(b) ~= "table" then
|
||||
assert(a == b)
|
||||
return
|
||||
end
|
||||
same[a] = b
|
||||
same[b] = a
|
||||
local count = 0
|
||||
for k, v in pairs(a) do
|
||||
count = count + 1
|
||||
assert(type(k) ~= "table")
|
||||
assert_same(v, b[k], same)
|
||||
end
|
||||
for _ in pairs(b) do
|
||||
count = count - 1
|
||||
end
|
||||
assert(count == 0)
|
||||
end
|
||||
|
||||
assert.same(test_in, test_out)
|
||||
local x, y = {}, {}
|
||||
local t1, t2 = {x, x, y, y}, {x, y, x, y}
|
||||
assert.same(t1, t2) -- will succeed because it only checks whether the depths match
|
||||
assert(not pcall(assert_same, t1, t2)) -- will correctly fail because it checks whether the refs match
|
||||
|
||||
describe("serialize", function()
|
||||
local function assert_preserves(value)
|
||||
local preserved_value = core.deserialize(core.serialize(value))
|
||||
assert_same(value, preserved_value)
|
||||
end
|
||||
it("works", function()
|
||||
assert_preserves({cat={sound="nyan", speed=400}, dog={sound="woof"}})
|
||||
end)
|
||||
|
||||
it("handles characters", function()
|
||||
local test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
assert_preserves({escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"})
|
||||
end)
|
||||
|
||||
it("handles NaN & infinities", function()
|
||||
local nan = core.deserialize(core.serialize(0/0))
|
||||
assert(nan ~= nan)
|
||||
assert_preserves(math.huge)
|
||||
assert_preserves(-math.huge)
|
||||
end)
|
||||
|
||||
it("handles precise numbers", function()
|
||||
local test_in = 0.2695949158945771
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
assert_preserves(0.2695949158945771)
|
||||
end)
|
||||
|
||||
it("handles big integers", function()
|
||||
local test_in = 269594915894577
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
assert_preserves(269594915894577)
|
||||
end)
|
||||
|
||||
it("handles recursive structures", function()
|
||||
local test_in = { hello = "world" }
|
||||
test_in.foo = test_in
|
||||
assert_preserves(test_in)
|
||||
end)
|
||||
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
it("handles cross-referencing structures", function()
|
||||
local test_in = {
|
||||
foo = {
|
||||
baz = {
|
||||
{}
|
||||
},
|
||||
},
|
||||
bar = {
|
||||
baz = {},
|
||||
},
|
||||
}
|
||||
|
||||
test_in.foo.baz[1].foo = test_in.foo
|
||||
test_in.foo.baz[1].bar = test_in.bar
|
||||
test_in.bar.baz[1] = test_in.foo.baz[1]
|
||||
|
||||
assert_preserves(test_in)
|
||||
end)
|
||||
|
||||
it("strips functions in safe mode", function()
|
||||
@@ -46,6 +101,7 @@ describe("serialize", function()
|
||||
end,
|
||||
foo = "bar"
|
||||
}
|
||||
setfenv(test_in.func, _G)
|
||||
|
||||
local str = core.serialize(test_in)
|
||||
assert.not_nil(str:find("loadstring"))
|
||||
@@ -57,13 +113,77 @@ describe("serialize", function()
|
||||
|
||||
it("vectors work", function()
|
||||
local v = vector.new(1, 2, 3)
|
||||
assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v})))
|
||||
assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v)))
|
||||
assert_preserves({v})
|
||||
assert_preserves(v)
|
||||
|
||||
-- abuse
|
||||
v = vector.new(1, 2, 3)
|
||||
v.a = "bla"
|
||||
assert.same({x = 1, y = 2, z = 3, a = "bla"},
|
||||
core.deserialize(core.serialize(v)))
|
||||
assert_preserves(v)
|
||||
end)
|
||||
|
||||
it("handles keywords as keys", function()
|
||||
assert_preserves({["and"] = "keyword", ["for"] = "keyword"})
|
||||
end)
|
||||
|
||||
describe("fuzzing", function()
|
||||
local atomics = {true, false, math.huge, -math.huge} -- no NaN or nil
|
||||
local function atomic()
|
||||
return atomics[math.random(1, #atomics)]
|
||||
end
|
||||
local function num()
|
||||
local sign = math.random() < 0.5 and -1 or 1
|
||||
-- HACK math.random(a, b) requires a, b & b - a to fit within a 32-bit int
|
||||
-- Use two random calls to generate a random number from 0 - 2^50 as lower & upper 25 bits
|
||||
local val = math.random(0, 2^25) * 2^25 + math.random(0, 2^25 - 1)
|
||||
local exp = math.random() < 0.5 and 1 or 2^(math.random(-120, 120))
|
||||
return sign * val * exp
|
||||
end
|
||||
local function charcodes(count)
|
||||
if count == 0 then return end
|
||||
return math.random(0, 0xFF), charcodes(count - 1)
|
||||
end
|
||||
local function str()
|
||||
return string.char(charcodes(math.random(0, 100)))
|
||||
end
|
||||
local primitives = {atomic, num, str}
|
||||
local function primitive()
|
||||
return primitives[math.random(1, #primitives)]()
|
||||
end
|
||||
local function tab(max_actions)
|
||||
local root = {}
|
||||
local tables = {root}
|
||||
local function random_table()
|
||||
return tables[math.random(1, #tables)]
|
||||
end
|
||||
for _ = 1, math.random(1, max_actions) do
|
||||
local tab = random_table()
|
||||
local value
|
||||
if math.random() < 0.5 then
|
||||
if math.random() < 0.5 then
|
||||
value = random_table()
|
||||
else
|
||||
value = {}
|
||||
table.insert(tables, value)
|
||||
end
|
||||
else
|
||||
value = primitive()
|
||||
end
|
||||
tab[math.random() < 0.5 and (#tab + 1) or primitive()] = value
|
||||
end
|
||||
return root
|
||||
end
|
||||
it("primitives work", function()
|
||||
for _ = 1, 1e3 do
|
||||
assert_preserves(primitive())
|
||||
end
|
||||
end)
|
||||
it("tables work", function()
|
||||
for _ = 1, 100 do
|
||||
local fuzzed_table = tab(1e3)
|
||||
assert_same(fuzzed_table, table.copy(fuzzed_table))
|
||||
assert_preserves(fuzzed_table)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_G.vector = {}
|
||||
_G.vector = {metatable = {}}
|
||||
dofile("builtin/common/vector.lua")
|
||||
|
||||
describe("vector", function()
|
||||
@@ -128,6 +128,14 @@ describe("vector", function()
|
||||
assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
|
||||
end)
|
||||
|
||||
it("combine()", function()
|
||||
local a = vector.new(1, 2, 3)
|
||||
local b = vector.new(3, 2, 1)
|
||||
assert.equal(vector.add(a, b), vector.combine(a, b, function(x, y) return x + y end))
|
||||
assert.equal(vector.new(3, 2, 3), vector.combine(a, b, math.max))
|
||||
assert.equal(vector.new(1, 2, 1), vector.combine(a, b, math.min))
|
||||
end)
|
||||
|
||||
it("equals()", function()
|
||||
local function assertE(a, b)
|
||||
assert.is_true(vector.equals(a, b))
|
||||
|
||||
@@ -6,10 +6,8 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta
|
||||
-- localize functions
|
||||
local setmetatable = setmetatable
|
||||
|
||||
vector = {}
|
||||
|
||||
local metatable = {}
|
||||
vector.metatable = metatable
|
||||
-- vector.metatable is set by C++.
|
||||
local metatable = vector.metatable
|
||||
|
||||
local xyz = {"x", "y", "z"}
|
||||
|
||||
@@ -112,6 +110,14 @@ function vector.apply(v, func)
|
||||
)
|
||||
end
|
||||
|
||||
function vector.combine(a, b, func)
|
||||
return fast_new(
|
||||
func(a.x, b.x),
|
||||
func(a.y, b.y),
|
||||
func(a.z, b.z)
|
||||
)
|
||||
end
|
||||
|
||||
function vector.distance(a, b)
|
||||
local x = a.x - b.x
|
||||
local y = a.y - b.y
|
||||
|
||||
22
builtin/game/async.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
core.async_jobs = {}
|
||||
|
||||
function core.async_event_handler(jobid, retval)
|
||||
local callback = core.async_jobs[jobid]
|
||||
assert(type(callback) == "function")
|
||||
callback(unpack(retval, 1, retval.n))
|
||||
core.async_jobs[jobid] = nil
|
||||
end
|
||||
|
||||
function core.handle_async(func, callback, ...)
|
||||
assert(type(func) == "function" and type(callback) == "function",
|
||||
"Invalid minetest.handle_async invocation")
|
||||
local args = {n = select("#", ...), ...}
|
||||
local mod_origin = core.get_last_run_mod()
|
||||
|
||||
local jobid = core.do_async_callback(func, args, mod_origin)
|
||||
core.async_jobs[jobid] = callback
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -130,8 +130,13 @@ local function parse_range_str(player_name, str)
|
||||
return false, S("Unable to get position of player @1.", player_name)
|
||||
end
|
||||
else
|
||||
p1, p2 = core.string_to_area(str)
|
||||
if p1 == nil then
|
||||
local player = core.get_player_by_name(player_name)
|
||||
local relpos
|
||||
if player then
|
||||
relpos = player:get_pos()
|
||||
end
|
||||
p1, p2 = core.string_to_area(str, relpos)
|
||||
if p1 == nil or p2 == nil then
|
||||
return false, S("Incorrect area format. "
|
||||
.. "Expected: (x1,y1,z1) (x2,y2,z2)")
|
||||
end
|
||||
@@ -310,12 +315,7 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
|
||||
and revokename == core.settings:get("name")
|
||||
and revokename ~= ""
|
||||
if revokeprivstr == "all" then
|
||||
revokeprivs = privs
|
||||
privs = {}
|
||||
else
|
||||
for priv, _ in pairs(revokeprivs) do
|
||||
privs[priv] = nil
|
||||
end
|
||||
revokeprivs = table.copy(privs)
|
||||
end
|
||||
|
||||
local privs_unknown = ""
|
||||
@@ -332,7 +332,10 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
|
||||
end
|
||||
local def = core.registered_privileges[priv]
|
||||
if not def then
|
||||
privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
|
||||
-- Old/removed privileges might still be granted to certain players
|
||||
if not privs[priv] then
|
||||
privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
|
||||
end
|
||||
elseif is_singleplayer and def.give_to_singleplayer then
|
||||
irrevokable[priv] = true
|
||||
elseif is_admin and def.give_to_admin then
|
||||
@@ -359,19 +362,22 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
|
||||
end
|
||||
|
||||
local revokecount = 0
|
||||
for priv, _ in pairs(revokeprivs) do
|
||||
privs[priv] = nil
|
||||
revokecount = revokecount + 1
|
||||
end
|
||||
|
||||
if revokecount == 0 then
|
||||
return false, S("No privileges were revoked.")
|
||||
end
|
||||
|
||||
core.set_player_privs(revokename, privs)
|
||||
for priv, _ in pairs(revokeprivs) do
|
||||
-- call the on_revoke callbacks
|
||||
core.run_priv_callbacks(revokename, priv, caller, "revoke")
|
||||
revokecount = revokecount + 1
|
||||
end
|
||||
local new_privs = core.get_player_privs(revokename)
|
||||
|
||||
if revokecount == 0 then
|
||||
return false, S("No privileges were revoked.")
|
||||
end
|
||||
|
||||
core.log("action", caller..' revoked ('
|
||||
..core.privs_to_string(revokeprivs, ', ')
|
||||
..') privileges from '..revokename)
|
||||
@@ -569,10 +575,15 @@ core.register_chatcommand("teleport", {
|
||||
description = S("Teleport to position or player"),
|
||||
privs = {teleport=true},
|
||||
func = function(name, param)
|
||||
local player = core.get_player_by_name(name)
|
||||
local relpos
|
||||
if player then
|
||||
relpos = player:get_pos()
|
||||
end
|
||||
local p = {}
|
||||
p.x, p.y, p.z = param:match("^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
|
||||
p = vector.apply(p, tonumber)
|
||||
if p.x and p.y and p.z then
|
||||
p.x, p.y, p.z = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
|
||||
p = core.parse_coordinates(p.x, p.y, p.z, relpos)
|
||||
if p and p.x and p.y and p.z then
|
||||
return teleport_to_pos(name, p)
|
||||
end
|
||||
|
||||
@@ -586,9 +597,19 @@ core.register_chatcommand("teleport", {
|
||||
"other players (missing privilege: @1).", "bring")
|
||||
|
||||
local teleportee_name
|
||||
p = {}
|
||||
teleportee_name, p.x, p.y, p.z = param:match(
|
||||
"^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
|
||||
"^([^ ]+) +([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
|
||||
if teleportee_name then
|
||||
local teleportee = core.get_player_by_name(teleportee_name)
|
||||
if not teleportee then
|
||||
return
|
||||
end
|
||||
relpos = teleportee:get_pos()
|
||||
p = core.parse_coordinates(p.x, p.y, p.z, relpos)
|
||||
end
|
||||
p = vector.apply(p, tonumber)
|
||||
|
||||
if teleportee_name and p.x and p.y and p.z then
|
||||
if not has_bring_priv then
|
||||
return false, missing_bring_msg
|
||||
@@ -621,6 +642,10 @@ core.register_chatcommand("set", {
|
||||
|
||||
setname, setvalue = string.match(param, "([^ ]+) (.+)")
|
||||
if setname and setvalue then
|
||||
if setname:sub(1, 7) == "secure." then
|
||||
return false, S("Failed. Cannot modify secure settings. "
|
||||
.. "Edit the settings file manually.")
|
||||
end
|
||||
if not core.settings:get(setname) then
|
||||
return false, S("Failed. Use '/set -n <name> <value>' "
|
||||
.. "to create a new setting.")
|
||||
@@ -837,7 +862,7 @@ core.register_chatcommand("spawnentity", {
|
||||
description = S("Spawn entity at given (or your) position"),
|
||||
privs = {give=true, interact=true},
|
||||
func = function(name, param)
|
||||
local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
|
||||
local entityname, pstr = string.match(param, "^([^ ]+) *(.*)$")
|
||||
if not entityname then
|
||||
return false, S("EntityName required.")
|
||||
end
|
||||
@@ -851,11 +876,15 @@ core.register_chatcommand("spawnentity", {
|
||||
if not core.registered_entities[entityname] then
|
||||
return false, S("Cannot spawn an unknown entity.")
|
||||
end
|
||||
if p == "" then
|
||||
local p
|
||||
if pstr == "" then
|
||||
p = player:get_pos()
|
||||
else
|
||||
p = core.string_to_pos(p)
|
||||
if p == nil then
|
||||
p = {}
|
||||
p.x, p.y, p.z = string.match(pstr, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
|
||||
local relpos = player:get_pos()
|
||||
p = core.parse_coordinates(p.x, p.y, p.z, relpos)
|
||||
if not (p and p.x and p.y and p.z) then
|
||||
return false, S("Invalid parameters (@1).", param)
|
||||
end
|
||||
end
|
||||
@@ -1014,6 +1043,13 @@ core.register_chatcommand("status", {
|
||||
end,
|
||||
})
|
||||
|
||||
local function get_time(timeofday)
|
||||
local time = math.floor(timeofday * 1440)
|
||||
local minute = time % 60
|
||||
local hour = (time - minute) / 60
|
||||
return time, hour, minute
|
||||
end
|
||||
|
||||
core.register_chatcommand("time", {
|
||||
params = S("[<0..23>:<0..59> | <0..24000>]"),
|
||||
description = S("Show or set time of day"),
|
||||
@@ -1032,9 +1068,14 @@ core.register_chatcommand("time", {
|
||||
return false, S("You don't have permission to run "
|
||||
.. "this command (missing privilege: @1).", "settime")
|
||||
end
|
||||
local hour, minute = param:match("^(%d+):(%d+)$")
|
||||
if not hour then
|
||||
local new_time = tonumber(param) or -1
|
||||
local relative, negative, hour, minute = param:match("^(~?)(%-?)(%d+):(%d+)$")
|
||||
if not relative then -- checking the first capture against nil suffices
|
||||
local new_time = core.parse_relative_number(param, core.get_timeofday() * 24000)
|
||||
if not new_time then
|
||||
new_time = tonumber(param) or -1
|
||||
else
|
||||
new_time = new_time % 24000
|
||||
end
|
||||
if new_time ~= new_time or new_time < 0 or new_time > 24000 then
|
||||
return false, S("Invalid time (must be between 0 and 24000).")
|
||||
end
|
||||
@@ -1042,14 +1083,29 @@ core.register_chatcommand("time", {
|
||||
core.log("action", name .. " sets time to " .. new_time)
|
||||
return true, S("Time of day changed.")
|
||||
end
|
||||
local new_time
|
||||
hour = tonumber(hour)
|
||||
minute = tonumber(minute)
|
||||
if hour < 0 or hour > 23 then
|
||||
return false, S("Invalid hour (must be between 0 and 23 inclusive).")
|
||||
elseif minute < 0 or minute > 59 then
|
||||
return false, S("Invalid minute (must be between 0 and 59 inclusive).")
|
||||
if relative == "" then
|
||||
if hour < 0 or hour > 23 then
|
||||
return false, S("Invalid hour (must be between 0 and 23 inclusive).")
|
||||
elseif minute < 0 or minute > 59 then
|
||||
return false, S("Invalid minute (must be between 0 and 59 inclusive).")
|
||||
end
|
||||
new_time = (hour * 60 + minute) / 1440
|
||||
else
|
||||
if minute < 0 or minute > 59 then
|
||||
return false, S("Invalid minute (must be between 0 and 59 inclusive).")
|
||||
end
|
||||
local current_time = core.get_timeofday()
|
||||
if negative == "-" then -- negative time
|
||||
hour, minute = -hour, -minute
|
||||
end
|
||||
new_time = (current_time + (hour * 60 + minute) / 1440) % 1
|
||||
local _
|
||||
_, hour, minute = get_time(new_time)
|
||||
end
|
||||
core.set_timeofday((hour * 60 + minute) / 1440)
|
||||
core.set_timeofday(new_time)
|
||||
core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
|
||||
return true, S("Time of day changed.")
|
||||
end,
|
||||
@@ -1131,6 +1187,9 @@ core.register_chatcommand("ban", {
|
||||
return true, S("Ban list: @1", ban_list)
|
||||
end
|
||||
end
|
||||
if core.is_singleplayer() then
|
||||
return false, S("You cannot ban players in singleplayer!")
|
||||
end
|
||||
if not core.get_player_by_name(param) then
|
||||
return false, S("Player is not online.")
|
||||
end
|
||||
|
||||
@@ -158,12 +158,10 @@ core.register_entity(":__builtin:falling_node", {
|
||||
or def.drawtype == "normal"
|
||||
or def.drawtype == "nodebox" then
|
||||
if (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") then
|
||||
local fdir = node.param2 % 32
|
||||
local fdir = node.param2 % 32 % 24
|
||||
-- Get rotation from a precalculated lookup table
|
||||
local euler = facedir_to_euler[fdir + 1]
|
||||
if euler then
|
||||
self.object:set_rotation(euler)
|
||||
end
|
||||
self.object:set_rotation(euler)
|
||||
elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and
|
||||
(def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then
|
||||
local rot = node.param2 % 8
|
||||
@@ -264,7 +262,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
end
|
||||
|
||||
-- Decide if we're replacing the node or placing on top
|
||||
local np = vector.new(bcp)
|
||||
local np = vector.copy(bcp)
|
||||
if bcd and bcd.buildable_to and
|
||||
(not self.floats or bcd.liquidtype == "none") then
|
||||
core.remove_node(bcp)
|
||||
@@ -436,7 +434,7 @@ local function drop_attached_node(p)
|
||||
if def and def.preserve_metadata then
|
||||
local oldmeta = core.get_meta(p):to_table().fields
|
||||
-- Copy pos and node because the callback can modify them.
|
||||
local pos_copy = vector.new(p)
|
||||
local pos_copy = vector.copy(p)
|
||||
local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
|
||||
local drop_stacks = {}
|
||||
for k, v in pairs(drops) do
|
||||
@@ -461,7 +459,7 @@ end
|
||||
|
||||
function builtin_shared.check_attached_node(p, n)
|
||||
local def = core.registered_nodes[n.name]
|
||||
local d = vector.new()
|
||||
local d = vector.zero()
|
||||
if def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted" then
|
||||
-- The fallback vector here is in case 'wallmounted to dir' is nil due
|
||||
|
||||
@@ -21,7 +21,9 @@ core.features = {
|
||||
use_texture_alpha_string_modes = true,
|
||||
degrotate_240_steps = true,
|
||||
abm_min_max_y = true,
|
||||
particlespawner_tweenable = true,
|
||||
dynamic_add_media_table = true,
|
||||
get_sky_as_table = true,
|
||||
}
|
||||
|
||||
function core.has_feature(arg)
|
||||
|
||||
@@ -8,6 +8,7 @@ local gamepath = scriptpath .. "game".. DIR_DELIM
|
||||
local builtin_shared = {}
|
||||
|
||||
dofile(gamepath .. "constants.lua")
|
||||
dofile(gamepath .. "item_s.lua")
|
||||
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
|
||||
dofile(gamepath .. "register.lua")
|
||||
|
||||
@@ -16,8 +17,10 @@ if core.settings:get_bool("profiler.load") then
|
||||
end
|
||||
|
||||
dofile(commonpath .. "after.lua")
|
||||
dofile(commonpath .. "mod_storage.lua")
|
||||
dofile(gamepath .. "item_entity.lua")
|
||||
dofile(gamepath .. "deprecated.lua")
|
||||
dofile(gamepath .. "misc_s.lua")
|
||||
dofile(gamepath .. "misc.lua")
|
||||
dofile(gamepath .. "privileges.lua")
|
||||
dofile(gamepath .. "auth.lua")
|
||||
@@ -32,5 +35,6 @@ dofile(gamepath .. "voxelarea.lua")
|
||||
dofile(gamepath .. "forceloading.lua")
|
||||
dofile(gamepath .. "statbars.lua")
|
||||
dofile(gamepath .. "knockback.lua")
|
||||
dofile(gamepath .. "async.lua")
|
||||
|
||||
profiler = nil
|
||||
|
||||
@@ -5,8 +5,8 @@ local builtin_shared = ...
|
||||
local function copy_pointed_thing(pointed_thing)
|
||||
return {
|
||||
type = pointed_thing.type,
|
||||
above = vector.new(pointed_thing.above),
|
||||
under = vector.new(pointed_thing.under),
|
||||
above = pointed_thing.above and vector.copy(pointed_thing.above),
|
||||
under = pointed_thing.under and vector.copy(pointed_thing.under),
|
||||
ref = pointed_thing.ref,
|
||||
}
|
||||
end
|
||||
@@ -15,15 +15,6 @@ end
|
||||
-- Item definition helpers
|
||||
--
|
||||
|
||||
function core.inventorycube(img1, img2, img3)
|
||||
img2 = img2 or img1
|
||||
img3 = img3 or img1
|
||||
return "[inventorycube"
|
||||
.. "{" .. img1:gsub("%^", "&")
|
||||
.. "{" .. img2:gsub("%^", "&")
|
||||
.. "{" .. img3:gsub("%^", "&")
|
||||
end
|
||||
|
||||
function core.get_pointed_thing_position(pointed_thing, above)
|
||||
if pointed_thing.type == "node" then
|
||||
if above then
|
||||
@@ -37,144 +28,6 @@ function core.get_pointed_thing_position(pointed_thing, above)
|
||||
end
|
||||
end
|
||||
|
||||
function core.dir_to_facedir(dir, is6d)
|
||||
--account for y if requested
|
||||
if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
|
||||
|
||||
--from above
|
||||
if dir.y < 0 then
|
||||
if math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 19
|
||||
else
|
||||
return 13
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 10
|
||||
else
|
||||
return 4
|
||||
end
|
||||
end
|
||||
|
||||
--from below
|
||||
else
|
||||
if math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 15
|
||||
else
|
||||
return 17
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 6
|
||||
else
|
||||
return 8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--otherwise, place horizontally
|
||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 3
|
||||
else
|
||||
return 1
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 2
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Table of possible dirs
|
||||
local facedir_to_dir = {
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new( 0, 0, -1),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, -1, 0),
|
||||
vector.new( 0, 1, 0),
|
||||
}
|
||||
-- Mapping from facedir value to index in facedir_to_dir.
|
||||
local facedir_to_dir_map = {
|
||||
[0]=1, 2, 3, 4,
|
||||
5, 2, 6, 4,
|
||||
6, 2, 5, 4,
|
||||
1, 5, 3, 6,
|
||||
1, 6, 3, 5,
|
||||
1, 4, 3, 2,
|
||||
}
|
||||
function core.facedir_to_dir(facedir)
|
||||
return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
|
||||
end
|
||||
|
||||
function core.dir_to_wallmounted(dir)
|
||||
if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
|
||||
if dir.y < 0 then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 3
|
||||
else
|
||||
return 2
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 5
|
||||
else
|
||||
return 4
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- table of dirs in wallmounted order
|
||||
local wallmounted_to_dir = {
|
||||
[0] = vector.new( 0, 1, 0),
|
||||
vector.new( 0, -1, 0),
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 0, 0, -1),
|
||||
}
|
||||
function core.wallmounted_to_dir(wallmounted)
|
||||
return wallmounted_to_dir[wallmounted % 8]
|
||||
end
|
||||
|
||||
function core.dir_to_yaw(dir)
|
||||
return -math.atan2(dir.x, dir.z)
|
||||
end
|
||||
|
||||
function core.yaw_to_dir(yaw)
|
||||
return vector.new(-math.sin(yaw), 0, math.cos(yaw))
|
||||
end
|
||||
|
||||
function core.is_colored_paramtype(ptype)
|
||||
return (ptype == "color") or (ptype == "colorfacedir") or
|
||||
(ptype == "colorwallmounted") or (ptype == "colordegrotate")
|
||||
end
|
||||
|
||||
function core.strip_param2_color(param2, paramtype2)
|
||||
if not core.is_colored_paramtype(paramtype2) then
|
||||
return nil
|
||||
end
|
||||
if paramtype2 == "colorfacedir" then
|
||||
param2 = math.floor(param2 / 32) * 32
|
||||
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
|
||||
|
||||
local function has_all_groups(tbl, required_groups)
|
||||
if type(required_groups) == "string" then
|
||||
return (tbl[required_groups] or 0) ~= 0
|
||||
@@ -323,12 +176,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
end
|
||||
|
||||
-- Place above pointed node
|
||||
local place_to = vector.new(above)
|
||||
local place_to = vector.copy(above)
|
||||
|
||||
-- If node under is buildable_to, place into it instead (eg. snow)
|
||||
if olddef_under.buildable_to then
|
||||
log("info", "node under is buildable to")
|
||||
place_to = vector.new(under)
|
||||
place_to = vector.copy(under)
|
||||
end
|
||||
|
||||
if core.is_protected(place_to, playername) then
|
||||
@@ -409,7 +262,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
-- Run callback
|
||||
if def.after_place_node and not prevent_after_place then
|
||||
-- Deepcopy place_to and pointed_thing because callback can modify it
|
||||
local place_to_copy = vector.new(place_to)
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if def.after_place_node(place_to_copy, placer, itemstack,
|
||||
pointed_thing_copy) then
|
||||
@@ -420,7 +273,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(core.registered_on_placenodes) do
|
||||
-- Deepcopy pos, node and pointed_thing because callback can modify them
|
||||
local place_to_copy = vector.new(place_to)
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
|
||||
local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
@@ -548,7 +401,7 @@ function core.node_punch(pos, node, puncher, pointed_thing)
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(core.registered_on_punchnodes) do
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
|
||||
callback(pos_copy, node_copy, puncher, pointed_thing_copy)
|
||||
@@ -589,7 +442,7 @@ function core.node_dig(pos, node, digger)
|
||||
local def = core.registered_nodes[node.name]
|
||||
-- Copy pos because the callback could modify it
|
||||
if def and (not def.diggable or
|
||||
(def.can_dig and not def.can_dig(vector.new(pos), digger))) then
|
||||
(def.can_dig and not def.can_dig(vector.copy(pos), digger))) then
|
||||
log("info", diggername .. " tried to dig "
|
||||
.. node.name .. " which is not diggable "
|
||||
.. core.pos_to_string(pos))
|
||||
@@ -636,7 +489,7 @@ function core.node_dig(pos, node, digger)
|
||||
if def and def.preserve_metadata then
|
||||
local oldmeta = core.get_meta(pos):to_table().fields
|
||||
-- Copy pos and node because the callback can modify them.
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
local drop_stacks = {}
|
||||
for k, v in pairs(drops) do
|
||||
@@ -668,7 +521,7 @@ function core.node_dig(pos, node, digger)
|
||||
-- Run callback
|
||||
if def and def.after_dig_node then
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
|
||||
end
|
||||
@@ -679,7 +532,7 @@ function core.node_dig(pos, node, digger)
|
||||
core.set_last_run_mod(origin.mod)
|
||||
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
callback(pos_copy, node_copy, digger)
|
||||
end
|
||||
@@ -744,13 +597,8 @@ core.nodedef_default = {
|
||||
-- Node properties
|
||||
drawtype = "normal",
|
||||
visual_scale = 1.0,
|
||||
-- Don't define these because otherwise the old tile_images and
|
||||
-- special_materials wouldn't be read
|
||||
--tiles ={""},
|
||||
--special_tiles = {
|
||||
-- {name="", backface_culling=true},
|
||||
-- {name="", backface_culling=true},
|
||||
--},
|
||||
tiles = nil,
|
||||
special_tiles = nil,
|
||||
post_effect_color = {a=0, r=0, g=0, b=0},
|
||||
paramtype = "none",
|
||||
paramtype2 = "none",
|
||||
|
||||
@@ -58,17 +58,21 @@ core.register_entity(":__builtin:item", {
|
||||
local glow = def and def.light_source and
|
||||
math.floor(def.light_source / 2 + 0.5)
|
||||
|
||||
local size_bias = 1e-3 * math.random() -- small random bias to counter Z-fighting
|
||||
local c = {-size, -size, -size, size, size, size}
|
||||
self.object:set_properties({
|
||||
is_visible = true,
|
||||
visual = "wielditem",
|
||||
textures = {itemname},
|
||||
visual_size = {x = size, y = size},
|
||||
collisionbox = {-size, -size, -size, size, size, size},
|
||||
visual_size = {x = size + size_bias, y = size + size_bias},
|
||||
collisionbox = c,
|
||||
automatic_rotate = math.pi * 0.5 * 0.2 / size,
|
||||
wield_item = self.itemstring,
|
||||
glow = glow,
|
||||
})
|
||||
|
||||
-- cache for usage in on_step
|
||||
self._collisionbox = c
|
||||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
@@ -93,6 +97,7 @@ core.register_entity(":__builtin:item", {
|
||||
self.object:set_armor_groups({immortal = 1})
|
||||
self.object:set_velocity({x = 0, y = 2, z = 0})
|
||||
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
|
||||
self._collisionbox = self.initial_properties.collisionbox
|
||||
self:set_item()
|
||||
end,
|
||||
|
||||
@@ -163,7 +168,7 @@ core.register_entity(":__builtin:item", {
|
||||
local pos = self.object:get_pos()
|
||||
local node = core.get_node_or_nil({
|
||||
x = pos.x,
|
||||
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
|
||||
y = pos.y + self._collisionbox[2] - 0.05,
|
||||
z = pos.z
|
||||
})
|
||||
-- Delete in 'ignore' nodes
|
||||
@@ -176,7 +181,7 @@ core.register_entity(":__builtin:item", {
|
||||
if self.force_out then
|
||||
-- This code runs after the entity got a push from the is_stuck code.
|
||||
-- It makes sure the entity is entirely outside the solid node
|
||||
local c = self.object:get_properties().collisionbox
|
||||
local c = self._collisionbox
|
||||
local s = self.force_out_start
|
||||
local f = self.force_out
|
||||
local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or
|
||||
|
||||
156
builtin/game/item_s.lua
Normal file
@@ -0,0 +1,156 @@
|
||||
-- Minetest: builtin/item_s.lua
|
||||
-- The distinction of what goes here is a bit tricky, basically it's everything
|
||||
-- that does not (directly or indirectly) need access to ServerEnvironment,
|
||||
-- Server or writable access to IGameDef on the engine side.
|
||||
-- (The '_s' stands for standalone.)
|
||||
|
||||
--
|
||||
-- Item definition helpers
|
||||
--
|
||||
|
||||
function core.inventorycube(img1, img2, img3)
|
||||
img2 = img2 or img1
|
||||
img3 = img3 or img1
|
||||
return "[inventorycube"
|
||||
.. "{" .. img1:gsub("%^", "&")
|
||||
.. "{" .. img2:gsub("%^", "&")
|
||||
.. "{" .. img3:gsub("%^", "&")
|
||||
end
|
||||
|
||||
function core.dir_to_facedir(dir, is6d)
|
||||
--account for y if requested
|
||||
if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
|
||||
|
||||
--from above
|
||||
if dir.y < 0 then
|
||||
if math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 19
|
||||
else
|
||||
return 13
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 10
|
||||
else
|
||||
return 4
|
||||
end
|
||||
end
|
||||
|
||||
--from below
|
||||
else
|
||||
if math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 15
|
||||
else
|
||||
return 17
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 6
|
||||
else
|
||||
return 8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--otherwise, place horizontally
|
||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 3
|
||||
else
|
||||
return 1
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 2
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Table of possible dirs
|
||||
local facedir_to_dir = {
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new( 0, 0, -1),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, -1, 0),
|
||||
vector.new( 0, 1, 0),
|
||||
}
|
||||
-- Mapping from facedir value to index in facedir_to_dir.
|
||||
local facedir_to_dir_map = {
|
||||
[0]=1, 2, 3, 4,
|
||||
5, 2, 6, 4,
|
||||
6, 2, 5, 4,
|
||||
1, 5, 3, 6,
|
||||
1, 6, 3, 5,
|
||||
1, 4, 3, 2,
|
||||
}
|
||||
function core.facedir_to_dir(facedir)
|
||||
return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
|
||||
end
|
||||
|
||||
function core.dir_to_wallmounted(dir)
|
||||
if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
|
||||
if dir.y < 0 then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 3
|
||||
else
|
||||
return 2
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 5
|
||||
else
|
||||
return 4
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- table of dirs in wallmounted order
|
||||
local wallmounted_to_dir = {
|
||||
[0] = vector.new( 0, 1, 0),
|
||||
vector.new( 0, -1, 0),
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 0, 0, -1),
|
||||
}
|
||||
function core.wallmounted_to_dir(wallmounted)
|
||||
return wallmounted_to_dir[wallmounted % 8]
|
||||
end
|
||||
|
||||
function core.dir_to_yaw(dir)
|
||||
return -math.atan2(dir.x, dir.z)
|
||||
end
|
||||
|
||||
function core.yaw_to_dir(yaw)
|
||||
return vector.new(-math.sin(yaw), 0, math.cos(yaw))
|
||||
end
|
||||
|
||||
function core.is_colored_paramtype(ptype)
|
||||
return (ptype == "color") or (ptype == "colorfacedir") or
|
||||
(ptype == "colorwallmounted") or (ptype == "colordegrotate")
|
||||
end
|
||||
|
||||
function core.strip_param2_color(param2, paramtype2)
|
||||
if not core.is_colored_paramtype(paramtype2) then
|
||||
return nil
|
||||
end
|
||||
if paramtype2 == "colorfacedir" then
|
||||
param2 = math.floor(param2 / 32) * 32
|
||||
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
|
||||
@@ -121,53 +121,6 @@ function core.get_player_radius_area(player_name, radius)
|
||||
end
|
||||
|
||||
|
||||
function core.hash_node_position(pos)
|
||||
return (pos.z + 32768) * 65536 * 65536
|
||||
+ (pos.y + 32768) * 65536
|
||||
+ pos.x + 32768
|
||||
end
|
||||
|
||||
|
||||
function core.get_position_from_hash(hash)
|
||||
local x = (hash % 65536) - 32768
|
||||
hash = math.floor(hash / 65536)
|
||||
local y = (hash % 65536) - 32768
|
||||
hash = math.floor(hash / 65536)
|
||||
local z = (hash % 65536) - 32768
|
||||
return vector.new(x, y, z)
|
||||
end
|
||||
|
||||
|
||||
function core.get_item_group(name, group)
|
||||
if not core.registered_items[name] or not
|
||||
core.registered_items[name].groups[group] then
|
||||
return 0
|
||||
end
|
||||
return core.registered_items[name].groups[group]
|
||||
end
|
||||
|
||||
|
||||
function core.get_node_group(name, group)
|
||||
core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
|
||||
return core.get_item_group(name, group)
|
||||
end
|
||||
|
||||
|
||||
function core.setting_get_pos(name)
|
||||
local value = core.settings:get(name)
|
||||
if not value then
|
||||
return nil
|
||||
end
|
||||
return core.string_to_pos(value)
|
||||
end
|
||||
|
||||
|
||||
-- See l_env.cpp for the other functions
|
||||
function core.get_artificial_light(param1)
|
||||
return math.floor(param1 / 16)
|
||||
end
|
||||
|
||||
|
||||
-- To be overriden by protection mods
|
||||
|
||||
function core.is_protected(pos, name)
|
||||
@@ -284,40 +237,30 @@ end
|
||||
core.dynamic_media_callbacks = {}
|
||||
|
||||
|
||||
-- PNG encoder safety wrapper
|
||||
-- Transfer of certain globals into async environment
|
||||
-- see builtin/async/game.lua for the other side
|
||||
|
||||
local o_encode_png = core.encode_png
|
||||
function core.encode_png(width, height, data, compression)
|
||||
if type(width) ~= "number" then
|
||||
error("Incorrect type for 'width', expected number, got " .. type(width))
|
||||
local function copy_filtering(t, seen)
|
||||
if type(t) == "userdata" or type(t) == "function" then
|
||||
return true -- don't use nil so presence can still be detected
|
||||
elseif type(t) ~= "table" then
|
||||
return t
|
||||
end
|
||||
if type(height) ~= "number" then
|
||||
error("Incorrect type for 'height', expected number, got " .. type(height))
|
||||
local n = {}
|
||||
seen = seen or {}
|
||||
seen[t] = n
|
||||
for k, v in pairs(t) do
|
||||
local k_ = seen[k] or copy_filtering(k, seen)
|
||||
local v_ = seen[v] or copy_filtering(v, seen)
|
||||
n[k_] = v_
|
||||
end
|
||||
|
||||
local expected_byte_count = width * height * 4
|
||||
|
||||
if type(data) ~= "table" and type(data) ~= "string" then
|
||||
error("Incorrect type for 'data', expected table or string, got " .. type(data))
|
||||
end
|
||||
|
||||
local data_length = type(data) == "table" and #data * 4 or string.len(data)
|
||||
|
||||
if data_length ~= expected_byte_count then
|
||||
error(string.format(
|
||||
"Incorrect length of 'data', width and height imply %d bytes but %d were provided",
|
||||
expected_byte_count,
|
||||
data_length
|
||||
))
|
||||
end
|
||||
|
||||
if type(data) == "table" then
|
||||
local dataBuf = {}
|
||||
for i = 1, #data do
|
||||
dataBuf[i] = core.colorspec_to_bytes(data[i])
|
||||
end
|
||||
data = table.concat(dataBuf)
|
||||
end
|
||||
|
||||
return o_encode_png(width, height, data, compression or 6)
|
||||
return n
|
||||
end
|
||||
|
||||
function core.get_globals_to_transfer()
|
||||
local all = {
|
||||
registered_items = copy_filtering(core.registered_items),
|
||||
registered_aliases = core.registered_aliases,
|
||||
}
|
||||
return all
|
||||
end
|
||||
|
||||
93
builtin/game/misc_s.lua
Normal file
@@ -0,0 +1,93 @@
|
||||
-- Minetest: builtin/misc_s.lua
|
||||
-- The distinction of what goes here is a bit tricky, basically it's everything
|
||||
-- that does not (directly or indirectly) need access to ServerEnvironment,
|
||||
-- Server or writable access to IGameDef on the engine side.
|
||||
-- (The '_s' stands for standalone.)
|
||||
|
||||
--
|
||||
-- Misc. API functions
|
||||
--
|
||||
|
||||
function core.hash_node_position(pos)
|
||||
return (pos.z + 32768) * 65536 * 65536
|
||||
+ (pos.y + 32768) * 65536
|
||||
+ pos.x + 32768
|
||||
end
|
||||
|
||||
|
||||
function core.get_position_from_hash(hash)
|
||||
local x = (hash % 65536) - 32768
|
||||
hash = math.floor(hash / 65536)
|
||||
local y = (hash % 65536) - 32768
|
||||
hash = math.floor(hash / 65536)
|
||||
local z = (hash % 65536) - 32768
|
||||
return vector.new(x, y, z)
|
||||
end
|
||||
|
||||
|
||||
function core.get_item_group(name, group)
|
||||
if not core.registered_items[name] or not
|
||||
core.registered_items[name].groups[group] then
|
||||
return 0
|
||||
end
|
||||
return core.registered_items[name].groups[group]
|
||||
end
|
||||
|
||||
|
||||
function core.get_node_group(name, group)
|
||||
core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
|
||||
return core.get_item_group(name, group)
|
||||
end
|
||||
|
||||
|
||||
function core.setting_get_pos(name)
|
||||
local value = core.settings:get(name)
|
||||
if not value then
|
||||
return nil
|
||||
end
|
||||
return core.string_to_pos(value)
|
||||
end
|
||||
|
||||
|
||||
-- See l_env.cpp for the other functions
|
||||
function core.get_artificial_light(param1)
|
||||
return math.floor(param1 / 16)
|
||||
end
|
||||
|
||||
-- PNG encoder safety wrapper
|
||||
|
||||
local o_encode_png = core.encode_png
|
||||
function core.encode_png(width, height, data, compression)
|
||||
if type(width) ~= "number" then
|
||||
error("Incorrect type for 'width', expected number, got " .. type(width))
|
||||
end
|
||||
if type(height) ~= "number" then
|
||||
error("Incorrect type for 'height', expected number, got " .. type(height))
|
||||
end
|
||||
|
||||
local expected_byte_count = width * height * 4
|
||||
|
||||
if type(data) ~= "table" and type(data) ~= "string" then
|
||||
error("Incorrect type for 'data', expected table or string, got " .. type(data))
|
||||
end
|
||||
|
||||
local data_length = type(data) == "table" and #data * 4 or string.len(data)
|
||||
|
||||
if data_length ~= expected_byte_count then
|
||||
error(string.format(
|
||||
"Incorrect length of 'data', width and height imply %d bytes but %d were provided",
|
||||
expected_byte_count,
|
||||
data_length
|
||||
))
|
||||
end
|
||||
|
||||
if type(data) == "table" then
|
||||
local dataBuf = {}
|
||||
for i = 1, #data do
|
||||
dataBuf[i] = core.colorspec_to_bytes(data[i])
|
||||
end
|
||||
data = table.concat(dataBuf)
|
||||
end
|
||||
|
||||
return o_encode_png(width, height, data, compression or 6)
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
-- Minetest: builtin/misc_register.lua
|
||||
-- Minetest: builtin/register.lua
|
||||
|
||||
local S = core.get_translator("__builtin")
|
||||
|
||||
@@ -303,14 +303,16 @@ end
|
||||
|
||||
function core.on_craft(itemstack, player, old_craft_list, craft_inv)
|
||||
for _, func in ipairs(core.registered_on_crafts) do
|
||||
itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
|
||||
-- cast to ItemStack since func() could return a string
|
||||
itemstack = ItemStack(func(itemstack, player, old_craft_list, craft_inv) or itemstack)
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
function core.craft_predict(itemstack, player, old_craft_list, craft_inv)
|
||||
for _, func in ipairs(core.registered_craft_predicts) do
|
||||
itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
|
||||
-- cast to ItemStack since func() could return a string
|
||||
itemstack = ItemStack(func(itemstack, player, old_craft_list, craft_inv) or itemstack)
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
@@ -138,13 +138,15 @@ local function player_event_handler(player,eventname)
|
||||
end
|
||||
|
||||
function core.hud_replace_builtin(hud_name, definition)
|
||||
|
||||
if type(definition) ~= "table" or
|
||||
definition.hud_elem_type ~= "statbar" then
|
||||
return false
|
||||
end
|
||||
|
||||
definition = table.copy(definition)
|
||||
|
||||
if hud_name == "health" then
|
||||
definition.item = definition.item or definition.number or core.PLAYER_MAX_HP_DEFAULT
|
||||
bar_definitions.hp = definition
|
||||
|
||||
for name, ids in pairs(hud_ids) do
|
||||
@@ -159,6 +161,7 @@ function core.hud_replace_builtin(hud_name, definition)
|
||||
end
|
||||
|
||||
if hud_name == "breath" then
|
||||
definition.item = definition.item or definition.number or core.PLAYER_MAX_BREATH_DEFAULT
|
||||
bar_definitions.breath = definition
|
||||
|
||||
for name, ids in pairs(hud_ids) do
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
local math_floor = math.floor
|
||||
local vector_new = vector.new
|
||||
|
||||
VoxelArea = {
|
||||
MinEdge = vector.new(1, 1, 1),
|
||||
MaxEdge = vector.new(0, 0, 0),
|
||||
MinEdge = vector_new(1, 1, 1),
|
||||
MaxEdge = vector_new(0, 0, 0),
|
||||
ystride = 0,
|
||||
zstride = 0,
|
||||
}
|
||||
@@ -19,7 +22,7 @@ end
|
||||
|
||||
function VoxelArea:getExtent()
|
||||
local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
|
||||
return vector.new(
|
||||
return vector_new(
|
||||
MaxEdge.x - MinEdge.x + 1,
|
||||
MaxEdge.y - MinEdge.y + 1,
|
||||
MaxEdge.z - MinEdge.z + 1
|
||||
@@ -36,7 +39,7 @@ function VoxelArea:index(x, y, z)
|
||||
local i = (z - MinEdge.z) * self.zstride +
|
||||
(y - MinEdge.y) * self.ystride +
|
||||
(x - MinEdge.x) + 1
|
||||
return math.floor(i)
|
||||
return math_floor(i)
|
||||
end
|
||||
|
||||
function VoxelArea:indexp(p)
|
||||
@@ -44,24 +47,23 @@ function VoxelArea:indexp(p)
|
||||
local i = (p.z - MinEdge.z) * self.zstride +
|
||||
(p.y - MinEdge.y) * self.ystride +
|
||||
(p.x - MinEdge.x) + 1
|
||||
return math.floor(i)
|
||||
return math_floor(i)
|
||||
end
|
||||
|
||||
function VoxelArea:position(i)
|
||||
local p = {}
|
||||
local MinEdge = self.MinEdge
|
||||
|
||||
i = i - 1
|
||||
|
||||
p.z = math.floor(i / self.zstride) + MinEdge.z
|
||||
local z = math_floor(i / self.zstride) + MinEdge.z
|
||||
i = i % self.zstride
|
||||
|
||||
p.y = math.floor(i / self.ystride) + MinEdge.y
|
||||
local y = math_floor(i / self.ystride) + MinEdge.y
|
||||
i = i % self.ystride
|
||||
|
||||
p.x = math.floor(i) + MinEdge.x
|
||||
local x = math_floor(i) + MinEdge.x
|
||||
|
||||
return p
|
||||
return vector_new(x, y, z)
|
||||
end
|
||||
|
||||
function VoxelArea:contains(x, y, z)
|
||||
|
||||
@@ -56,8 +56,10 @@ elseif INIT == "mainmenu" then
|
||||
if not custom_loaded then
|
||||
dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua")
|
||||
end
|
||||
elseif INIT == "async" then
|
||||
dofile(asyncpath .. "init.lua")
|
||||
elseif INIT == "async" then
|
||||
dofile(asyncpath .. "mainmenu.lua")
|
||||
elseif INIT == "async_game" then
|
||||
dofile(asyncpath .. "game.lua")
|
||||
elseif INIT == "client" then
|
||||
dofile(clientpath .. "init.lua")
|
||||
else
|
||||
|
||||
@@ -72,6 +72,7 @@ Teleport to position or player=Zu Position oder Spieler teleportieren
|
||||
You don't have permission to teleport other players (missing privilege: @1).=Sie haben nicht die Erlaubnis, andere Spieler zu teleportieren (fehlendes Privileg: @1).
|
||||
([-n] <name> <value>) | <name>=([-n] <Name> <Wert>) | <Name>
|
||||
Set or read server configuration setting=Serverkonfigurationseinstellung setzen oder lesen
|
||||
Failed. Cannot modify secure settings. Edit the settings file manually.=Fehlgeschlagen. Sicherheitseinstellungen können nicht modifiziert werden. Bearbeiten Sie die Einstellungsdatei manuell.
|
||||
Failed. Use '/set -n <name> <value>' to create a new setting.=Fehlgeschlagen. Benutzen Sie „/set -n <Name> <Wert>“, um eine neue Einstellung zu erstellen.
|
||||
@1 @= @2=@1 @= @2
|
||||
<not set>=<nicht gesetzt>
|
||||
@@ -151,6 +152,7 @@ Server shutting down (operator request).=Server wird heruntergefahren (Betreiber
|
||||
Ban the IP of a player or show the ban list=Die IP eines Spielers verbannen oder die Bannliste anzeigen
|
||||
The ban list is empty.=Die Bannliste ist leer.
|
||||
Ban list: @1=Bannliste: @1
|
||||
You cannot ban players in singleplayer!=Im Einzelspielermodus können Sie keine Spieler verbannen!
|
||||
Player is not online.=Spieler ist nicht online.
|
||||
Failed to ban player.=Konnte Spieler nicht verbannen.
|
||||
Banned @1.=@1 verbannt.
|
||||
@@ -195,7 +197,6 @@ Available commands:=Verfügbare Befehle:
|
||||
Command not available: @1=Befehl nicht verfügbar: @1
|
||||
[all | privs | <cmd>] [-t]=[all | privs | <Befehl>] [-t]
|
||||
Get help for commands or list privileges (-t: output in chat)=Hilfe für Befehle erhalten oder Privilegien auflisten (-t: Ausgabe im Chat)
|
||||
Available privileges:=Verfügbare Privilegien:
|
||||
Command=Befehl
|
||||
Parameters=Parameter
|
||||
For more information, click on any entry in the list.=Für mehr Informationen klicken Sie auf einen beliebigen Eintrag in der Liste.
|
||||
@@ -205,6 +206,7 @@ Available commands: (see also: /help <cmd>)=Verfügbare Befehle: (siehe auch: /h
|
||||
Close=Schließen
|
||||
Privilege=Privileg
|
||||
Description=Beschreibung
|
||||
Available privileges:=Verfügbare Privilegien:
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<Filter>] | dump [<Filter>] | save [<Format> [<Filter>]]
|
||||
Handle the profiler and profiling data=Den Profiler und Profilingdaten verwalten
|
||||
Statistics written to action log.=Statistiken zum Aktionsprotokoll geschrieben.
|
||||
@@ -232,7 +234,6 @@ Can use fly mode=Kann den Flugmodus benutzen
|
||||
Can use fast mode=Kann den Schnellmodus benutzen
|
||||
Can fly through solid nodes using noclip mode=Kann durch feste Blöcke mit dem Geistmodus fliegen
|
||||
Can use the rollback functionality=Kann die Rollback-Funktionalität benutzen
|
||||
Can view more debug info that might give a gameplay advantage=Kann zusätzliche Debuginformationen betrachten, welche einen spielerischen Vorteil geben könnten
|
||||
Can enable wireframe=Kann Drahtmodell aktivieren
|
||||
Unknown Item=Unbekannter Gegenstand
|
||||
Air=Luft
|
||||
|
||||
@@ -72,6 +72,7 @@ Teleport to position or player=Teletrasporta a una posizione o da un giocatore
|
||||
You don't have permission to teleport other players (missing privilege: @1).=Non hai il permesso di teletrasportare altri giocatori (privilegio mancante: @1).
|
||||
([-n] <name> <value>) | <name>=([-n] <nome> <valore>) | <nome>
|
||||
Set or read server configuration setting=Imposta o ottieni le configurazioni del server
|
||||
Failed. Cannot modify secure settings. Edit the settings file manually.=
|
||||
Failed. Use '/set -n <name> <value>' to create a new setting.=Errore. Usa 'set -n <nome> <valore>' per creare una nuova impostazione
|
||||
@1 @= @2=@1 @= @2
|
||||
<not set>=<non impostato>
|
||||
@@ -151,6 +152,7 @@ Server shutting down (operator request).=Arresto del server in corso (per richie
|
||||
Ban the IP of a player or show the ban list=Bandisce l'IP del giocatore o mostra la lista di quelli banditi
|
||||
The ban list is empty.=La lista banditi è vuota.
|
||||
Ban list: @1=Lista banditi: @1
|
||||
You cannot ban players in singleplayer!=
|
||||
Player is not online.=Il giocatore non è connesso.
|
||||
Failed to ban player.=Errore nel bandire il giocatore.
|
||||
Banned @1.=@1 banditǝ.
|
||||
@@ -195,7 +197,6 @@ Available commands:=Comandi disponibili:
|
||||
Command not available: @1=Comando non disponibile: @1
|
||||
[all | privs | <cmd>] [-t]=
|
||||
Get help for commands or list privileges (-t: output in chat)=
|
||||
Available privileges:=Privilegi disponibili:
|
||||
Command=Comando
|
||||
Parameters=Parametri
|
||||
For more information, click on any entry in the list.=Per più informazioni, clicca su una qualsiasi voce dell'elenco.
|
||||
@@ -205,6 +206,7 @@ Available commands: (see also: /help <cmd>)=Comandi disponibili: (vedi anche /he
|
||||
Close=Chiudi
|
||||
Privilege=Privilegio
|
||||
Description=Descrizione
|
||||
Available privileges:=Privilegi disponibili:
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filtro>] | dump [<filtro>] | save [<formato> [<filtro>]] | reset
|
||||
Handle the profiler and profiling data=Gestisce il profiler e i dati da esso elaborati
|
||||
Statistics written to action log.=Statistiche scritte nel log delle azioni.
|
||||
@@ -232,7 +234,6 @@ Can use fly mode=Si può usare la modalità volo
|
||||
Can use fast mode=Si può usare la modalità rapida
|
||||
Can fly through solid nodes using noclip mode=Si può volare attraverso i nodi solidi con la modalità incorporea
|
||||
Can use the rollback functionality=Si può usare la funzione di rollback
|
||||
Can view more debug info that might give a gameplay advantage=
|
||||
Can enable wireframe=
|
||||
Unknown Item=Oggetto sconosciuto
|
||||
Air=Aria
|
||||
|
||||
@@ -72,6 +72,7 @@ Teleport to position or player=
|
||||
You don't have permission to teleport other players (missing privilege: @1).=
|
||||
([-n] <name> <value>) | <name>=
|
||||
Set or read server configuration setting=
|
||||
Failed. Cannot modify secure settings. Edit the settings file manually.=
|
||||
Failed. Use '/set -n <name> <value>' to create a new setting.=
|
||||
@1 @= @2=
|
||||
<not set>=
|
||||
@@ -151,6 +152,7 @@ Server shutting down (operator request).=
|
||||
Ban the IP of a player or show the ban list=
|
||||
The ban list is empty.=
|
||||
Ban list: @1=
|
||||
You cannot ban players in singleplayer!=
|
||||
Player is not online.=
|
||||
Failed to ban player.=
|
||||
Banned @1.=
|
||||
@@ -195,7 +197,6 @@ Available commands:=
|
||||
Command not available: @1=
|
||||
[all | privs | <cmd>] [-t]=
|
||||
Get help for commands or list privileges (-t: output in chat)=
|
||||
Available privileges:=
|
||||
Command=
|
||||
Parameters=
|
||||
For more information, click on any entry in the list.=
|
||||
@@ -205,6 +206,7 @@ Available commands: (see also: /help <cmd>)=
|
||||
Close=
|
||||
Privilege=
|
||||
Description=
|
||||
Available privileges:=
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=
|
||||
Handle the profiler and profiling data=
|
||||
Statistics written to action log.=
|
||||
@@ -232,7 +234,6 @@ Can use fly mode=
|
||||
Can use fast mode=
|
||||
Can fly through solid nodes using noclip mode=
|
||||
Can use the rollback functionality=
|
||||
Can view more debug info that might give a gameplay advantage=
|
||||
Can enable wireframe=
|
||||
Unknown Item=
|
||||
Air=
|
||||
|
||||
@@ -125,18 +125,12 @@ os.tmpname = function()
|
||||
end
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function menu_render_worldlist(show_gameid)
|
||||
function menu_render_worldlist()
|
||||
local retval = {}
|
||||
local current_worldlist = menudata.worldlist:get_list()
|
||||
|
||||
local row
|
||||
for i, v in ipairs(current_worldlist) do
|
||||
row = v.name
|
||||
if show_gameid == nil or show_gameid == true then
|
||||
row = row .. " [" .. v.gameid .. "]"
|
||||
end
|
||||
retval[#retval+1] = core.formspec_escape(row)
|
||||
|
||||
retval[#retval+1] = core.formspec_escape(v.name)
|
||||
end
|
||||
|
||||
return table.concat(retval, ",")
|
||||
@@ -242,3 +236,11 @@ function menu_worldmt_legacy(selected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function confirmation_formspec(message, confirm_id, confirm_label, cancel_id, cancel_label)
|
||||
return "size[10,2.5,true]" ..
|
||||
"label[0.5,0.5;" .. message .. "]" ..
|
||||
"style[" .. confirm_id .. ";bgcolor=red]" ..
|
||||
"button[0.5,1.5;2.5,0.5;" .. confirm_id .. ";" .. confirm_label .. "]" ..
|
||||
"button[7.0,1.5;2.5,0.5;" .. cancel_id .. ";" .. cancel_label .. "]"
|
||||
end
|
||||
|
||||
@@ -61,12 +61,68 @@ local function init_data(data)
|
||||
data.list:set_sortmode("alphabetic")
|
||||
end
|
||||
|
||||
|
||||
-- Returns errors errors and a list of all enabled mods (inc. game and world mods)
|
||||
--
|
||||
-- `with_errors` is a table from mod virtual path to `{ type = "error" | "warning" }`.
|
||||
-- `enabled_mods_by_name` is a table from mod virtual path to `true`.
|
||||
--
|
||||
-- @param world_path Path to the world
|
||||
-- @param all_mods List of mods, with `enabled` property.
|
||||
-- @returns with_errors, enabled_mods_by_name
|
||||
local function check_mod_configuration(world_path, all_mods)
|
||||
-- Build up lookup tables for enabled mods and all mods by vpath
|
||||
local enabled_mod_paths = {}
|
||||
local all_mods_by_vpath = {}
|
||||
for _, mod in ipairs(all_mods) do
|
||||
if mod.type == "mod" then
|
||||
all_mods_by_vpath[mod.virtual_path] = mod
|
||||
end
|
||||
if mod.enabled then
|
||||
enabled_mod_paths[mod.virtual_path] = mod.path
|
||||
end
|
||||
end
|
||||
|
||||
-- Use the engine's mod configuration code to resolve dependencies and return any errors
|
||||
local config_status = core.check_mod_configuration(world_path, enabled_mod_paths)
|
||||
|
||||
-- Build the list of enabled mod virtual paths
|
||||
local enabled_mods_by_name = {}
|
||||
for _, mod in ipairs(config_status.satisfied_mods) do
|
||||
assert(mod.virtual_path ~= "")
|
||||
enabled_mods_by_name[mod.name] = all_mods_by_vpath[mod.virtual_path] or mod
|
||||
end
|
||||
for _, mod in ipairs(config_status.unsatisfied_mods) do
|
||||
assert(mod.virtual_path ~= "")
|
||||
enabled_mods_by_name[mod.name] = all_mods_by_vpath[mod.virtual_path] or mod
|
||||
end
|
||||
|
||||
-- Build the table of errors
|
||||
local with_error = {}
|
||||
for _, mod in ipairs(config_status.unsatisfied_mods) do
|
||||
local error = { type = "warning" }
|
||||
with_error[mod.virtual_path] = error
|
||||
|
||||
for _, depname in ipairs(mod.unsatisfied_depends) do
|
||||
if not enabled_mods_by_name[depname] then
|
||||
error.type = "error"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return with_error, enabled_mods_by_name
|
||||
end
|
||||
|
||||
local function get_formspec(data)
|
||||
if not data.list then
|
||||
init_data(data)
|
||||
end
|
||||
|
||||
local mod = data.list:get_list()[data.selected_mod] or {name = ""}
|
||||
local all_mods = data.list:get_list()
|
||||
local with_error, enabled_mods_by_name = check_mod_configuration(data.worldspec.path, all_mods)
|
||||
|
||||
local mod = all_mods[data.selected_mod] or {name = ""}
|
||||
|
||||
local retval =
|
||||
"size[11.5,7.5,true]" ..
|
||||
@@ -87,6 +143,29 @@ local function get_formspec(data)
|
||||
"textarea[0.25,0.7;5.75,7.2;;" .. info .. ";]"
|
||||
else
|
||||
local hard_deps, soft_deps = pkgmgr.get_dependencies(mod.path)
|
||||
|
||||
-- Add error messages to dep lists
|
||||
if mod.enabled or mod.is_game_content then
|
||||
for i, dep_name in ipairs(hard_deps) do
|
||||
local dep = enabled_mods_by_name[dep_name]
|
||||
if not dep then
|
||||
hard_deps[i] = mt_color_red .. dep_name .. " " .. fgettext("(Unsatisfied)")
|
||||
elseif with_error[dep.virtual_path] then
|
||||
hard_deps[i] = mt_color_orange .. dep_name .. " " .. fgettext("(Enabled, has error)")
|
||||
else
|
||||
hard_deps[i] = mt_color_green .. dep_name
|
||||
end
|
||||
end
|
||||
for i, dep_name in ipairs(soft_deps) do
|
||||
local dep = enabled_mods_by_name[dep_name]
|
||||
if dep and with_error[dep.virtual_path] then
|
||||
soft_deps[i] = mt_color_orange .. dep_name .. " " .. fgettext("(Enabled, has error)")
|
||||
elseif dep then
|
||||
soft_deps[i] = mt_color_green .. dep_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local hard_deps_str = table.concat(hard_deps, ",")
|
||||
local soft_deps_str = table.concat(soft_deps, ",")
|
||||
|
||||
@@ -138,7 +217,6 @@ local function get_formspec(data)
|
||||
|
||||
if mod.name ~= "" and not mod.is_game_content then
|
||||
if mod.is_modpack then
|
||||
|
||||
if pkgmgr.is_modpack_entirely_enabled(data, mod.name) then
|
||||
retval = retval ..
|
||||
"button[5.5,0.125;3,0.5;btn_mp_disable;" ..
|
||||
@@ -163,10 +241,16 @@ local function get_formspec(data)
|
||||
"button[8.95,0.125;2.5,0.5;btn_enable_all_mods;" ..
|
||||
fgettext("Enable all") .. "]"
|
||||
end
|
||||
|
||||
local use_technical_names = core.settings:get_bool("show_technical_names")
|
||||
|
||||
return retval ..
|
||||
"tablecolumns[color;tree;text]" ..
|
||||
"tablecolumns[color;tree;image,align=inline,width=1.5,0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") ..
|
||||
",1=" .. core.formspec_escape(defaulttexturedir .. "checkbox_16_white.png") ..
|
||||
",2=" .. core.formspec_escape(defaulttexturedir .. "error_icon_orange.png") ..
|
||||
",3=" .. core.formspec_escape(defaulttexturedir .. "error_icon_red.png") .. ";text]" ..
|
||||
"table[5.5,0.75;5.75,6;world_config_modlist;" ..
|
||||
pkgmgr.render_packagelist(data.list) .. ";" .. data.selected_mod .."]"
|
||||
pkgmgr.render_packagelist(data.list, use_technical_names, with_error) .. ";" .. data.selected_mod .."]"
|
||||
end
|
||||
|
||||
local function handle_buttons(this, fields)
|
||||
@@ -205,14 +289,19 @@ local function handle_buttons(this, fields)
|
||||
local mods = worldfile:to_table()
|
||||
|
||||
local rawlist = this.data.list:get_raw_list()
|
||||
local was_set = {}
|
||||
|
||||
for i = 1, #rawlist do
|
||||
local mod = rawlist[i]
|
||||
if not mod.is_modpack and
|
||||
not mod.is_game_content then
|
||||
if modname_valid(mod.name) then
|
||||
worldfile:set("load_mod_" .. mod.name,
|
||||
mod.enabled and "true" or "false")
|
||||
if mod.enabled then
|
||||
worldfile:set("load_mod_" .. mod.name, mod.virtual_path)
|
||||
was_set[mod.name] = true
|
||||
elseif not was_set[mod.name] then
|
||||
worldfile:set("load_mod_" .. mod.name, "false")
|
||||
end
|
||||
elseif mod.enabled then
|
||||
gamedata.errormessage = fgettext_ne("Failed to enable mo" ..
|
||||
"d \"$1\" as it contains disallowed characters. " ..
|
||||
@@ -256,12 +345,26 @@ local function handle_buttons(this, fields)
|
||||
if fields.btn_enable_all_mods then
|
||||
local list = this.data.list:get_raw_list()
|
||||
|
||||
-- When multiple copies of a mod are installed, we need to avoid enabling multiple of them
|
||||
-- at a time. So lets first collect all the enabled mods, and then use this to exclude
|
||||
-- multiple enables.
|
||||
|
||||
local was_enabled = {}
|
||||
for i = 1, #list do
|
||||
if not list[i].is_game_content
|
||||
and not list[i].is_modpack then
|
||||
list[i].enabled = true
|
||||
and not list[i].is_modpack and list[i].enabled then
|
||||
was_enabled[list[i].name] = true
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #list do
|
||||
if not list[i].is_game_content and not list[i].is_modpack and
|
||||
not was_enabled[list[i].name] then
|
||||
list[i].enabled = true
|
||||
was_enabled[list[i].name] = true
|
||||
end
|
||||
end
|
||||
|
||||
enabled_all = true
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -151,11 +151,9 @@ local function start_install(package, reason)
|
||||
|
||||
if conf_path then
|
||||
local conf = Settings(conf_path)
|
||||
if name_is_title then
|
||||
conf:set("name", package.title)
|
||||
else
|
||||
conf:set("title", package.title)
|
||||
conf:set("name", package.name)
|
||||
conf:set("title", package.title)
|
||||
if not name_is_title then
|
||||
conf:set("name", package.name)
|
||||
end
|
||||
if not conf:get("description") then
|
||||
conf:set("description", package.short_description)
|
||||
@@ -193,7 +191,7 @@ end
|
||||
|
||||
local function queue_download(package, reason)
|
||||
local max_concurrent_downloads = tonumber(core.settings:get("contentdb_max_concurrent_downloads"))
|
||||
if number_downloading < max_concurrent_downloads then
|
||||
if number_downloading < math.max(max_concurrent_downloads, 1) then
|
||||
start_install(package, reason)
|
||||
else
|
||||
table.insert(download_queue, { package = package, reason = reason })
|
||||
@@ -360,7 +358,7 @@ function install_dialog.get_formspec()
|
||||
selected_game_idx = i
|
||||
end
|
||||
|
||||
games[i] = core.formspec_escape(games[i].name)
|
||||
games[i] = core.formspec_escape(games[i].title)
|
||||
end
|
||||
|
||||
local selected_game = pkgmgr.games[selected_game_idx]
|
||||
@@ -410,7 +408,7 @@ function install_dialog.get_formspec()
|
||||
"container[0.375,0.70]",
|
||||
|
||||
"label[0,0.25;", fgettext("Base Game:"), "]",
|
||||
"dropdown[2,0;4.25,0.5;gameid;", table.concat(games, ","), ";", selected_game_idx, "]",
|
||||
"dropdown[2,0;4.25,0.5;selected_game;", table.concat(games, ","), ";", selected_game_idx, "]",
|
||||
|
||||
"label[0,0.8;", fgettext("Dependencies:"), "]",
|
||||
|
||||
@@ -461,9 +459,9 @@ function install_dialog.handle_submit(this, fields)
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.gameid then
|
||||
if fields.selected_game then
|
||||
for _, game in pairs(pkgmgr.games) do
|
||||
if game.name == fields.gameid then
|
||||
if game.title == fields.selected_game then
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
break
|
||||
end
|
||||
@@ -490,12 +488,10 @@ local confirm_overwrite = {}
|
||||
function confirm_overwrite.get_formspec()
|
||||
local package = confirm_overwrite.package
|
||||
|
||||
return "size[11.5,4.5,true]" ..
|
||||
"label[2,2;" ..
|
||||
fgettext("\"$1\" already exists. Would you like to overwrite it?", package.name) .. "]"..
|
||||
"style[install;bgcolor=red]" ..
|
||||
"button[3.25,3.5;2.5,0.5;install;" .. fgettext("Overwrite") .. "]" ..
|
||||
"button[5.75,3.5;2.5,0.5;cancel;" .. fgettext("Cancel") .. "]"
|
||||
return confirmation_formspec(
|
||||
fgettext("\"$1\" already exists. Would you like to overwrite it?", package.name),
|
||||
'install', fgettext("Overwrite"),
|
||||
'cancel', fgettext("Cancel"))
|
||||
end
|
||||
|
||||
function confirm_overwrite.handle_submit(this, fields)
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
-- cf. tab_local, the gamebar already provides game selection so we hide the list from here
|
||||
local hide_gamelist = PLATFORM ~= "Android"
|
||||
|
||||
local function table_to_flags(ftable)
|
||||
-- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"
|
||||
local str = {}
|
||||
@@ -94,14 +91,14 @@ local mgv6_biomes = {
|
||||
|
||||
local function create_world_formspec(dialogdata)
|
||||
|
||||
-- Error out when no games found
|
||||
-- Point the player to ContentDB when no games are found
|
||||
if #pkgmgr.games == 0 then
|
||||
return "size[12.25,3,true]" ..
|
||||
"box[0,0;12,2;" .. mt_color_orange .. "]" ..
|
||||
"textarea[0.3,0;11.7,2;;;"..
|
||||
fgettext("You have no games installed.") .. "\n" ..
|
||||
fgettext("Download one from minetest.net") .. "]" ..
|
||||
"button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
|
||||
return "size[8,2.5,true]" ..
|
||||
"style[label_button;border=false]" ..
|
||||
"button[0.5,0.5;7,0.5;label_button;" ..
|
||||
fgettext("You have no games installed.") .. "]" ..
|
||||
"button[0.5,1.5;2.5,0.5;world_create_open_cdb;" .. fgettext("Install a game") .. "]" ..
|
||||
"button[5.0,1.5;2.5,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
|
||||
end
|
||||
|
||||
local current_mg = dialogdata.mg
|
||||
@@ -111,14 +108,11 @@ local function create_world_formspec(dialogdata)
|
||||
|
||||
local flags = dialogdata.flags
|
||||
|
||||
local game, gameidx = pkgmgr.find_by_gameid(gameid)
|
||||
if game == nil and hide_gamelist then
|
||||
local game = pkgmgr.find_by_gameid(gameid)
|
||||
if game == nil then
|
||||
-- should never happen but just pick the first game
|
||||
game = pkgmgr.get_game(1)
|
||||
gameidx = 1
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
elseif game == nil then
|
||||
gameidx = 0
|
||||
end
|
||||
|
||||
local disallowed_mapgen_settings = {}
|
||||
@@ -296,17 +290,6 @@ local function create_world_formspec(dialogdata)
|
||||
label_spflags = "label[0,"..y_start..";" .. fgettext("Mapgen-specific flags") .. "]"
|
||||
end
|
||||
|
||||
-- Warning if only devtest is installed
|
||||
local devtest_only = ""
|
||||
local gamelist_height = 2.3
|
||||
if #pkgmgr.games == 1 and pkgmgr.games[1].id == "devtest" then
|
||||
devtest_only = "box[0,0;5.8,1.7;#ff8800]" ..
|
||||
"textarea[0.3,0;6,1.8;;;"..
|
||||
fgettext("Warning: The Development Test is meant for developers.") .. "\n" ..
|
||||
fgettext("Download a game, such as Minetest Game, from minetest.net") .. "]"
|
||||
gamelist_height = 0.5
|
||||
end
|
||||
|
||||
local retval =
|
||||
"size[12.25,7,true]" ..
|
||||
|
||||
@@ -315,22 +298,28 @@ local function create_world_formspec(dialogdata)
|
||||
"field[0.3,0.6;6,0.5;te_world_name;" ..
|
||||
fgettext("World name") ..
|
||||
";" .. core.formspec_escape(dialogdata.worldname) .. "]" ..
|
||||
"set_focus[te_world_name;false]" ..
|
||||
"set_focus[te_world_name;false]"
|
||||
|
||||
"field[0.3,1.7;6,0.5;te_seed;" ..
|
||||
fgettext("Seed") ..
|
||||
";".. core.formspec_escape(dialogdata.seed) .. "]" ..
|
||||
if not disallowed_mapgen_settings["seed"] then
|
||||
|
||||
retval = retval .. "field[0.3,1.7;6,0.5;te_seed;" ..
|
||||
fgettext("Seed") ..
|
||||
";".. core.formspec_escape(dialogdata.seed) .. "]"
|
||||
|
||||
end
|
||||
|
||||
retval = retval ..
|
||||
"label[0,2;" .. fgettext("Mapgen") .. "]"..
|
||||
"dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]"
|
||||
|
||||
if not hide_gamelist or devtest_only ~= "" then
|
||||
-- Warning if only devtest is installed
|
||||
if #pkgmgr.games == 1 and pkgmgr.games[1].id == "devtest" then
|
||||
retval = retval ..
|
||||
"label[0,3.35;" .. fgettext("Game") .. "]"..
|
||||
"textlist[0,3.85;5.8,"..gamelist_height..";games;" ..
|
||||
pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" ..
|
||||
"container[0,4.5]" ..
|
||||
devtest_only ..
|
||||
"container[0,3.5]" ..
|
||||
"box[0,0;5.8,1.7;#ff8800]" ..
|
||||
"textarea[0.4,0.1;6,1.8;;;"..
|
||||
fgettext("Development Test is meant for developers.") .. "]" ..
|
||||
"button[1,1;4,0.5;world_create_open_cdb;" .. fgettext("Install another game") .. "]" ..
|
||||
"container_end[]"
|
||||
end
|
||||
|
||||
@@ -353,17 +342,20 @@ end
|
||||
|
||||
local function create_world_buttonhandler(this, fields)
|
||||
|
||||
if fields["world_create_open_cdb"] then
|
||||
local dlg = create_store_dlg("game")
|
||||
dlg:set_parent(this.parent)
|
||||
this:delete()
|
||||
this.parent:hide()
|
||||
dlg:show()
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["world_create_confirm"] or
|
||||
fields["key_enter"] then
|
||||
|
||||
local worldname = fields["te_world_name"]
|
||||
local game, gameindex
|
||||
if hide_gamelist then
|
||||
game, gameindex = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
|
||||
else
|
||||
gameindex = core.get_textlist_index("games")
|
||||
game = pkgmgr.get_game(gameindex)
|
||||
end
|
||||
local game, gameindex = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
|
||||
|
||||
local message
|
||||
if game == nil then
|
||||
@@ -391,7 +383,7 @@ local function create_world_buttonhandler(this, fields)
|
||||
end
|
||||
|
||||
if message == nil then
|
||||
this.data.seed = fields["te_seed"]
|
||||
this.data.seed = fields["te_seed"] or ""
|
||||
this.data.mg = fields["dd_mapgen"]
|
||||
|
||||
-- actual names as used by engine
|
||||
@@ -412,9 +404,7 @@ local function create_world_buttonhandler(this, fields)
|
||||
|
||||
if message == nil then
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
if this.data.update_worldlist_filter then
|
||||
menudata.worldlist:set_filtercriteria(game.id)
|
||||
end
|
||||
menudata.worldlist:set_filtercriteria(game.id)
|
||||
menudata.worldlist:refresh()
|
||||
core.settings:set("mainmenu_last_selected_world",
|
||||
menudata.worldlist:raw_index_by_uid(worldname))
|
||||
@@ -426,7 +416,7 @@ local function create_world_buttonhandler(this, fields)
|
||||
end
|
||||
|
||||
this.data.worldname = fields["te_world_name"]
|
||||
this.data.seed = fields["te_seed"]
|
||||
this.data.seed = fields["te_seed"] or ""
|
||||
|
||||
if fields["games"] then
|
||||
local gameindex = core.get_textlist_index("games")
|
||||
@@ -472,13 +462,12 @@ local function create_world_buttonhandler(this, fields)
|
||||
end
|
||||
|
||||
|
||||
function create_create_world_dlg(update_worldlistfilter)
|
||||
function create_create_world_dlg()
|
||||
local retval = dialog_create("sp_create_world",
|
||||
create_world_formspec,
|
||||
create_world_buttonhandler,
|
||||
nil)
|
||||
retval.data = {
|
||||
update_worldlist_filter = update_worldlistfilter,
|
||||
worldname = "",
|
||||
-- settings the world is created with:
|
||||
seed = core.settings:get("fixed_map_seed") or "",
|
||||
|
||||
@@ -18,15 +18,10 @@
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function delete_content_formspec(dialogdata)
|
||||
local retval =
|
||||
"size[11.5,4.5,true]" ..
|
||||
"label[2,2;" ..
|
||||
fgettext("Are you sure you want to delete \"$1\"?", dialogdata.content.name) .. "]"..
|
||||
"style[dlg_delete_content_confirm;bgcolor=red]" ..
|
||||
"button[3.25,3.5;2.5,0.5;dlg_delete_content_confirm;" .. fgettext("Delete") .. "]" ..
|
||||
"button[5.75,3.5;2.5,0.5;dlg_delete_content_cancel;" .. fgettext("Cancel") .. "]"
|
||||
|
||||
return retval
|
||||
return confirmation_formspec(
|
||||
fgettext("Are you sure you want to delete \"$1\"?", dialogdata.content.name),
|
||||
'dlg_delete_content_confirm', fgettext("Delete"),
|
||||
'dlg_delete_content_cancel', fgettext("Cancel"))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -17,14 +17,10 @@
|
||||
|
||||
|
||||
local function delete_world_formspec(dialogdata)
|
||||
local retval =
|
||||
"size[10,2.5,true]" ..
|
||||
"label[0.5,0.5;" ..
|
||||
fgettext("Delete World \"$1\"?", dialogdata.delete_name) .. "]" ..
|
||||
"style[world_delete_confirm;bgcolor=red]" ..
|
||||
"button[0.5,1.5;2.5,0.5;world_delete_confirm;" .. fgettext("Delete") .. "]" ..
|
||||
"button[7.0,1.5;2.5,0.5;world_delete_cancel;" .. fgettext("Cancel") .. "]"
|
||||
return retval
|
||||
return confirmation_formspec(
|
||||
fgettext("Delete World \"$1\"?", dialogdata.delete_name),
|
||||
'world_delete_confirm', fgettext("Delete"),
|
||||
'world_delete_cancel', fgettext("Cancel"))
|
||||
end
|
||||
|
||||
local function delete_world_buttonhandler(this, fields)
|
||||
|
||||
123
builtin/mainmenu/dlg_register.lua
Normal file
@@ -0,0 +1,123 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2022 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 register_formspec(dialogdata)
|
||||
local title = fgettext("Joining $1", dialogdata.server and dialogdata.server.name or dialogdata.address)
|
||||
local buttons_y = 4 + 1.3
|
||||
if dialogdata.error then
|
||||
buttons_y = buttons_y + 0.8
|
||||
end
|
||||
|
||||
local retval = {
|
||||
"formspec_version[4]",
|
||||
"size[8,", tostring(buttons_y + 1.175), "]",
|
||||
"set_focus[", (dialogdata.name ~= "" and "password" or "name"), "]",
|
||||
"label[0.375,0.8;", title, "]",
|
||||
"field[0.375,1.575;7.25,0.8;name;", core.formspec_escape(fgettext("Name")), ";",
|
||||
core.formspec_escape(dialogdata.name), "]",
|
||||
"pwdfield[0.375,2.875;7.25,0.8;password;", core.formspec_escape(fgettext("Password")), "]",
|
||||
"pwdfield[0.375,4.175;7.25,0.8;password_2;", core.formspec_escape(fgettext("Confirm Password")), "]"
|
||||
}
|
||||
|
||||
if dialogdata.error then
|
||||
table.insert_all(retval, {
|
||||
"box[0.375,", tostring(buttons_y - 0.9), ";7.25,0.6;darkred]",
|
||||
"label[0.625,", tostring(buttons_y - 0.6), ";", core.formspec_escape(dialogdata.error), "]",
|
||||
})
|
||||
end
|
||||
|
||||
table.insert_all(retval, {
|
||||
"container[0.375,", tostring(buttons_y), "]",
|
||||
"button[0,0;2.5,0.8;dlg_register_confirm;", fgettext("Register"), "]",
|
||||
"button[4.75,0;2.5,0.8;dlg_register_cancel;", fgettext("Cancel"), "]",
|
||||
"container_end[]",
|
||||
})
|
||||
|
||||
return table.concat(retval, "")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
local function register_buttonhandler(this, fields)
|
||||
this.data.name = fields.name
|
||||
this.data.error = nil
|
||||
|
||||
if fields.dlg_register_confirm or fields.key_enter then
|
||||
if fields.name == "" then
|
||||
this.data.error = fgettext("Missing name")
|
||||
return true
|
||||
end
|
||||
if fields.password ~= fields.password_2 then
|
||||
this.data.error = fgettext("Passwords do not match")
|
||||
return true
|
||||
end
|
||||
|
||||
gamedata.playername = fields.name
|
||||
gamedata.password = fields.password
|
||||
gamedata.address = this.data.address
|
||||
gamedata.port = this.data.port
|
||||
gamedata.allow_login_or_register = "register"
|
||||
gamedata.selected_world = 0
|
||||
|
||||
assert(gamedata.address and gamedata.port)
|
||||
|
||||
local server = this.data.server
|
||||
if server then
|
||||
serverlistmgr.add_favorite(server)
|
||||
gamedata.servername = server.name
|
||||
gamedata.serverdescription = server.description
|
||||
else
|
||||
gamedata.servername = ""
|
||||
gamedata.serverdescription = ""
|
||||
|
||||
serverlistmgr.add_favorite({
|
||||
address = gamedata.address,
|
||||
port = gamedata.port,
|
||||
})
|
||||
end
|
||||
|
||||
core.settings:set("name", fields.name)
|
||||
core.settings:set("address", gamedata.address)
|
||||
core.settings:set("remote_port", gamedata.port)
|
||||
|
||||
core.start()
|
||||
end
|
||||
|
||||
if fields["dlg_register_cancel"] then
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function create_register_dialog(address, port, server)
|
||||
assert(address)
|
||||
assert(type(port) == "number")
|
||||
|
||||
local retval = dialog_create("dlg_register",
|
||||
register_formspec,
|
||||
register_buttonhandler,
|
||||
nil)
|
||||
retval.data.address = address
|
||||
retval.data.port = port
|
||||
retval.data.server = server
|
||||
retval.data.name = core.settings:get("name") or ""
|
||||
return retval
|
||||
end
|
||||
@@ -351,9 +351,9 @@ local function parse_config_file(read_all, parse_mods)
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
if not games_category_initialized then
|
||||
fgettext_ne("Games") -- not used, but needed for xgettext
|
||||
fgettext_ne("Content: Games") -- not used, but needed for xgettext
|
||||
table.insert(settings, {
|
||||
name = "Games",
|
||||
name = "Content: Games",
|
||||
level = 0,
|
||||
type = "category",
|
||||
})
|
||||
@@ -378,21 +378,52 @@ local function parse_config_file(read_all, parse_mods)
|
||||
-- Parse mods
|
||||
local mods_category_initialized = false
|
||||
local mods = {}
|
||||
get_mods(core.get_modpath(), mods)
|
||||
get_mods(core.get_modpath(), "mods", mods)
|
||||
for _, mod in ipairs(mods) do
|
||||
local path = mod.path .. DIR_DELIM .. FILENAME
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
if not mods_category_initialized then
|
||||
fgettext_ne("Mods") -- not used, but needed for xgettext
|
||||
fgettext_ne("Content: Mods") -- not used, but needed for xgettext
|
||||
table.insert(settings, {
|
||||
name = "Mods",
|
||||
name = "Content: Mods",
|
||||
level = 0,
|
||||
type = "category",
|
||||
})
|
||||
mods_category_initialized = true
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = mod.name,
|
||||
readable_name = mod.title,
|
||||
level = 1,
|
||||
type = "category",
|
||||
})
|
||||
|
||||
parse_single_file(file, path, read_all, settings, 2, false)
|
||||
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse client mods
|
||||
local clientmods_category_initialized = false
|
||||
local clientmods = {}
|
||||
get_mods(core.get_clientmodpath(), "clientmods", clientmods)
|
||||
for _, mod in ipairs(clientmods) do
|
||||
local path = mod.path .. DIR_DELIM .. FILENAME
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
if not clientmods_category_initialized then
|
||||
fgettext_ne("Client Mods") -- not used, but needed for xgettext
|
||||
table.insert(settings, {
|
||||
name = "Client Mods",
|
||||
level = 0,
|
||||
type = "category",
|
||||
})
|
||||
clientmods_category_initialized = true
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = mod.name,
|
||||
level = 1,
|
||||
@@ -497,44 +528,40 @@ end
|
||||
|
||||
local function get_current_np_group(setting)
|
||||
local value = core.settings:get_np_group(setting.name)
|
||||
local t = {}
|
||||
if value == nil then
|
||||
t = setting.values
|
||||
else
|
||||
table.insert(t, value.offset)
|
||||
table.insert(t, value.scale)
|
||||
table.insert(t, value.spread.x)
|
||||
table.insert(t, value.spread.y)
|
||||
table.insert(t, value.spread.z)
|
||||
table.insert(t, value.seed)
|
||||
table.insert(t, value.octaves)
|
||||
table.insert(t, value.persistence)
|
||||
table.insert(t, value.lacunarity)
|
||||
table.insert(t, value.flags)
|
||||
return setting.values
|
||||
end
|
||||
return t
|
||||
local p = "%g"
|
||||
return {
|
||||
p:format(value.offset),
|
||||
p:format(value.scale),
|
||||
p:format(value.spread.x),
|
||||
p:format(value.spread.y),
|
||||
p:format(value.spread.z),
|
||||
p:format(value.seed),
|
||||
p:format(value.octaves),
|
||||
p:format(value.persistence),
|
||||
p:format(value.lacunarity),
|
||||
value.flags
|
||||
}
|
||||
end
|
||||
|
||||
local function get_current_np_group_as_string(setting)
|
||||
local value = core.settings:get_np_group(setting.name)
|
||||
local t
|
||||
if value == nil then
|
||||
t = setting.default
|
||||
else
|
||||
t = value.offset .. ", " ..
|
||||
value.scale .. ", (" ..
|
||||
value.spread.x .. ", " ..
|
||||
value.spread.y .. ", " ..
|
||||
value.spread.z .. "), " ..
|
||||
value.seed .. ", " ..
|
||||
value.octaves .. ", " ..
|
||||
value.persistence .. ", " ..
|
||||
value.lacunarity
|
||||
if value.flags ~= "" then
|
||||
t = t .. ", " .. value.flags
|
||||
end
|
||||
return setting.default
|
||||
end
|
||||
return t
|
||||
return ("%g, %g, (%g, %g, %g), %g, %g, %g, %g"):format(
|
||||
value.offset,
|
||||
value.scale,
|
||||
value.spread.x,
|
||||
value.spread.y,
|
||||
value.spread.z,
|
||||
value.seed,
|
||||
value.octaves,
|
||||
value.persistence,
|
||||
value.lacunarity
|
||||
) .. (value.flags ~= "" and (", " .. value.flags) or "")
|
||||
end
|
||||
|
||||
local checkboxes = {} -- handle checkboxes events
|
||||
@@ -667,7 +694,7 @@ local function create_change_setting_formspec(dialogdata)
|
||||
elseif setting.type == "v3f" then
|
||||
local val = get_current_value(setting)
|
||||
local v3f = {}
|
||||
for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
|
||||
for line in val:gmatch("[+-]?[%d.+-eE]+") do -- All numeric characters
|
||||
table.insert(v3f, line)
|
||||
end
|
||||
|
||||
@@ -960,7 +987,7 @@ local function create_settings_formspec(tabview, _, tabdata)
|
||||
local current_level = 0
|
||||
for _, entry in ipairs(settings) do
|
||||
local name
|
||||
if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
|
||||
if not core.settings:get_bool("show_technical_names") and entry.readable_name then
|
||||
name = fgettext_ne(entry.readable_name)
|
||||
else
|
||||
name = entry.name
|
||||
@@ -1001,7 +1028,7 @@ local function create_settings_formspec(tabview, _, tabdata)
|
||||
"button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
|
||||
"button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
|
||||
"checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
|
||||
.. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
|
||||
.. dump(core.settings:get_bool("show_technical_names")) .. "]"
|
||||
|
||||
return formspec
|
||||
end
|
||||
@@ -1084,7 +1111,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
|
||||
end
|
||||
|
||||
if fields["cb_tech_settings"] then
|
||||
core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
|
||||
core.settings:set("show_technical_names", fields["cb_tech_settings"])
|
||||
core.settings:write()
|
||||
core.update_formspec(this:get_formspec())
|
||||
return true
|
||||
|
||||
172
builtin/mainmenu/dlg_version_info.lua
Normal file
@@ -0,0 +1,172 @@
|
||||
--[[
|
||||
Minetest
|
||||
Copyright (C) 2018-2020 SmallJoker, 2022 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.
|
||||
]]
|
||||
|
||||
if not core.get_http_api then
|
||||
function check_new_version()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local function version_info_formspec(data)
|
||||
local cur_ver = core.get_version()
|
||||
local title = fgettext("A new $1 version is available", cur_ver.project)
|
||||
local message =
|
||||
fgettext("Installed version: $1\nNew version: $2\n" ..
|
||||
"Visit $3 to find out how to get the newest version and stay up to date" ..
|
||||
" with features and bugfixes.",
|
||||
cur_ver.string, data.new_version or "", data.url or "")
|
||||
|
||||
local fs = {
|
||||
"formspec_version[3]",
|
||||
"size[12.8,7]",
|
||||
"style_type[label;textcolor=#0E0]",
|
||||
"label[0.5,0.8;", core.formspec_escape(title), "]",
|
||||
"textarea[0.4,1.6;12,3.4;;;",
|
||||
core.formspec_escape(message), "]",
|
||||
"container[0.4,5.8]",
|
||||
"button[0.0,0;4.0,0.8;version_check_visit;", fgettext("Visit website"), "]",
|
||||
"button[4.5,0;3.5,0.8;version_check_remind;", fgettext("Later"), "]",
|
||||
"button[8.5.5,0;3.5,0.8;version_check_never;", fgettext("Never"), "]",
|
||||
"container_end[]",
|
||||
}
|
||||
|
||||
return table.concat(fs, "")
|
||||
end
|
||||
|
||||
local function version_info_buttonhandler(this, fields)
|
||||
if fields.version_check_remind then
|
||||
-- Erase last known, user will be reminded again at next check
|
||||
core.settings:set("update_last_known", "")
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
if fields.version_check_never then
|
||||
core.settings:set("update_last_checked", "disabled")
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
if fields.version_check_visit then
|
||||
if type(this.data.url) == "string" then
|
||||
core.open_url(this.data.url)
|
||||
end
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function create_version_info_dlg(new_version, url)
|
||||
assert(type(new_version) == "string")
|
||||
assert(type(url) == "string")
|
||||
|
||||
local retval = dialog_create("version_info",
|
||||
version_info_formspec,
|
||||
version_info_buttonhandler,
|
||||
nil)
|
||||
|
||||
retval.data.new_version = new_version
|
||||
retval.data.url = url
|
||||
|
||||
return retval
|
||||
end
|
||||
|
||||
local function get_current_version_code()
|
||||
-- Format: Major.Minor.Patch
|
||||
-- Convert to MMMNNNPPP
|
||||
local cur_string = core.get_version().string
|
||||
local cur_major, cur_minor, cur_patch = cur_string:match("^(%d+).(%d+).(%d+)")
|
||||
|
||||
if not cur_patch then
|
||||
core.log("error", "Failed to parse version numbers (invalid tag format?)")
|
||||
return
|
||||
end
|
||||
|
||||
return (cur_major * 1000 + cur_minor) * 1000 + cur_patch
|
||||
end
|
||||
|
||||
local function on_version_info_received(json)
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
if maintab.hidden then
|
||||
-- Another dialog is open, abort.
|
||||
return
|
||||
end
|
||||
|
||||
local known_update = tonumber(core.settings:get("update_last_known")) or 0
|
||||
|
||||
-- Format: MMNNPPP (Major, Minor, Patch)
|
||||
local new_number = type(json.latest) == "table" and json.latest.version_code
|
||||
if type(new_number) ~= "number" then
|
||||
core.log("error", "Failed to read version number (invalid response?)")
|
||||
return
|
||||
end
|
||||
|
||||
local cur_number = get_current_version_code()
|
||||
if new_number <= known_update or new_number < cur_number then
|
||||
return
|
||||
end
|
||||
|
||||
-- Also consider updating from 1.2.3-dev to 1.2.3
|
||||
if new_number == cur_number and not core.get_version().is_dev then
|
||||
return
|
||||
end
|
||||
|
||||
core.settings:set("update_last_known", tostring(new_number))
|
||||
|
||||
-- Show version info dialog (once)
|
||||
maintab:hide()
|
||||
|
||||
local version_info_dlg = create_version_info_dlg(json.latest.version, json.latest.url)
|
||||
version_info_dlg:set_parent(maintab)
|
||||
version_info_dlg:show()
|
||||
|
||||
ui.update()
|
||||
end
|
||||
|
||||
function check_new_version()
|
||||
local url = core.settings:get("update_information_url")
|
||||
if core.settings:get("update_last_checked") == "disabled" or
|
||||
url == "" then
|
||||
-- Never show any updates
|
||||
return
|
||||
end
|
||||
|
||||
local time_now = os.time()
|
||||
local time_checked = tonumber(core.settings:get("update_last_checked")) or 0
|
||||
if time_now - time_checked < 2 * 24 * 3600 then
|
||||
-- Check interval of 2 entire days
|
||||
return
|
||||
end
|
||||
|
||||
core.settings:set("update_last_checked", tostring(time_now))
|
||||
|
||||
core.handle_async(function(params)
|
||||
local http = core.get_http_api()
|
||||
return http.fetch_sync(params)
|
||||
end, { url = url }, function(result)
|
||||
local json = result.succeeded and core.parse_json(result.data)
|
||||
if type(json) ~= "table" or not json.latest then
|
||||
core.log("error", "Failed to read JSON output from " .. url ..
|
||||
", status code = " .. result.code)
|
||||
return
|
||||
end
|
||||
|
||||
on_version_info_received(json)
|
||||
end)
|
||||
end
|
||||
@@ -31,7 +31,7 @@ local group_format_template = [[
|
||||
# octaves = %s,
|
||||
# persistence = %s,
|
||||
# lacunarity = %s,
|
||||
# flags = %s
|
||||
# flags =%s
|
||||
# }
|
||||
|
||||
]]
|
||||
@@ -55,7 +55,11 @@ local function create_minetest_conf_example()
|
||||
end
|
||||
if entry.comment ~= "" then
|
||||
for _, comment_line in ipairs(entry.comment:split("\n", true)) do
|
||||
insert(result, "# " .. comment_line .. "\n")
|
||||
if comment_line == "" then
|
||||
insert(result, "#\n")
|
||||
else
|
||||
insert(result, "# " .. comment_line .. "\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
insert(result, "# type: " .. entry.type)
|
||||
@@ -73,10 +77,14 @@ local function create_minetest_conf_example()
|
||||
end
|
||||
insert(result, "\n")
|
||||
if group_format == true then
|
||||
local flags = entry.values[10]
|
||||
if flags ~= "" then
|
||||
flags = " "..flags
|
||||
end
|
||||
insert(result, sprintf(group_format_template, entry.name, entry.values[1],
|
||||
entry.values[2], entry.values[3], entry.values[4], entry.values[5],
|
||||
entry.values[6], entry.values[7], entry.values[8], entry.values[9],
|
||||
entry.values[10]))
|
||||
flags))
|
||||
else
|
||||
local append
|
||||
if entry.default ~= "" then
|
||||
@@ -91,7 +99,7 @@ end
|
||||
|
||||
local translation_file_header = [[
|
||||
// This file is automatically generated
|
||||
// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files
|
||||
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files
|
||||
// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
|
||||
|
||||
fake_function() {]]
|
||||
@@ -126,4 +134,3 @@ file = assert(io.open("src/settings_translation_file.cpp", "w"))
|
||||
--file = assert(io.open("settings_translation_file.cpp", "w"))
|
||||
file:write(create_translation_file())
|
||||
file:close()
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
|
||||
mt_color_grey = "#AAAAAA"
|
||||
mt_color_blue = "#6389FF"
|
||||
mt_color_lightblue = "#99CCFF"
|
||||
mt_color_green = "#72FF63"
|
||||
mt_color_dark_green = "#25C191"
|
||||
mt_color_orange = "#FF8800"
|
||||
mt_color_red = "#FF3300"
|
||||
|
||||
local menupath = core.get_mainmenu_path()
|
||||
local basepath = core.get_builtin_path()
|
||||
@@ -43,7 +45,9 @@ dofile(menupath .. DIR_DELIM .. "dlg_contentstore.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_register.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua")
|
||||
|
||||
local tabs = {}
|
||||
|
||||
@@ -91,6 +95,7 @@ local function init_globals()
|
||||
|
||||
-- Create main tabview
|
||||
local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0})
|
||||
-- note: size would be 15.5,7.1 in real coordinates mode
|
||||
|
||||
tv_main:set_autosave_tab(true)
|
||||
tv_main:add(tabs.local_game)
|
||||
@@ -118,8 +123,8 @@ local function init_globals()
|
||||
end
|
||||
|
||||
ui.set_default("maintab")
|
||||
check_new_version()
|
||||
tv_main:show()
|
||||
|
||||
ui.update()
|
||||
end
|
||||
|
||||
|
||||
@@ -78,34 +78,35 @@ local function load_texture_packs(txtpath, retval)
|
||||
|
||||
for _, item in ipairs(list) do
|
||||
if item ~= "base" then
|
||||
local name = item
|
||||
|
||||
local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
|
||||
if path == current_texture_path then
|
||||
name = fgettext("$1 (Enabled)", name)
|
||||
end
|
||||
|
||||
local conf = Settings(path .. "texture_pack.conf")
|
||||
local enabled = path == current_texture_path
|
||||
|
||||
local title = conf:get("title") or item
|
||||
|
||||
-- list_* is only used if non-nil, else the regular versions are used.
|
||||
retval[#retval + 1] = {
|
||||
name = item,
|
||||
title = title,
|
||||
list_name = enabled and fgettext("$1 (Enabled)", item) or nil,
|
||||
list_title = enabled and fgettext("$1 (Enabled)", title) or nil,
|
||||
author = conf:get("author"),
|
||||
release = tonumber(conf:get("release")) or 0,
|
||||
list_name = name,
|
||||
type = "txp",
|
||||
path = path,
|
||||
enabled = path == current_texture_path,
|
||||
enabled = enabled,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function get_mods(path,retval,modpack)
|
||||
function get_mods(path, virtual_path, retval, modpack)
|
||||
local mods = core.get_dir_list(path, true)
|
||||
|
||||
for _, name in ipairs(mods) do
|
||||
if name:sub(1, 1) ~= "." then
|
||||
local prefix = path .. DIR_DELIM .. name
|
||||
local mod_path = path .. DIR_DELIM .. name
|
||||
local mod_virtual_path = virtual_path .. "/" .. name
|
||||
local toadd = {
|
||||
dir_name = name,
|
||||
parent_dir = path,
|
||||
@@ -114,18 +115,18 @@ function get_mods(path,retval,modpack)
|
||||
|
||||
-- Get config file
|
||||
local mod_conf
|
||||
local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf")
|
||||
local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf")
|
||||
if modpack_conf then
|
||||
toadd.is_modpack = true
|
||||
modpack_conf:close()
|
||||
|
||||
mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table()
|
||||
mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table()
|
||||
if mod_conf.name then
|
||||
name = mod_conf.name
|
||||
toadd.is_name_explicit = true
|
||||
end
|
||||
else
|
||||
mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table()
|
||||
mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table()
|
||||
if mod_conf.name then
|
||||
name = mod_conf.name
|
||||
toadd.is_name_explicit = true
|
||||
@@ -134,14 +135,16 @@ function get_mods(path,retval,modpack)
|
||||
|
||||
-- Read from config
|
||||
toadd.name = name
|
||||
toadd.title = mod_conf.title
|
||||
toadd.author = mod_conf.author
|
||||
toadd.release = tonumber(mod_conf.release) or 0
|
||||
toadd.path = prefix
|
||||
toadd.path = mod_path
|
||||
toadd.virtual_path = mod_virtual_path
|
||||
toadd.type = "mod"
|
||||
|
||||
-- Check modpack.txt
|
||||
-- Note: modpack.conf is already checked above
|
||||
local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
|
||||
local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
|
||||
if modpackfile then
|
||||
modpackfile:close()
|
||||
toadd.is_modpack = true
|
||||
@@ -153,7 +156,7 @@ function get_mods(path,retval,modpack)
|
||||
elseif toadd.is_modpack then
|
||||
toadd.type = "modpack"
|
||||
toadd.is_modpack = true
|
||||
get_mods(prefix, retval, name)
|
||||
get_mods(mod_path, mod_virtual_path, retval, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -334,7 +337,7 @@ function pkgmgr.identify_modname(modpath,filename)
|
||||
return nil
|
||||
end
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.render_packagelist(render_list)
|
||||
function pkgmgr.render_packagelist(render_list, use_technical_names, with_error)
|
||||
if not render_list then
|
||||
if not pkgmgr.global_mods then
|
||||
pkgmgr.refresh_globals()
|
||||
@@ -346,31 +349,75 @@ function pkgmgr.render_packagelist(render_list)
|
||||
local retval = {}
|
||||
for i, v in ipairs(list) do
|
||||
local color = ""
|
||||
local icon = 0
|
||||
local error = with_error and with_error[v.virtual_path]
|
||||
local function update_error(val)
|
||||
if val and (not error or (error.type == "warning" and val.type == "error")) then
|
||||
error = val
|
||||
end
|
||||
end
|
||||
|
||||
if v.is_modpack then
|
||||
local rawlist = render_list:get_raw_list()
|
||||
color = mt_color_dark_green
|
||||
|
||||
for j = 1, #rawlist, 1 do
|
||||
if rawlist[j].modpack == list[i].name and
|
||||
not rawlist[j].enabled then
|
||||
-- Modpack not entirely enabled so showing as grey
|
||||
color = mt_color_grey
|
||||
break
|
||||
for j = 1, #rawlist do
|
||||
if rawlist[j].modpack == list[i].name then
|
||||
if with_error then
|
||||
update_error(with_error[rawlist[j].virtual_path])
|
||||
end
|
||||
|
||||
if rawlist[j].enabled then
|
||||
icon = 1
|
||||
else
|
||||
-- Modpack not entirely enabled so showing as grey
|
||||
color = mt_color_grey
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif v.is_game_content or v.type == "game" then
|
||||
icon = 1
|
||||
color = mt_color_blue
|
||||
|
||||
local rawlist = render_list:get_raw_list()
|
||||
if v.type == "game" and with_error then
|
||||
for j = 1, #rawlist do
|
||||
if rawlist[j].is_game_content then
|
||||
update_error(with_error[rawlist[j].virtual_path])
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif v.enabled or v.type == "txp" then
|
||||
icon = 1
|
||||
color = mt_color_green
|
||||
end
|
||||
|
||||
if error then
|
||||
if error.type == "warning" then
|
||||
color = mt_color_orange
|
||||
icon = 2
|
||||
else
|
||||
color = mt_color_red
|
||||
icon = 3
|
||||
end
|
||||
end
|
||||
|
||||
retval[#retval + 1] = color
|
||||
if v.modpack ~= nil or v.loc == "game" then
|
||||
retval[#retval + 1] = "1"
|
||||
else
|
||||
retval[#retval + 1] = "0"
|
||||
end
|
||||
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
|
||||
|
||||
if with_error then
|
||||
retval[#retval + 1] = icon
|
||||
end
|
||||
|
||||
if use_technical_names then
|
||||
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
|
||||
else
|
||||
retval[#retval + 1] = core.formspec_escape(v.list_title or v.list_name or v.title or v.name)
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(retval, ",")
|
||||
@@ -397,6 +444,14 @@ function pkgmgr.is_modpack_entirely_enabled(data, name)
|
||||
return true
|
||||
end
|
||||
|
||||
local function disable_all_by_name(list, name, except)
|
||||
for i=1, #list do
|
||||
if list[i].name == name and list[i] ~= except then
|
||||
list[i].enabled = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---------- toggles or en/disables a mod or modpack and its dependencies --------
|
||||
local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
|
||||
if not mod.is_modpack then
|
||||
@@ -405,13 +460,16 @@ local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mo
|
||||
toset = not mod.enabled
|
||||
end
|
||||
if mod.enabled ~= toset then
|
||||
mod.enabled = toset
|
||||
toggled_mods[#toggled_mods+1] = mod.name
|
||||
end
|
||||
if toset then
|
||||
-- Mark this mod for recursive dependency traversal
|
||||
enabled_mods[mod.name] = true
|
||||
|
||||
-- Disable other mods with the same name
|
||||
disable_all_by_name(list, mod.name, mod)
|
||||
end
|
||||
mod.enabled = toset
|
||||
else
|
||||
-- Toggle or en/disable every mod in the modpack,
|
||||
-- interleaved unsupported
|
||||
@@ -472,6 +530,7 @@ function pkgmgr.enable_mod(this, toset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If sp is 0, every dependency is already activated
|
||||
while sp > 0 do
|
||||
local name = to_enable[sp]
|
||||
@@ -483,8 +542,8 @@ function pkgmgr.enable_mod(this, toset)
|
||||
if not mod_to_enable then
|
||||
core.log("warning", "Mod dependency \"" .. name ..
|
||||
"\" not found!")
|
||||
else
|
||||
if not mod_to_enable.enabled then
|
||||
elseif not mod_to_enable.is_game_content then
|
||||
if not mod_to_enable.enabled then
|
||||
mod_to_enable.enabled = true
|
||||
toggled_mods[#toggled_mods+1] = mod_to_enable.name
|
||||
end
|
||||
@@ -549,11 +608,10 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
|
||||
end
|
||||
|
||||
local from = basefolder and basefolder.path or path
|
||||
if targetpath then
|
||||
core.delete_dir(targetpath)
|
||||
else
|
||||
if not targetpath then
|
||||
targetpath = core.get_texturepath() .. DIR_DELIM .. basename
|
||||
end
|
||||
core.delete_dir(targetpath)
|
||||
if not core.copy_dir(from, targetpath, false) then
|
||||
return nil,
|
||||
fgettext("Failed to install $1 to $2", basename, targetpath)
|
||||
@@ -626,9 +684,12 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
|
||||
else
|
||||
targetpath = core.get_gamepath() .. DIR_DELIM .. basename
|
||||
end
|
||||
else
|
||||
error("basefolder didn't return a recognised type, this shouldn't happen")
|
||||
end
|
||||
|
||||
-- Copy it
|
||||
core.delete_dir(targetpath)
|
||||
if not core.copy_dir(basefolder.path, targetpath, false) then
|
||||
return nil,
|
||||
fgettext("Failed to install $1 to $2", basename, targetpath)
|
||||
@@ -652,13 +713,14 @@ function pkgmgr.preparemodlist(data)
|
||||
|
||||
--read global mods
|
||||
local modpaths = core.get_modpaths()
|
||||
for _, modpath in ipairs(modpaths) do
|
||||
get_mods(modpath, global_mods)
|
||||
for key, modpath in pairs(modpaths) do
|
||||
get_mods(modpath, key, global_mods)
|
||||
end
|
||||
|
||||
for i=1,#global_mods,1 do
|
||||
global_mods[i].type = "mod"
|
||||
global_mods[i].loc = "global"
|
||||
global_mods[i].enabled = false
|
||||
retval[#retval + 1] = global_mods[i]
|
||||
end
|
||||
|
||||
@@ -671,7 +733,7 @@ function pkgmgr.preparemodlist(data)
|
||||
retval[#retval + 1] = {
|
||||
type = "game",
|
||||
is_game_content = true,
|
||||
name = fgettext("$1 mods", gamespec.name),
|
||||
name = fgettext("$1 mods", gamespec.title),
|
||||
path = gamespec.path
|
||||
}
|
||||
end
|
||||
@@ -692,22 +754,37 @@ function pkgmgr.preparemodlist(data)
|
||||
DIR_DELIM .. "world.mt"
|
||||
|
||||
local worldfile = Settings(filename)
|
||||
|
||||
for key,value in pairs(worldfile:to_table()) do
|
||||
for key, value in pairs(worldfile:to_table()) do
|
||||
if key:sub(1, 9) == "load_mod_" then
|
||||
key = key:sub(10)
|
||||
local element = nil
|
||||
for i=1,#retval,1 do
|
||||
local mod_found = false
|
||||
|
||||
local fallback_found = false
|
||||
local fallback_mod = nil
|
||||
|
||||
for i=1, #retval do
|
||||
if retval[i].name == key and
|
||||
not retval[i].is_modpack then
|
||||
element = retval[i]
|
||||
break
|
||||
not retval[i].is_modpack then
|
||||
if core.is_yes(value) or retval[i].virtual_path == value then
|
||||
retval[i].enabled = true
|
||||
mod_found = true
|
||||
break
|
||||
elseif fallback_found then
|
||||
-- Only allow fallback if only one mod matches
|
||||
fallback_mod = nil
|
||||
else
|
||||
fallback_found = true
|
||||
fallback_mod = retval[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
if element ~= nil then
|
||||
element.enabled = value ~= "false" and value ~= "nil" and value
|
||||
else
|
||||
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
|
||||
|
||||
if not mod_found then
|
||||
if fallback_mod and value:find("/") then
|
||||
fallback_mod.enabled = true
|
||||
else
|
||||
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -801,7 +878,7 @@ function pkgmgr.get_game_mods(gamespec, retval)
|
||||
if gamespec ~= nil and
|
||||
gamespec.gamemods_path ~= nil and
|
||||
gamespec.gamemods_path ~= "" then
|
||||
get_mods(gamespec.gamemods_path, retval)
|
||||
get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -837,10 +914,10 @@ end
|
||||
function pkgmgr.gamelist()
|
||||
local retval = ""
|
||||
if #pkgmgr.games > 0 then
|
||||
retval = retval .. core.formspec_escape(pkgmgr.games[1].name)
|
||||
retval = retval .. core.formspec_escape(pkgmgr.games[1].title)
|
||||
|
||||
for i=2,#pkgmgr.games,1 do
|
||||
retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].name)
|
||||
retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].title)
|
||||
end
|
||||
end
|
||||
return retval
|
||||
|
||||
@@ -15,48 +15,46 @@
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--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>",
|
||||
"Perttu Ahola (celeron55) <celeron55@gmail.com> [Project founder]",
|
||||
"sfan5 <sfan5@live.de>",
|
||||
"Nathanaël Courant (Nore/Ekdohibs) <nore@mesecons.net>",
|
||||
"ShadowNinja <shadowninja@minetest.net>",
|
||||
"Nathanaëlle Courant (Nore/Ekdohibs) <nore@mesecons.net>",
|
||||
"Loic Blot (nerzhul/nrz) <loic.blot@unix-experience.fr>",
|
||||
"paramat",
|
||||
"Andrew Ward (rubenwardy) <rw@rubenwardy.com>",
|
||||
"Krock/SmallJoker <mk939@ymail.com>",
|
||||
"Lars Hofhansl <larsh@apache.org>",
|
||||
"Pierre-Yves Rollo <dev@pyrollo.com>",
|
||||
"v-rob <robinsonvincent89@gmail.com>",
|
||||
"hecks",
|
||||
"Hugues Ross <hugues.ross@gmail.com>",
|
||||
"Dmitry Kostenko (x2048) <codeforsmile@gmail.com>",
|
||||
}
|
||||
|
||||
local core_team = {
|
||||
"Zughy [Issue triager]",
|
||||
}
|
||||
|
||||
-- For updating active/previous contributors, see the script in ./util/gather_git_credits.py
|
||||
|
||||
local active_contributors = {
|
||||
"Wuzzy [I18n for builtin, liquid features, fixes]",
|
||||
"Zughy [Various features and fixes]",
|
||||
"numzero [Graphics and rendering]",
|
||||
"Desour [Internal fixes, Clipboard on X11]",
|
||||
"Lars Müller [Various internal fixes]",
|
||||
"JosiahWI [CMake, cleanups and fixes]",
|
||||
"HybridDog [builtin, documentation]",
|
||||
"Jude Melton-Houghton [Database implementation]",
|
||||
"savilli [Fixes]",
|
||||
"Wuzzy [Features, translations, devtest]",
|
||||
"Lars Müller [Lua optimizations and fixes]",
|
||||
"Jude Melton-Houghton [Optimizations, bugfixes]",
|
||||
"paradust7 [Performance, fixes, Irrlicht refactoring]",
|
||||
"Desour [Fixes]",
|
||||
"ROllerozxa [Main menu]",
|
||||
"savilli [Bugfixes]",
|
||||
"Lexi Hale [Particlespawner animation]",
|
||||
"Liso [Shadow Mapping]",
|
||||
"MoNTE48 [Build fix]",
|
||||
"Jean-Patrick Guerrero (kilbith) [Fixes]",
|
||||
"ROllerozxa [Code cleanups]",
|
||||
"Lejo [bitop library integration]",
|
||||
"LoneWolfHT [Build fixes]",
|
||||
"JosiahWI [Fixes, build system]",
|
||||
"numzero [Graphics and rendering]",
|
||||
"HybridDog [Fixes]",
|
||||
"NeroBurner [Joystick]",
|
||||
"Elias Fleckenstein [Internal fixes]",
|
||||
"David CARLIER [Unix & Haiku build fixes]",
|
||||
"pecksin [Clickable web links]",
|
||||
"srfqi [Android & rendering fixes]",
|
||||
"EvidenceB [Formspec]",
|
||||
"Daroc Alden [Fixes]",
|
||||
"Jean-Patrick Guerrero (kilbith) [Fixes]",
|
||||
}
|
||||
|
||||
local previous_core_developers = {
|
||||
@@ -71,33 +69,50 @@ local previous_core_developers = {
|
||||
"Ryan Kwolek (kwolekr) <kwolekr@minetest.net>",
|
||||
"sapier",
|
||||
"Zeno",
|
||||
"ShadowNinja <shadowninja@minetest.net>",
|
||||
"Auke Kok (sofar) <sofar@foo-projects.org>",
|
||||
"Aaron Suen <warr1024@gmail.com>",
|
||||
"paramat",
|
||||
"Pierre-Yves Rollo <dev@pyrollo.com>",
|
||||
}
|
||||
|
||||
local previous_contributors = {
|
||||
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest Logo]",
|
||||
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest logo]",
|
||||
"red-001 <red-001@outlook.ie>",
|
||||
"Giuseppe Bilotta",
|
||||
"ClobberXD",
|
||||
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
|
||||
"MirceaKitsune <mirceakitsune@gmail.com>",
|
||||
"MoNTE48",
|
||||
"Constantin Wenger (SpeedProg)",
|
||||
"Ciaran Gultnieks (CiaranG)",
|
||||
"Paul Ouellette (pauloue)",
|
||||
"stujones11",
|
||||
"srifqi",
|
||||
"Rogier <rogier777@gmail.com>",
|
||||
"Gregory Currie (gregorycu)",
|
||||
"JacobF",
|
||||
"Jeija <jeija@mesecons.net> [HTTP, particles]",
|
||||
"Jeija <jeija@mesecons.net>",
|
||||
}
|
||||
|
||||
local function buildCreditList(source)
|
||||
local ret = {}
|
||||
for i = 1, #source do
|
||||
ret[i] = core.formspec_escape(source[i])
|
||||
local function prepare_credits(dest, source)
|
||||
for _, s in ipairs(source) do
|
||||
-- if there's text inside brackets make it gray-ish
|
||||
s = s:gsub("%[.-%]", core.colorize("#aaa", "%1"))
|
||||
dest[#dest+1] = s
|
||||
end
|
||||
return table.concat(ret, ",,")
|
||||
end
|
||||
|
||||
local function build_hacky_list(items, spacing)
|
||||
spacing = spacing or 0.5
|
||||
local y = spacing / 2
|
||||
local ret = {}
|
||||
for _, item in ipairs(items) do
|
||||
if item ~= "" then
|
||||
ret[#ret+1] = ("label[0,%f;%s]"):format(y, core.formspec_escape(item))
|
||||
end
|
||||
y = y + spacing
|
||||
end
|
||||
return table.concat(ret, ""), y
|
||||
end
|
||||
|
||||
return {
|
||||
@@ -106,42 +121,73 @@ return {
|
||||
cbf_formspec = function(tabview, name, tabdata)
|
||||
local logofile = defaulttexturedir .. "logo.png"
|
||||
local version = core.get_version()
|
||||
local fs = "image[0.75,0.5;2.2,2.2;" .. core.formspec_escape(logofile) .. "]" ..
|
||||
|
||||
local credit_list = {}
|
||||
table.insert_all(credit_list, {
|
||||
core.colorize("#ff0", fgettext("Core Developers"))
|
||||
})
|
||||
prepare_credits(credit_list, core_developers)
|
||||
table.insert_all(credit_list, {
|
||||
"",
|
||||
core.colorize("#ff0", fgettext("Core Team"))
|
||||
})
|
||||
prepare_credits(credit_list, core_team)
|
||||
table.insert_all(credit_list, {
|
||||
"",
|
||||
core.colorize("#ff0", fgettext("Active Contributors"))
|
||||
})
|
||||
prepare_credits(credit_list, active_contributors)
|
||||
table.insert_all(credit_list, {
|
||||
"",
|
||||
core.colorize("#ff0", fgettext("Previous Core Developers"))
|
||||
})
|
||||
prepare_credits(credit_list, previous_core_developers)
|
||||
table.insert_all(credit_list, {
|
||||
"",
|
||||
core.colorize("#ff0", fgettext("Previous Contributors"))
|
||||
})
|
||||
prepare_credits(credit_list, previous_contributors)
|
||||
local credit_fs, scroll_height = build_hacky_list(credit_list)
|
||||
-- account for the visible portion
|
||||
scroll_height = math.max(0, scroll_height - 6.9)
|
||||
|
||||
local fs = "image[1.5,0.6;2.5,2.5;" .. core.formspec_escape(logofile) .. "]" ..
|
||||
"style[label_button;border=false]" ..
|
||||
"button[0.5,2;2.5,2;label_button;" .. version.project .. " " .. version.string .. "]" ..
|
||||
"button[0.75,2.75;2,2;homepage;minetest.net]" ..
|
||||
"tablecolumns[color;text]" ..
|
||||
"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
|
||||
"table[3.5,-0.25;8.5,6.05;list_credits;" ..
|
||||
"#FFFF00," .. fgettext("Core Developers") .. ",," ..
|
||||
buildCreditList(core_developers) .. ",,," ..
|
||||
"#FFFF00," .. fgettext("Active Contributors") .. ",," ..
|
||||
buildCreditList(active_contributors) .. ",,," ..
|
||||
"#FFFF00," .. fgettext("Previous Core Developers") ..",," ..
|
||||
buildCreditList(previous_core_developers) .. ",,," ..
|
||||
"#FFFF00," .. fgettext("Previous Contributors") .. ",," ..
|
||||
buildCreditList(previous_contributors) .. "," ..
|
||||
";1]"
|
||||
"button[0.1,3.4;5.3,0.5;label_button;" ..
|
||||
core.formspec_escape(version.project .. " " .. version.string) .. "]" ..
|
||||
"button[1.5,4.1;2.5,0.8;homepage;minetest.net]" ..
|
||||
"scroll_container[5.5,0.1;9.5,6.9;scroll_credits;vertical;" ..
|
||||
tostring(scroll_height / 1000) .. "]" .. credit_fs ..
|
||||
"scroll_container_end[]"..
|
||||
"scrollbar[15,0.1;0.4,6.9;vertical;scroll_credits;0]"
|
||||
|
||||
-- Render information
|
||||
fs = fs .. "label[0.75,4.9;" ..
|
||||
fs = fs .. "style[label_button2;border=false]" ..
|
||||
"button[0.1,6;5.3,1;label_button2;" ..
|
||||
fgettext("Active renderer:") .. "\n" ..
|
||||
core.formspec_escape(core.get_screen_info().render_info) .. "]"
|
||||
|
||||
if PLATFORM ~= "Android" then
|
||||
if PLATFORM == "Android" then
|
||||
fs = fs .. "button[0.5,5.1;4.5,0.8;share_debug;" .. fgettext("Share debug log") .. "]"
|
||||
else
|
||||
fs = fs .. "tooltip[userdata;" ..
|
||||
fgettext("Opens the directory that contains user-provided worlds, games, mods,\n" ..
|
||||
"and texture packs in a file manager / explorer.") .. "]"
|
||||
fs = fs .. "button[0,4;3.5,1;userdata;" .. fgettext("Open User Data Directory") .. "]"
|
||||
fs = fs .. "button[0.5,5.1;4.5,0.8;userdata;" .. fgettext("Open User Data Directory") .. "]"
|
||||
end
|
||||
|
||||
return fs
|
||||
return fs, "size[15.5,7.1,false]real_coordinates[true]"
|
||||
end,
|
||||
cbf_button_handler = function(this, fields, name, tabdata)
|
||||
if fields.homepage then
|
||||
core.open_url("https://www.minetest.net")
|
||||
end
|
||||
|
||||
if fields.share_debug then
|
||||
local path = core.get_user_path() .. DIR_DELIM .. "debug.txt"
|
||||
core.share_file(path)
|
||||
end
|
||||
|
||||
if fields.userdata then
|
||||
core.open_dir(core.get_user_path())
|
||||
end
|
||||
|
||||
@@ -51,12 +51,14 @@ local function get_formspec(tabview, name, tabdata)
|
||||
tabdata.selected_pkg = 1
|
||||
end
|
||||
|
||||
local use_technical_names = core.settings:get_bool("show_technical_names")
|
||||
|
||||
|
||||
local retval =
|
||||
"label[0.05,-0.25;".. fgettext("Installed Packages:") .. "]" ..
|
||||
"tablecolumns[color;tree;text]" ..
|
||||
"table[0,0.25;5.1,4.3;pkglist;" ..
|
||||
pkgmgr.render_packagelist(packages) ..
|
||||
pkgmgr.render_packagelist(packages, use_technical_names) ..
|
||||
";" .. tabdata.selected_pkg .. "]" ..
|
||||
"button[0,4.85;5.25,0.5;btn_contentdb;".. fgettext("Browse online content") .. "]"
|
||||
|
||||
@@ -87,9 +89,17 @@ local function get_formspec(tabview, name, tabdata)
|
||||
desc = info.description
|
||||
end
|
||||
|
||||
local title_and_name
|
||||
if selected_pkg.type == "game" then
|
||||
title_and_name = selected_pkg.name
|
||||
else
|
||||
title_and_name = (selected_pkg.title or selected_pkg.name) .. "\n" ..
|
||||
core.colorize("#BFBFBF", selected_pkg.name)
|
||||
end
|
||||
|
||||
retval = retval ..
|
||||
"image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" ..
|
||||
"label[8.25,0.6;" .. core.formspec_escape(selected_pkg.name) .. "]" ..
|
||||
"label[8.25,0.6;" .. core.formspec_escape(title_and_name) .. "]" ..
|
||||
"box[5.5,2.2;6.15,2.35;#000]"
|
||||
|
||||
if selected_pkg.type == "mod" then
|
||||
@@ -154,6 +164,9 @@ local function handle_doubleclick(pkg)
|
||||
core.settings:set("texture_path", pkg.path)
|
||||
end
|
||||
packages = nil
|
||||
|
||||
mm_game_theme.init()
|
||||
mm_game_theme.reset()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,17 +210,17 @@ local function handle_buttons(tabview, fields, tabname, tabdata)
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.btn_mod_mgr_use_txp then
|
||||
local txp = packages:get_list()[tabdata.selected_pkg]
|
||||
core.settings:set("texture_path", txp.path)
|
||||
packages = nil
|
||||
return true
|
||||
end
|
||||
if fields.btn_mod_mgr_use_txp or fields.btn_mod_mgr_disable_txp then
|
||||
local txp_path = ""
|
||||
if fields.btn_mod_mgr_use_txp then
|
||||
txp_path = packages:get_list()[tabdata.selected_pkg].path
|
||||
end
|
||||
|
||||
|
||||
if fields.btn_mod_mgr_disable_txp then
|
||||
core.settings:set("texture_path", "")
|
||||
core.settings:set("texture_path", txp_path)
|
||||
packages = nil
|
||||
|
||||
mm_game_theme.init()
|
||||
mm_game_theme.reset()
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
|
||||
local enable_gamebar = PLATFORM ~= "Android"
|
||||
local current_game, singleplayer_refresh_gamebar
|
||||
local valid_disabled_settings = {
|
||||
["enable_damage"]=true,
|
||||
@@ -24,95 +23,88 @@ local valid_disabled_settings = {
|
||||
["enable_server"]=true,
|
||||
}
|
||||
|
||||
if enable_gamebar then
|
||||
-- Currently chosen game in gamebar for theming and filtering
|
||||
function current_game()
|
||||
local last_game_id = core.settings:get("menu_last_game")
|
||||
local game = pkgmgr.find_by_gameid(last_game_id)
|
||||
-- Currently chosen game in gamebar for theming and filtering
|
||||
function current_game()
|
||||
local last_game_id = core.settings:get("menu_last_game")
|
||||
local game = pkgmgr.find_by_gameid(last_game_id)
|
||||
|
||||
return game
|
||||
return game
|
||||
end
|
||||
|
||||
-- Apply menu changes from given game
|
||||
function apply_game(game)
|
||||
core.set_topleft_text(game.name)
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
menudata.worldlist:set_filtercriteria(game.id)
|
||||
|
||||
mm_game_theme.update("singleplayer", game) -- this refreshes the formspec
|
||||
|
||||
local index = filterlist.get_current_index(menudata.worldlist,
|
||||
tonumber(core.settings:get("mainmenu_last_selected_world")))
|
||||
if not index or index < 1 then
|
||||
local selected = core.get_textlist_index("sp_worlds")
|
||||
if selected ~= nil and selected < #menudata.worldlist:get_list() then
|
||||
index = selected
|
||||
else
|
||||
index = #menudata.worldlist:get_list()
|
||||
end
|
||||
end
|
||||
menu_worldmt_legacy(index)
|
||||
end
|
||||
|
||||
function singleplayer_refresh_gamebar()
|
||||
|
||||
local old_bar = ui.find_by_name("game_button_bar")
|
||||
if old_bar ~= nil then
|
||||
old_bar:delete()
|
||||
end
|
||||
|
||||
-- Apply menu changes from given game
|
||||
function apply_game(game)
|
||||
core.set_topleft_text(game.name)
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
menudata.worldlist:set_filtercriteria(game.id)
|
||||
|
||||
mm_game_theme.update("singleplayer", game) -- this refreshes the formspec
|
||||
|
||||
local index = filterlist.get_current_index(menudata.worldlist,
|
||||
tonumber(core.settings:get("mainmenu_last_selected_world")))
|
||||
if not index or index < 1 then
|
||||
local selected = core.get_textlist_index("sp_worlds")
|
||||
if selected ~= nil and selected < #menudata.worldlist:get_list() then
|
||||
index = selected
|
||||
else
|
||||
index = #menudata.worldlist:get_list()
|
||||
end
|
||||
local function game_buttonbar_button_handler(fields)
|
||||
if fields.game_open_cdb then
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
local dlg = create_store_dlg("game")
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:show()
|
||||
return true
|
||||
end
|
||||
menu_worldmt_legacy(index)
|
||||
end
|
||||
|
||||
function singleplayer_refresh_gamebar()
|
||||
|
||||
local old_bar = ui.find_by_name("game_button_bar")
|
||||
if old_bar ~= nil then
|
||||
old_bar:delete()
|
||||
end
|
||||
|
||||
local function game_buttonbar_button_handler(fields)
|
||||
if fields.game_open_cdb then
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
local dlg = create_store_dlg("game")
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:show()
|
||||
return true
|
||||
end
|
||||
|
||||
for _, game in ipairs(pkgmgr.games) do
|
||||
if fields["game_btnbar_" .. game.id] then
|
||||
apply_game(game)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local btnbar = buttonbar_create("game_button_bar",
|
||||
game_buttonbar_button_handler,
|
||||
{x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
|
||||
|
||||
for _, game in ipairs(pkgmgr.games) do
|
||||
local btn_name = "game_btnbar_" .. game.id
|
||||
|
||||
local image = nil
|
||||
local text = nil
|
||||
local tooltip = core.formspec_escape(game.name)
|
||||
|
||||
if (game.menuicon_path or "") ~= "" then
|
||||
image = core.formspec_escape(game.menuicon_path)
|
||||
else
|
||||
local part1 = game.id:sub(1,5)
|
||||
local part2 = game.id:sub(6,10)
|
||||
local part3 = game.id:sub(11)
|
||||
|
||||
text = part1 .. "\n" .. part2
|
||||
if part3 ~= "" then
|
||||
text = text .. "\n" .. part3
|
||||
end
|
||||
if fields["game_btnbar_" .. game.id] then
|
||||
apply_game(game)
|
||||
return true
|
||||
end
|
||||
btnbar:add_button(btn_name, text, image, tooltip)
|
||||
end
|
||||
end
|
||||
|
||||
local plus_image = core.formspec_escape(defaulttexturedir .. "plus.png")
|
||||
btnbar:add_button("game_open_cdb", "", plus_image, fgettext("Install games from ContentDB"))
|
||||
end
|
||||
else
|
||||
-- Currently chosen game in gamebar: no gamebar -> no "current" game
|
||||
function current_game()
|
||||
return nil
|
||||
local btnbar = buttonbar_create("game_button_bar",
|
||||
game_buttonbar_button_handler,
|
||||
{x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
|
||||
|
||||
for _, game in ipairs(pkgmgr.games) do
|
||||
local btn_name = "game_btnbar_" .. game.id
|
||||
|
||||
local image = nil
|
||||
local text = nil
|
||||
local tooltip = core.formspec_escape(game.title)
|
||||
|
||||
if (game.menuicon_path or "") ~= "" then
|
||||
image = core.formspec_escape(game.menuicon_path)
|
||||
else
|
||||
local part1 = game.id:sub(1,5)
|
||||
local part2 = game.id:sub(6,10)
|
||||
local part3 = game.id:sub(11)
|
||||
|
||||
text = part1 .. "\n" .. part2
|
||||
if part3 ~= "" then
|
||||
text = text .. "\n" .. part3
|
||||
end
|
||||
end
|
||||
btnbar:add_button(btn_name, text, image, tooltip)
|
||||
end
|
||||
|
||||
local plus_image = core.formspec_escape(defaulttexturedir .. "plus.png")
|
||||
btnbar:add_button("game_open_cdb", "", plus_image, fgettext("Install games from ContentDB"))
|
||||
end
|
||||
|
||||
local function get_disabled_settings(game)
|
||||
@@ -187,7 +179,7 @@ local function get_formspec(tabview, name, tabdata)
|
||||
damage ..
|
||||
host ..
|
||||
"textlist[3.9,0.4;7.9,3.45;sp_worlds;" ..
|
||||
menu_render_worldlist(not enable_gamebar) ..
|
||||
menu_render_worldlist() ..
|
||||
";" .. index .. "]"
|
||||
|
||||
if core.settings:get_bool("enable_server") and disabled_settings["enable_server"] == nil then
|
||||
@@ -324,7 +316,7 @@ local function main_button_handler(this, fields, name, tabdata)
|
||||
end
|
||||
|
||||
if fields["world_create"] ~= nil then
|
||||
local create_world_dlg = create_create_world_dlg(enable_gamebar)
|
||||
local create_world_dlg = create_create_world_dlg()
|
||||
create_world_dlg:set_parent(this)
|
||||
this:hide()
|
||||
create_world_dlg:show()
|
||||
@@ -371,26 +363,23 @@ local function main_button_handler(this, fields, name, tabdata)
|
||||
end
|
||||
end
|
||||
|
||||
local on_change
|
||||
if enable_gamebar then
|
||||
function on_change(type, old_tab, new_tab)
|
||||
if (type == "ENTER") then
|
||||
local game = current_game()
|
||||
if game then
|
||||
apply_game(game)
|
||||
end
|
||||
|
||||
singleplayer_refresh_gamebar()
|
||||
ui.find_by_name("game_button_bar"):show()
|
||||
else
|
||||
menudata.worldlist:set_filtercriteria(nil)
|
||||
local gamebar = ui.find_by_name("game_button_bar")
|
||||
if gamebar then
|
||||
gamebar:hide()
|
||||
end
|
||||
core.set_topleft_text("")
|
||||
mm_game_theme.update(new_tab,nil)
|
||||
local function on_change(type, old_tab, new_tab)
|
||||
if (type == "ENTER") then
|
||||
local game = current_game()
|
||||
if game then
|
||||
apply_game(game)
|
||||
end
|
||||
|
||||
singleplayer_refresh_gamebar()
|
||||
ui.find_by_name("game_button_bar"):show()
|
||||
else
|
||||
menudata.worldlist:set_filtercriteria(nil)
|
||||
local gamebar = ui.find_by_name("game_button_bar")
|
||||
if gamebar then
|
||||
gamebar:hide()
|
||||
end
|
||||
core.set_topleft_text("")
|
||||
mm_game_theme.update(new_tab,nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -87,27 +87,34 @@ local function get_formspec(tabview, name, tabdata)
|
||||
"field[4.25,0.5;1.25,0.75;te_port;;" ..
|
||||
core.formspec_escape(core.settings:get("remote_port")) .. "]" ..
|
||||
|
||||
-- Name / Password
|
||||
"label[0.25,1.55;" .. fgettext("Name") .. "]" ..
|
||||
"label[3,1.55;" .. fgettext("Password") .. "]" ..
|
||||
"field[0.25,1.75;2.75,0.75;te_name;;" ..
|
||||
core.formspec_escape(core.settings:get("name")) .. "]" ..
|
||||
"pwdfield[3,1.75;2.5,0.75;te_pwd;]" ..
|
||||
|
||||
-- Description Background
|
||||
"label[0.25,2.75;" .. fgettext("Server Description") .. "]" ..
|
||||
"box[0.25,3;5.25,2.75;#999999]"..
|
||||
"label[0.25,1.6;" .. fgettext("Server Description") .. "]" ..
|
||||
"box[0.25,1.85;5.25,2.7;#999999]"..
|
||||
|
||||
-- Name / Password
|
||||
"container[0,4.8]" ..
|
||||
"label[0.25,0;" .. fgettext("Name") .. "]" ..
|
||||
"label[2.875,0;" .. fgettext("Password") .. "]" ..
|
||||
"field[0.25,0.2;2.625,0.75;te_name;;" .. core.formspec_escape(core.settings:get("name")) .. "]" ..
|
||||
"pwdfield[2.875,0.2;2.625,0.75;te_pwd;]" ..
|
||||
"container_end[]" ..
|
||||
|
||||
-- Connect
|
||||
"button[3,6;2.5,0.75;btn_mp_connect;" .. fgettext("Connect") .. "]"
|
||||
"button[3,6;2.5,0.75;btn_mp_login;" .. fgettext("Login") .. "]"
|
||||
|
||||
if core.settings:get_bool("enable_split_login_register") then
|
||||
retval = retval .. "button[0.25,6;2.5,0.75;btn_mp_register;" .. fgettext("Register") .. "]"
|
||||
end
|
||||
|
||||
if tabdata.selected then
|
||||
if gamedata.fav then
|
||||
retval = retval .. "button[0.25,6;2.5,0.75;btn_delete_favorite;" ..
|
||||
fgettext("Del. Favorite") .. "]"
|
||||
retval = retval .. "tooltip[btn_delete_favorite;" .. fgettext("Remove favorite") .. "]"
|
||||
retval = retval .. "style[btn_delete_favorite;padding=6]"
|
||||
retval = retval .. "image_button[5,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir ..
|
||||
"server_favorite_delete.png") .. ";btn_delete_favorite;]"
|
||||
end
|
||||
if gamedata.serverdescription then
|
||||
retval = retval .. "textarea[0.25,3;5.25,2.75;;;" ..
|
||||
retval = retval .. "textarea[0.25,1.85;5.2,2.75;;;" ..
|
||||
core.formspec_escape(gamedata.serverdescription) .. "]"
|
||||
end
|
||||
end
|
||||
@@ -339,12 +346,15 @@ local function main_button_handler(tabview, fields, name, tabdata)
|
||||
return true
|
||||
end
|
||||
|
||||
if (fields.btn_mp_connect or fields.key_enter)
|
||||
if (fields.btn_mp_login or fields.key_enter)
|
||||
and fields.te_address ~= "" and fields.te_port then
|
||||
gamedata.playername = fields.te_name
|
||||
gamedata.password = fields.te_pwd
|
||||
gamedata.address = fields.te_address
|
||||
gamedata.port = tonumber(fields.te_port)
|
||||
|
||||
local enable_split_login_register = core.settings:get_bool("enable_split_login_register")
|
||||
gamedata.allow_login_or_register = enable_split_login_register and "login" or "any"
|
||||
gamedata.selected_world = 0
|
||||
|
||||
local idx = core.get_table_index("servers")
|
||||
@@ -381,6 +391,25 @@ local function main_button_handler(tabview, fields, name, tabdata)
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.btn_mp_register and fields.te_address ~= "" and fields.te_port then
|
||||
local idx = core.get_table_index("servers")
|
||||
local server = idx and tabdata.lookup[idx]
|
||||
if server and (server.address ~= fields.te_address or server.port ~= tonumber(fields.te_port)) then
|
||||
server = nil
|
||||
end
|
||||
|
||||
if server and not is_server_protocol_compat_or_error(
|
||||
server.proto_min, server.proto_max) then
|
||||
return true
|
||||
end
|
||||
|
||||
local dlg = create_register_dialog(fields.te_address, tonumber(fields.te_port), server)
|
||||
dlg:set_parent(tabview)
|
||||
tabview:hide()
|
||||
dlg:show()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ local labels = {
|
||||
fgettext("Low"),
|
||||
fgettext("Medium"),
|
||||
fgettext("High"),
|
||||
fgettext("Ultra High")
|
||||
fgettext("Very High")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ local function formspec(tabview, name, tabdata)
|
||||
.. getSettingIndex.NodeHighlighting() .. "]" ..
|
||||
"dropdown[0.25,3.6;3.5;dd_leaves_style;" .. dd_options.leaves[1] .. ";"
|
||||
.. getSettingIndex.Leaves() .. "]" ..
|
||||
"box[4,0;3.75,4.5;#999999]" ..
|
||||
"box[4,0;3.75,4.9;#999999]" ..
|
||||
"label[4.25,0.1;" .. fgettext("Texturing:") .. "]" ..
|
||||
"dropdown[4.25,0.55;3.5;dd_filters;" .. dd_options.filters[1] .. ";"
|
||||
.. getSettingIndex.Filter() .. "]" ..
|
||||
@@ -169,9 +169,6 @@ local function formspec(tabview, name, tabdata)
|
||||
"label[4.25,2.15;" .. fgettext("Antialiasing:") .. "]" ..
|
||||
"dropdown[4.25,2.6;3.5;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";"
|
||||
.. getSettingIndex.Antialiasing() .. "]" ..
|
||||
"label[4.25,3.45;" .. fgettext("Screen:") .. "]" ..
|
||||
"checkbox[4.25,3.6;cb_autosave_screensize;" .. fgettext("Autosave Screen Size") .. ";"
|
||||
.. dump(core.settings:get_bool("autosave_screensize")) .. "]" ..
|
||||
"box[8,0;3.75,4.5;#999999]"
|
||||
|
||||
local video_driver = core.settings:get("video_driver")
|
||||
@@ -203,10 +200,15 @@ local function formspec(tabview, name, tabdata)
|
||||
|
||||
if core.settings:get("touchscreen_threshold") ~= nil then
|
||||
tab_string = tab_string ..
|
||||
"label[4.3,4.2;" .. fgettext("Touchthreshold: (px)") .. "]" ..
|
||||
"dropdown[4.25,4.65;3.5;dd_touchthreshold;0,10,20,30,40,50;" ..
|
||||
"label[4.25,3.5;" .. fgettext("Touch threshold (px):") .. "]" ..
|
||||
"dropdown[4.25,3.95;3.5;dd_touchthreshold;0,10,20,30,40,50;" ..
|
||||
((tonumber(core.settings:get("touchscreen_threshold")) / 10) + 1) ..
|
||||
"]box[4.0,4.5;3.75,1.0;#999999]"
|
||||
"]"
|
||||
else
|
||||
tab_string = tab_string ..
|
||||
"label[4.25,3.65;" .. fgettext("Screen:") .. "]" ..
|
||||
"checkbox[4.25,3.9;cb_autosave_screensize;" .. fgettext("Autosave Screen Size") .. ";"
|
||||
.. dump(core.settings:get_bool("autosave_screensize")) .. "]"
|
||||
end
|
||||
|
||||
if shaders_enabled then
|
||||
@@ -219,9 +221,18 @@ local function formspec(tabview, name, tabdata)
|
||||
.. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" ..
|
||||
"checkbox[8.25,2;cb_waving_plants;" .. fgettext("Waving Plants") .. ";"
|
||||
.. dump(core.settings:get_bool("enable_waving_plants")) .. "]"
|
||||
--"label[8.25,3.0;" .. fgettext("Dynamic shadows: ") .. "]" ..
|
||||
--"dropdown[8.25,3.5;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";"
|
||||
-- .. getSettingIndex.ShadowMapping() .. "]"
|
||||
|
||||
if video_driver == "opengl" then
|
||||
tab_string = tab_string ..
|
||||
"label[8.25,2.8;" .. fgettext("Dynamic shadows:") .. "]" ..
|
||||
"label[8.25,3.2;" .. fgettext("(game support required)") .. "]" ..
|
||||
"dropdown[8.25,3.7;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";"
|
||||
.. getSettingIndex.ShadowMapping() .. "]"
|
||||
else
|
||||
tab_string = tab_string ..
|
||||
"label[8.38,2.7;" .. core.colorize("#888888",
|
||||
fgettext("Dynamic shadows")) .. "]"
|
||||
end
|
||||
else
|
||||
tab_string = tab_string ..
|
||||
"label[8.38,0.7;" .. core.colorize("#888888",
|
||||
@@ -231,9 +242,9 @@ local function formspec(tabview, name, tabdata)
|
||||
"label[8.38,1.7;" .. core.colorize("#888888",
|
||||
fgettext("Waving Leaves")) .. "]" ..
|
||||
"label[8.38,2.2;" .. core.colorize("#888888",
|
||||
fgettext("Waving Plants")) .. "]"
|
||||
--"label[8.38,2.7;" .. core.colorize("#888888",
|
||||
-- fgettext("Dynamic shadows")) .. "]"
|
||||
fgettext("Waving Plants")) .. "]"..
|
||||
"label[8.38,2.7;" .. core.colorize("#888888",
|
||||
fgettext("Dynamic shadows")) .. "]"
|
||||
end
|
||||
|
||||
return tab_string
|
||||
@@ -364,11 +375,11 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
|
||||
core.settings:set("enable_dynamic_shadows", "false")
|
||||
else
|
||||
local shadow_presets = {
|
||||
[2] = { 80, 512, "true", 0, "false" },
|
||||
[3] = { 120, 1024, "true", 1, "false" },
|
||||
[4] = { 350, 2048, "true", 1, "false" },
|
||||
[5] = { 350, 2048, "true", 2, "true" },
|
||||
[6] = { 450, 4096, "true", 2, "true" },
|
||||
[2] = { 62, 512, "true", 0, "false" },
|
||||
[3] = { 93, 1024, "true", 0, "false" },
|
||||
[4] = { 140, 2048, "true", 1, "false" },
|
||||
[5] = { 210, 4096, "true", 2, "true" },
|
||||
[6] = { 300, 8192, "true", 2, "true" },
|
||||
}
|
||||
local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])]
|
||||
if s then
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
_G.core = {}
|
||||
_G.vector = {metatable = {}}
|
||||
_G.unpack = table.unpack
|
||||
_G.serverlistmgr = {}
|
||||
|
||||
|
||||
@@ -16,10 +16,16 @@ uniform float animationTimer;
|
||||
uniform float f_textureresolution;
|
||||
uniform mat4 m_ShadowViewProj;
|
||||
uniform float f_shadowfar;
|
||||
varying float normalOffsetScale;
|
||||
uniform float f_shadow_strength;
|
||||
uniform vec4 CameraPos;
|
||||
uniform float xyPerspectiveBias0;
|
||||
uniform float xyPerspectiveBias1;
|
||||
|
||||
varying float adj_shadow_strength;
|
||||
varying float cosLight;
|
||||
varying float f_normal_length;
|
||||
varying vec3 shadow_position;
|
||||
varying float perspective_factor;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -43,23 +49,7 @@ varying float nightRatio;
|
||||
const float fogStart = FOG_START;
|
||||
const float fogShadingParameter = 1.0 / ( 1.0 - fogStart);
|
||||
|
||||
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
const float bias0 = 0.9;
|
||||
const float zPersFactor = 0.5;
|
||||
const float bias1 = 1.0 - bias0 + 1e-6;
|
||||
|
||||
vec4 getPerspectiveFactor(in vec4 shadowPosition)
|
||||
{
|
||||
|
||||
float pDistance = length(shadowPosition.xy);
|
||||
float pFactor = pDistance * bias0 + bias1;
|
||||
|
||||
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
|
||||
|
||||
return shadowPosition;
|
||||
}
|
||||
|
||||
// assuming near is always 1.0
|
||||
float getLinearDepth()
|
||||
@@ -69,16 +59,7 @@ float getLinearDepth()
|
||||
|
||||
vec3 getLightSpacePosition()
|
||||
{
|
||||
vec4 pLightSpace;
|
||||
// some drawtypes have zero normals, so we need to handle it :(
|
||||
#if DRAW_TYPE == NDT_PLANTLIKE
|
||||
pLightSpace = m_ShadowViewProj * vec4(worldPosition, 1.0);
|
||||
#else
|
||||
float offsetScale = (0.0057 * getLinearDepth() + normalOffsetScale);
|
||||
pLightSpace = m_ShadowViewProj * vec4(worldPosition + offsetScale * normalize(vNormal), 1.0);
|
||||
#endif
|
||||
pLightSpace = getPerspectiveFactor(pLightSpace);
|
||||
return pLightSpace.xyz * 0.5 + 0.5;
|
||||
return shadow_position * 0.5 + 0.5;
|
||||
}
|
||||
// custom smoothstep implementation because it's not defined in glsl1.2
|
||||
// https://docs.gl/sl4/smoothstep
|
||||
@@ -136,23 +117,16 @@ float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
|
||||
|
||||
|
||||
#if SHADOW_FILTER == 2
|
||||
#define PCFBOUND 3.5
|
||||
#define PCFSAMPLES 64.0
|
||||
#define PCFBOUND 2.0 // 5x5
|
||||
#define PCFSAMPLES 25
|
||||
#elif SHADOW_FILTER == 1
|
||||
#define PCFBOUND 1.5
|
||||
#if defined(POISSON_FILTER)
|
||||
#define PCFSAMPLES 32.0
|
||||
#else
|
||||
#define PCFSAMPLES 16.0
|
||||
#endif
|
||||
#define PCFBOUND 1.0 // 3x3
|
||||
#define PCFSAMPLES 9
|
||||
#else
|
||||
#define PCFBOUND 0.0
|
||||
#if defined(POISSON_FILTER)
|
||||
#define PCFSAMPLES 4.0
|
||||
#else
|
||||
#define PCFSAMPLES 1.0
|
||||
#endif
|
||||
#define PCFSAMPLES 1
|
||||
#endif
|
||||
|
||||
#ifdef COLORED_SHADOWS
|
||||
float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
@@ -169,57 +143,31 @@ float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDis
|
||||
}
|
||||
#endif
|
||||
|
||||
float getBaseLength(vec2 smTexCoord)
|
||||
{
|
||||
float l = length(2.0 * smTexCoord.xy - 1.0); // length in texture coords
|
||||
return bias1 / (1.0 / l - bias0); // return to undistorted coords
|
||||
}
|
||||
#define BASEFILTERRADIUS 1.0
|
||||
|
||||
float getDeltaPerspectiveFactor(float l)
|
||||
float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
return 0.1 / (bias0 * l + bias1); // original distortion factor, divided by 10
|
||||
}
|
||||
|
||||
float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier)
|
||||
{
|
||||
float baseLength = getBaseLength(smTexCoord);
|
||||
float perspectiveFactor;
|
||||
|
||||
if (PCFBOUND == 0.0) return 0.0;
|
||||
// Return fast if sharp shadows are requested
|
||||
if (SOFTSHADOWRADIUS <= 1.0) {
|
||||
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
|
||||
return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS);
|
||||
}
|
||||
if (PCFBOUND == 0.0 || SOFTSHADOWRADIUS <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
vec2 clampedpos;
|
||||
float texture_size = 1.0 / (2048 /*f_textureresolution*/ * 0.5);
|
||||
float y, x;
|
||||
float depth = 0.0;
|
||||
float pointDepth;
|
||||
float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier;
|
||||
float depth = getHardShadowDepth(shadowsampler, smTexCoord.xy, realDistance);
|
||||
// A factor from 0 to 1 to reduce blurring of short shadows
|
||||
float sharpness_factor = 1.0;
|
||||
// conversion factor from shadow depth to blur radius
|
||||
float depth_to_blur = f_shadowfar / SOFTSHADOWRADIUS / xyPerspectiveBias0;
|
||||
if (depth > 0.0 && f_normal_length > 0.0)
|
||||
// 5 is empirical factor that controls how fast shadow loses sharpness
|
||||
sharpness_factor = clamp(5 * depth * depth_to_blur, 0.0, 1.0);
|
||||
depth = 0.0;
|
||||
|
||||
float bound = clamp(PCFBOUND * (1 - baseLength), 0.0, PCFBOUND);
|
||||
int n = 0;
|
||||
float world_to_texture = xyPerspectiveBias1 / perspective_factor / perspective_factor
|
||||
* f_textureresolution / 2.0 / f_shadowfar;
|
||||
float world_radius = 0.2; // shadow blur radius in world float coordinates, e.g. 0.2 = 0.02 of one node
|
||||
|
||||
for (y = -bound; y <= bound; y += 1.0)
|
||||
for (x = -bound; x <= bound; x += 1.0) {
|
||||
clampedpos = vec2(x,y);
|
||||
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * maxRadius);
|
||||
clampedpos = clampedpos * texture_size * perspectiveFactor * maxRadius * perspectiveFactor + smTexCoord.xy;
|
||||
|
||||
pointDepth = getHardShadowDepth(shadowsampler, clampedpos.xy, realDistance);
|
||||
if (pointDepth > -0.01) {
|
||||
depth += pointDepth;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
depth = depth / n;
|
||||
depth = pow(clamp(depth, 0.0, 1000.0), 1.6) / 0.001;
|
||||
|
||||
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
|
||||
return max(length(smTexCoord.xy) * 2 * 2048 / f_textureresolution / pow(perspectiveFactor, 3), depth * maxRadius);
|
||||
return max(BASEFILTERRADIUS * f_textureresolution / 4096.0, sharpness_factor * world_radius * world_to_texture * SOFTSHADOWRADIUS);
|
||||
}
|
||||
|
||||
#ifdef POISSON_FILTER
|
||||
@@ -294,26 +242,23 @@ const vec2[64] poissonDisk = vec2[64](
|
||||
|
||||
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
vec2 clampedpos;
|
||||
vec4 visibility = vec4(0.0);
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
float baseLength = getBaseLength(smTexCoord);
|
||||
float perspectiveFactor;
|
||||
vec2 clampedpos;
|
||||
vec4 visibility = vec4(0.0);
|
||||
float scale_factor = radius / f_textureresolution;
|
||||
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES));
|
||||
int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
|
||||
samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
|
||||
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
|
||||
int end_offset = int(samples) + init_offset;
|
||||
|
||||
for (int x = init_offset; x < end_offset; x++) {
|
||||
clampedpos = poissonDisk[x];
|
||||
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
|
||||
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
|
||||
clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
|
||||
}
|
||||
|
||||
@@ -324,26 +269,23 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
|
||||
|
||||
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
vec2 clampedpos;
|
||||
float visibility = 0.0;
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
float baseLength = getBaseLength(smTexCoord);
|
||||
float perspectiveFactor;
|
||||
vec2 clampedpos;
|
||||
float visibility = 0.0;
|
||||
float scale_factor = radius / f_textureresolution;
|
||||
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES));
|
||||
int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
|
||||
samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
|
||||
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
|
||||
int end_offset = int(samples) + init_offset;
|
||||
|
||||
for (int x = init_offset; x < end_offset; x++) {
|
||||
clampedpos = poissonDisk[x];
|
||||
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
|
||||
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
|
||||
clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
|
||||
}
|
||||
|
||||
@@ -359,65 +301,57 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
|
||||
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
vec2 clampedpos;
|
||||
vec4 visibility = vec4(0.0);
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
float baseLength = getBaseLength(smTexCoord);
|
||||
float perspectiveFactor;
|
||||
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
float y, x;
|
||||
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND);
|
||||
int n = 0;
|
||||
vec2 clampedpos;
|
||||
vec4 visibility = vec4(0.0);
|
||||
float x, y;
|
||||
float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
|
||||
bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
|
||||
float scale_factor = radius / bound / f_textureresolution;
|
||||
float n = 0.0;
|
||||
|
||||
// basic PCF filter
|
||||
for (y = -bound; y <= bound; y += 1.0)
|
||||
for (x = -bound; x <= bound; x += 1.0) {
|
||||
clampedpos = vec2(x,y); // screen offset
|
||||
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
|
||||
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
|
||||
clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
|
||||
n += 1;
|
||||
n += 1.0;
|
||||
}
|
||||
|
||||
return visibility / n;
|
||||
return visibility / max(n, 1.0);
|
||||
}
|
||||
|
||||
#else
|
||||
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
vec2 clampedpos;
|
||||
float visibility = 0.0;
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
float baseLength = getBaseLength(smTexCoord);
|
||||
float perspectiveFactor;
|
||||
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
float y, x;
|
||||
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND);
|
||||
int n = 0;
|
||||
vec2 clampedpos;
|
||||
float visibility = 0.0;
|
||||
float x, y;
|
||||
float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
|
||||
bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
|
||||
float scale_factor = radius / bound / f_textureresolution;
|
||||
float n = 0.0;
|
||||
|
||||
// basic PCF filter
|
||||
for (y = -bound; y <= bound; y += 1.0)
|
||||
for (x = -bound; x <= bound; x += 1.0) {
|
||||
clampedpos = vec2(x,y); // screen offset
|
||||
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
|
||||
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
|
||||
clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
|
||||
n += 1;
|
||||
n += 1.0;
|
||||
}
|
||||
|
||||
return visibility / n;
|
||||
return visibility / max(n, 1.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -481,53 +415,59 @@ void main(void)
|
||||
vec4 col = vec4(color.rgb * varColor.rgb, 1.0);
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
float shadow_int = 0.0;
|
||||
vec3 shadow_color = vec3(0.0, 0.0, 0.0);
|
||||
vec3 posLightSpace = getLightSpacePosition();
|
||||
if (f_shadow_strength > 0.0) {
|
||||
float shadow_int = 0.0;
|
||||
vec3 shadow_color = vec3(0.0, 0.0, 0.0);
|
||||
vec3 posLightSpace = getLightSpacePosition();
|
||||
|
||||
float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0));
|
||||
float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z ),0.0);
|
||||
float distance_rate = (1.0 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 10.0));
|
||||
if (max(abs(posLightSpace.x - 0.5), abs(posLightSpace.y - 0.5)) > 0.5)
|
||||
distance_rate = 0.0;
|
||||
float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z),0.0);
|
||||
|
||||
if (distance_rate > 1e-7) {
|
||||
|
||||
if (distance_rate > 1e-7) {
|
||||
|
||||
#ifdef COLORED_SHADOWS
|
||||
vec4 visibility;
|
||||
if (cosLight > 0.0)
|
||||
visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
else
|
||||
visibility = vec4(1.0, 0.0, 0.0, 0.0);
|
||||
shadow_int = visibility.r;
|
||||
shadow_color = visibility.gba;
|
||||
vec4 visibility;
|
||||
if (cosLight > 0.0 || f_normal_length < 1e-3)
|
||||
visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
else
|
||||
visibility = vec4(1.0, 0.0, 0.0, 0.0);
|
||||
shadow_int = visibility.r;
|
||||
shadow_color = visibility.gba;
|
||||
#else
|
||||
if (cosLight > 0.0)
|
||||
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
else
|
||||
shadow_int = 1.0;
|
||||
if (cosLight > 0.0 || f_normal_length < 1e-3)
|
||||
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
else
|
||||
shadow_int = 1.0;
|
||||
#endif
|
||||
shadow_int *= distance_rate;
|
||||
shadow_int = clamp(shadow_int, 0.0, 1.0);
|
||||
shadow_int *= distance_rate;
|
||||
shadow_int = clamp(shadow_int, 0.0, 1.0);
|
||||
|
||||
}
|
||||
|
||||
// turns out that nightRatio falls off much faster than
|
||||
// actual brightness of artificial light in relation to natual light.
|
||||
// Power ratio was measured on torches in MTG (brightness = 14).
|
||||
float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6);
|
||||
|
||||
// Apply self-shadowing when light falls at a narrow angle to the surface
|
||||
// Cosine of the cut-off angle.
|
||||
const float self_shadow_cutoff_cosine = 0.035;
|
||||
if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) {
|
||||
shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
|
||||
shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
|
||||
}
|
||||
|
||||
shadow_int *= f_adj_shadow_strength;
|
||||
|
||||
// calculate fragment color from components:
|
||||
col.rgb =
|
||||
adjusted_night_ratio * col.rgb + // artificial light
|
||||
(1.0 - adjusted_night_ratio) * ( // natural light
|
||||
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
|
||||
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
|
||||
}
|
||||
|
||||
// turns out that nightRatio falls off much faster than
|
||||
// actual brightness of artificial light in relation to natual light.
|
||||
// Power ratio was measured on torches in MTG (brightness = 14).
|
||||
float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6);
|
||||
|
||||
if (f_normal_length != 0 && cosLight < 0.035) {
|
||||
shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, 0.035)/0.035);
|
||||
}
|
||||
|
||||
shadow_int *= f_adj_shadow_strength;
|
||||
|
||||
// calculate fragment color from components:
|
||||
col.rgb =
|
||||
adjusted_night_ratio * col.rgb + // artificial light
|
||||
(1.0 - adjusted_night_ratio) * ( // natural light
|
||||
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
|
||||
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
|
||||
// col.r = 0.5 * clamp(getPenumbraRadius(ShadowMapSampler, posLightSpace.xy, posLightSpace.z, 1.0) / SOFTSHADOWRADIUS, 0.0, 1.0) + 0.5 * col.r;
|
||||
// col.r = adjusted_night_ratio; // debug night ratio adjustment
|
||||
#endif
|
||||
|
||||
#if ENABLE_TONE_MAPPING
|
||||
@@ -547,6 +487,6 @@ void main(void)
|
||||
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
|
||||
col = mix(skyBgColor, col, clarity);
|
||||
col = vec4(col.rgb, base.a);
|
||||
|
||||
|
||||
gl_FragColor = col;
|
||||
}
|
||||
|
||||
@@ -32,10 +32,14 @@ centroid varying vec2 varTexCoord;
|
||||
uniform float f_shadowfar;
|
||||
uniform float f_shadow_strength;
|
||||
uniform float f_timeofday;
|
||||
uniform vec4 CameraPos;
|
||||
|
||||
varying float cosLight;
|
||||
varying float normalOffsetScale;
|
||||
varying float adj_shadow_strength;
|
||||
varying float f_normal_length;
|
||||
varying vec3 shadow_position;
|
||||
varying float perspective_factor;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -45,8 +49,38 @@ varying float nightRatio;
|
||||
const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
|
||||
const float e = 2.718281828459;
|
||||
const float BS = 10.0;
|
||||
uniform float xyPerspectiveBias0;
|
||||
uniform float xyPerspectiveBias1;
|
||||
uniform float zPerspectiveBias;
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
|
||||
vec4 getRelativePosition(in vec4 position)
|
||||
{
|
||||
vec2 l = position.xy - CameraPos.xy;
|
||||
vec2 s = l / abs(l);
|
||||
s = (1.0 - s * CameraPos.xy);
|
||||
l /= s;
|
||||
return vec4(l, s);
|
||||
}
|
||||
|
||||
float getPerspectiveFactor(in vec4 relativePosition)
|
||||
{
|
||||
float pDistance = length(relativePosition.xy);
|
||||
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
|
||||
return pFactor;
|
||||
}
|
||||
|
||||
vec4 applyPerspectiveDistortion(in vec4 position)
|
||||
{
|
||||
vec4 l = getRelativePosition(position);
|
||||
float pFactor = getPerspectiveFactor(l);
|
||||
l.xy /= pFactor;
|
||||
position.xy = l.xy * l.zw + CameraPos.xy;
|
||||
position.z *= zPerspectiveBias;
|
||||
return position;
|
||||
}
|
||||
|
||||
// custom smoothstep implementation because it's not defined in glsl1.2
|
||||
// https://docs.gl/sl4/smoothstep
|
||||
float mtsmoothstep(in float edge0, in float edge1, in float x)
|
||||
@@ -193,24 +227,46 @@ void main(void)
|
||||
varColor = clamp(color, 0.0, 1.0);
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
vec3 nNormal = normalize(vNormal);
|
||||
cosLight = dot(nNormal, -v_LightDirection);
|
||||
float texelSize = 767.0 / f_textureresolution;
|
||||
float slopeScale = clamp(1.0 - abs(cosLight), 0.0, 1.0);
|
||||
normalOffsetScale = texelSize * slopeScale;
|
||||
|
||||
if (f_timeofday < 0.2) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday));
|
||||
} else if (f_timeofday >= 0.8) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
mtsmoothstep(0.8, 0.83, f_timeofday);
|
||||
} else {
|
||||
adj_shadow_strength = f_shadow_strength *
|
||||
mtsmoothstep(0.20, 0.25, f_timeofday) *
|
||||
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday));
|
||||
}
|
||||
f_normal_length = length(vNormal);
|
||||
#endif
|
||||
if (f_shadow_strength > 0.0) {
|
||||
vec3 nNormal;
|
||||
f_normal_length = length(vNormal);
|
||||
|
||||
/* normalOffsetScale is in world coordinates (1/10th of a meter)
|
||||
z_bias is in light space coordinates */
|
||||
float normalOffsetScale, z_bias;
|
||||
float pFactor = getPerspectiveFactor(getRelativePosition(m_ShadowViewProj * mWorld * inVertexPosition));
|
||||
if (f_normal_length > 0.0) {
|
||||
nNormal = normalize(vNormal);
|
||||
cosLight = dot(nNormal, -v_LightDirection);
|
||||
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
|
||||
normalOffsetScale = 2.0 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) /
|
||||
xyPerspectiveBias1 / f_textureresolution;
|
||||
z_bias = 1.0 * sinLight / cosLight;
|
||||
}
|
||||
else {
|
||||
nNormal = vec3(0.0);
|
||||
cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0);
|
||||
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
|
||||
normalOffsetScale = 0.0;
|
||||
z_bias = 3.6e3 * sinLight / cosLight;
|
||||
}
|
||||
z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar;
|
||||
|
||||
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
|
||||
shadow_position.z -= z_bias;
|
||||
perspective_factor = pFactor;
|
||||
|
||||
if (f_timeofday < 0.2) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday));
|
||||
} else if (f_timeofday >= 0.8) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
mtsmoothstep(0.8, 0.83, f_timeofday);
|
||||
} else {
|
||||
adj_shadow_strength = f_shadow_strength *
|
||||
mtsmoothstep(0.20, 0.25, f_timeofday) *
|
||||
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,28 +1,13 @@
|
||||
uniform sampler2D baseTexture;
|
||||
|
||||
uniform vec4 emissiveColor;
|
||||
uniform vec3 dayLight;
|
||||
uniform vec4 skyBgColor;
|
||||
uniform float fogDistance;
|
||||
uniform vec3 eyePosition;
|
||||
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vPosition;
|
||||
varying vec3 worldPosition;
|
||||
varying lowp vec4 varColor;
|
||||
#ifdef GL_ES
|
||||
varying mediump vec2 varTexCoord;
|
||||
#else
|
||||
centroid varying vec2 varTexCoord;
|
||||
#endif
|
||||
|
||||
varying vec3 eyeVec;
|
||||
varying float vIDiff;
|
||||
|
||||
const float e = 2.718281828459;
|
||||
const float BS = 10.0;
|
||||
const float fogStart = FOG_START;
|
||||
const float fogShadingParameter = 1.0 / (1.0 - fogStart);
|
||||
|
||||
// The cameraOffset is the current center of the visible world.
|
||||
uniform vec3 cameraOffset;
|
||||
uniform float animationTimer;
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
// shadow texture
|
||||
uniform sampler2D ShadowMapSampler;
|
||||
@@ -31,57 +16,42 @@ const float fogShadingParameter = 1.0 / (1.0 - fogStart);
|
||||
uniform float f_textureresolution;
|
||||
uniform mat4 m_ShadowViewProj;
|
||||
uniform float f_shadowfar;
|
||||
uniform float f_timeofday;
|
||||
varying float normalOffsetScale;
|
||||
uniform float f_shadow_strength;
|
||||
uniform vec4 CameraPos;
|
||||
uniform float xyPerspectiveBias0;
|
||||
uniform float xyPerspectiveBias1;
|
||||
|
||||
varying float adj_shadow_strength;
|
||||
varying float cosLight;
|
||||
varying float f_normal_length;
|
||||
varying vec3 shadow_position;
|
||||
varying float perspective_factor;
|
||||
#endif
|
||||
|
||||
#if ENABLE_TONE_MAPPING
|
||||
/* Hable's UC2 Tone mapping parameters
|
||||
A = 0.22;
|
||||
B = 0.30;
|
||||
C = 0.10;
|
||||
D = 0.20;
|
||||
E = 0.01;
|
||||
F = 0.30;
|
||||
W = 11.2;
|
||||
equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F
|
||||
*/
|
||||
|
||||
vec3 uncharted2Tonemap(vec3 x)
|
||||
{
|
||||
return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333;
|
||||
}
|
||||
|
||||
vec4 applyToneMapping(vec4 color)
|
||||
{
|
||||
color = vec4(pow(color.rgb, vec3(2.2)), color.a);
|
||||
const float gamma = 1.6;
|
||||
const float exposureBias = 5.5;
|
||||
color.rgb = uncharted2Tonemap(exposureBias * color.rgb);
|
||||
// Precalculated white_scale from
|
||||
//vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W));
|
||||
vec3 whiteScale = vec3(1.036015346);
|
||||
color.rgb *= whiteScale;
|
||||
return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a);
|
||||
}
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vPosition;
|
||||
// World position in the visible world (i.e. relative to the cameraOffset.)
|
||||
// This can be used for many shader effects without loss of precision.
|
||||
// If the absolute position is required it can be calculated with
|
||||
// cameraOffset + worldPosition (for large coordinates the limits of float
|
||||
// precision must be considered).
|
||||
varying vec3 worldPosition;
|
||||
varying lowp vec4 varColor;
|
||||
#ifdef GL_ES
|
||||
varying mediump vec2 varTexCoord;
|
||||
#else
|
||||
centroid varying vec2 varTexCoord;
|
||||
#endif
|
||||
varying vec3 eyeVec;
|
||||
varying float nightRatio;
|
||||
|
||||
varying float vIDiff;
|
||||
|
||||
const float fogStart = FOG_START;
|
||||
const float fogShadingParameter = 1.0 / (1.0 - fogStart);
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
const float bias0 = 0.9;
|
||||
const float zPersFactor = 0.5;
|
||||
const float bias1 = 1.0 - bias0;
|
||||
|
||||
vec4 getPerspectiveFactor(in vec4 shadowPosition)
|
||||
{
|
||||
float pDistance = length(shadowPosition.xy);
|
||||
float pFactor = pDistance * bias0 + bias1;
|
||||
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
|
||||
|
||||
return shadowPosition;
|
||||
}
|
||||
|
||||
// assuming near is always 1.0
|
||||
float getLinearDepth()
|
||||
@@ -91,11 +61,14 @@ float getLinearDepth()
|
||||
|
||||
vec3 getLightSpacePosition()
|
||||
{
|
||||
vec4 pLightSpace;
|
||||
float normalBias = 0.0005 * getLinearDepth() * cosLight + normalOffsetScale;
|
||||
pLightSpace = m_ShadowViewProj * vec4(worldPosition + normalBias * normalize(vNormal), 1.0);
|
||||
pLightSpace = getPerspectiveFactor(pLightSpace);
|
||||
return pLightSpace.xyz * 0.5 + 0.5;
|
||||
return shadow_position * 0.5 + 0.5;
|
||||
}
|
||||
// custom smoothstep implementation because it's not defined in glsl1.2
|
||||
// https://docs.gl/sl4/smoothstep
|
||||
float mtsmoothstep(in float edge0, in float edge1, in float x)
|
||||
{
|
||||
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
|
||||
#ifdef COLORED_SHADOWS
|
||||
@@ -124,10 +97,10 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
|
||||
{
|
||||
vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba;
|
||||
|
||||
float visibility = step(0.0, (realDistance-2e-5) - texDepth.r);
|
||||
float visibility = step(0.0, realDistance - texDepth.r);
|
||||
vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g));
|
||||
if (visibility < 0.1) {
|
||||
visibility = step(0.0, (realDistance-2e-5) - texDepth.r);
|
||||
visibility = step(0.0, realDistance - texDepth.b);
|
||||
result = vec4(visibility, unpackColor(texDepth.a));
|
||||
}
|
||||
return result;
|
||||
@@ -138,32 +111,67 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
|
||||
float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
float texDepth = texture2D(shadowsampler, smTexCoord.xy).r;
|
||||
float visibility = step(0.0, (realDistance-2e-5) - texDepth);
|
||||
|
||||
float visibility = step(0.0, realDistance - texDepth);
|
||||
return visibility;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if SHADOW_FILTER == 2
|
||||
#define PCFBOUND 3.5
|
||||
#define PCFSAMPLES 64.0
|
||||
#define PCFBOUND 2.0 // 5x5
|
||||
#define PCFSAMPLES 25
|
||||
#elif SHADOW_FILTER == 1
|
||||
#define PCFBOUND 1.5
|
||||
#if defined(POISSON_FILTER)
|
||||
#define PCFSAMPLES 32.0
|
||||
#else
|
||||
#define PCFSAMPLES 16.0
|
||||
#endif
|
||||
#define PCFBOUND 1.0 // 3x3
|
||||
#define PCFSAMPLES 9
|
||||
#else
|
||||
#define PCFBOUND 0.0
|
||||
#if defined(POISSON_FILTER)
|
||||
#define PCFSAMPLES 4.0
|
||||
#else
|
||||
#define PCFSAMPLES 1.0
|
||||
#endif
|
||||
#define PCFSAMPLES 1
|
||||
#endif
|
||||
|
||||
#ifdef COLORED_SHADOWS
|
||||
float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy);
|
||||
float depth = max(realDistance - texDepth.r, realDistance - texDepth.b);
|
||||
return depth;
|
||||
}
|
||||
#else
|
||||
float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
float texDepth = texture2D(shadowsampler, smTexCoord.xy).r;
|
||||
float depth = realDistance - texDepth;
|
||||
return depth;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define BASEFILTERRADIUS 1.0
|
||||
|
||||
float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
// Return fast if sharp shadows are requested
|
||||
if (PCFBOUND == 0.0 || SOFTSHADOWRADIUS <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
vec2 clampedpos;
|
||||
float y, x;
|
||||
float depth = getHardShadowDepth(shadowsampler, smTexCoord.xy, realDistance);
|
||||
// A factor from 0 to 1 to reduce blurring of short shadows
|
||||
float sharpness_factor = 1.0;
|
||||
// conversion factor from shadow depth to blur radius
|
||||
float depth_to_blur = f_shadowfar / SOFTSHADOWRADIUS / xyPerspectiveBias0;
|
||||
if (depth > 0.0 && f_normal_length > 0.0)
|
||||
// 5 is empirical factor that controls how fast shadow loses sharpness
|
||||
sharpness_factor = clamp(5 * depth * depth_to_blur, 0.0, 1.0);
|
||||
depth = 0.0;
|
||||
|
||||
float world_to_texture = xyPerspectiveBias1 / perspective_factor / perspective_factor
|
||||
* f_textureresolution / 2.0 / f_shadowfar;
|
||||
float world_radius = 0.2; // shadow blur radius in world float coordinates, e.g. 0.2 = 0.02 of one node
|
||||
|
||||
return max(BASEFILTERRADIUS * f_textureresolution / 4096.0, sharpness_factor * world_radius * world_to_texture * SOFTSHADOWRADIUS);
|
||||
}
|
||||
|
||||
#ifdef POISSON_FILTER
|
||||
const vec2[64] poissonDisk = vec2[64](
|
||||
vec2(0.170019, -0.040254),
|
||||
@@ -236,38 +244,54 @@ const vec2[64] poissonDisk = vec2[64](
|
||||
|
||||
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
vec2 clampedpos;
|
||||
vec4 visibility = vec4(0.0);
|
||||
float scale_factor = radius / f_textureresolution;
|
||||
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES)));
|
||||
int end_offset = int(PCFSAMPLES) + init_offset;
|
||||
int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
|
||||
samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
|
||||
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
|
||||
int end_offset = int(samples) + init_offset;
|
||||
|
||||
for (int x = init_offset; x < end_offset; x++) {
|
||||
clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy;
|
||||
clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
|
||||
}
|
||||
|
||||
return visibility / PCFSAMPLES;
|
||||
return visibility / samples;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
vec2 clampedpos;
|
||||
float visibility = 0.0;
|
||||
float scale_factor = radius / f_textureresolution;
|
||||
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES)));
|
||||
int end_offset = int(PCFSAMPLES) + init_offset;
|
||||
int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
|
||||
samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
|
||||
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
|
||||
int end_offset = int(samples) + init_offset;
|
||||
|
||||
for (int x = init_offset; x < end_offset; x++) {
|
||||
clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy;
|
||||
clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
|
||||
}
|
||||
|
||||
return visibility / PCFSAMPLES;
|
||||
return visibility / samples;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -279,42 +303,57 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
|
||||
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
vec2 clampedpos;
|
||||
vec4 visibility = vec4(0.0);
|
||||
float sradius=0.0;
|
||||
if( PCFBOUND>0)
|
||||
sradius = SOFTSHADOWRADIUS / PCFBOUND;
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
float y, x;
|
||||
// basic PCF filter
|
||||
for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0)
|
||||
for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) {
|
||||
clampedpos = vec2(x,y) * texture_size* sradius + smTexCoord.xy;
|
||||
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
return visibility / PCFSAMPLES;
|
||||
vec2 clampedpos;
|
||||
vec4 visibility = vec4(0.0);
|
||||
float x, y;
|
||||
float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
|
||||
bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
|
||||
float scale_factor = radius / bound / f_textureresolution;
|
||||
float n = 0.0;
|
||||
|
||||
// basic PCF filter
|
||||
for (y = -bound; y <= bound; y += 1.0)
|
||||
for (x = -bound; x <= bound; x += 1.0) {
|
||||
clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
|
||||
n += 1.0;
|
||||
}
|
||||
|
||||
return visibility / max(n, 1.0);
|
||||
}
|
||||
|
||||
#else
|
||||
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
{
|
||||
vec2 clampedpos;
|
||||
float visibility = 0.0;
|
||||
float sradius=0.0;
|
||||
if( PCFBOUND>0)
|
||||
sradius = SOFTSHADOWRADIUS / PCFBOUND;
|
||||
|
||||
float texture_size = 1.0 / (f_textureresolution * 0.5);
|
||||
float y, x;
|
||||
// basic PCF filter
|
||||
for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0)
|
||||
for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) {
|
||||
clampedpos = vec2(x,y) * texture_size * sradius + smTexCoord.xy;
|
||||
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
|
||||
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
|
||||
if (radius < 0.1) {
|
||||
// we are in the middle of even brightness, no need for filtering
|
||||
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
|
||||
}
|
||||
|
||||
return visibility / PCFSAMPLES;
|
||||
vec2 clampedpos;
|
||||
float visibility = 0.0;
|
||||
float x, y;
|
||||
float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
|
||||
bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
|
||||
float scale_factor = radius / bound / f_textureresolution;
|
||||
float n = 0.0;
|
||||
|
||||
// basic PCF filter
|
||||
for (y = -bound; y <= bound; y += 1.0)
|
||||
for (x = -bound; x <= bound; x += 1.0) {
|
||||
clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
|
||||
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
|
||||
n += 1.0;
|
||||
}
|
||||
|
||||
return visibility / max(n, 1.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -322,12 +361,46 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ENABLE_TONE_MAPPING
|
||||
|
||||
/* Hable's UC2 Tone mapping parameters
|
||||
A = 0.22;
|
||||
B = 0.30;
|
||||
C = 0.10;
|
||||
D = 0.20;
|
||||
E = 0.01;
|
||||
F = 0.30;
|
||||
W = 11.2;
|
||||
equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F
|
||||
*/
|
||||
|
||||
vec3 uncharted2Tonemap(vec3 x)
|
||||
{
|
||||
return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333;
|
||||
}
|
||||
|
||||
vec4 applyToneMapping(vec4 color)
|
||||
{
|
||||
color = vec4(pow(color.rgb, vec3(2.2)), color.a);
|
||||
const float gamma = 1.6;
|
||||
const float exposureBias = 5.5;
|
||||
color.rgb = uncharted2Tonemap(exposureBias * color.rgb);
|
||||
// Precalculated white_scale from
|
||||
//vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W));
|
||||
vec3 whiteScale = vec3(1.036015346);
|
||||
color.rgb *= whiteScale;
|
||||
return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec3 color;
|
||||
vec2 uv = varTexCoord.st;
|
||||
vec4 base = texture2D(baseTexture, uv).rgba;
|
||||
|
||||
vec4 base = texture2D(baseTexture, uv).rgba;
|
||||
// If alpha is zero, we can just discard the pixel. This fixes transparency
|
||||
// on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa,
|
||||
// and also on GLES 2, where GL_ALPHA_TEST is missing entirely.
|
||||
@@ -341,38 +414,65 @@ void main(void)
|
||||
#endif
|
||||
|
||||
color = base.rgb;
|
||||
vec4 col = vec4(color.rgb, base.a);
|
||||
col.rgb *= varColor.rgb;
|
||||
col.rgb *= emissiveColor.rgb * vIDiff;
|
||||
vec4 col = vec4(color.rgb * varColor.rgb, 1.0);
|
||||
col.rgb *= vIDiff;
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
float shadow_int = 0.0;
|
||||
vec3 shadow_color = vec3(0.0, 0.0, 0.0);
|
||||
vec3 posLightSpace = getLightSpacePosition();
|
||||
if (f_shadow_strength > 0.0) {
|
||||
float shadow_int = 0.0;
|
||||
vec3 shadow_color = vec3(0.0, 0.0, 0.0);
|
||||
vec3 posLightSpace = getLightSpacePosition();
|
||||
|
||||
float distance_rate = (1.0 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 10.0));
|
||||
if (max(abs(posLightSpace.x - 0.5), abs(posLightSpace.y - 0.5)) > 0.5)
|
||||
distance_rate = 0.0;
|
||||
float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z),0.0);
|
||||
|
||||
if (distance_rate > 1e-7) {
|
||||
|
||||
#ifdef COLORED_SHADOWS
|
||||
vec4 visibility;
|
||||
if (cosLight > 0.0)
|
||||
visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
else
|
||||
visibility = vec4(1.0, 0.0, 0.0, 0.0);
|
||||
shadow_int = visibility.r;
|
||||
shadow_color = visibility.gba;
|
||||
vec4 visibility;
|
||||
if (cosLight > 0.0 || f_normal_length < 1e-3)
|
||||
visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
else
|
||||
visibility = vec4(1.0, 0.0, 0.0, 0.0);
|
||||
shadow_int = visibility.r;
|
||||
shadow_color = visibility.gba;
|
||||
#else
|
||||
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
if (cosLight > 0.0 || f_normal_length < 1e-3)
|
||||
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
|
||||
else
|
||||
shadow_int = 1.0;
|
||||
#endif
|
||||
shadow_int *= distance_rate;
|
||||
shadow_int = clamp(shadow_int, 0.0, 1.0);
|
||||
|
||||
if (f_normal_length != 0 && cosLight <= 0.001) {
|
||||
shadow_int = clamp(shadow_int + 0.5 * abs(cosLight), 0.0, 1.0);
|
||||
}
|
||||
|
||||
// turns out that nightRatio falls off much faster than
|
||||
// actual brightness of artificial light in relation to natual light.
|
||||
// Power ratio was measured on torches in MTG (brightness = 14).
|
||||
float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6);
|
||||
|
||||
// Apply self-shadowing when light falls at a narrow angle to the surface
|
||||
// Cosine of the cut-off angle.
|
||||
const float self_shadow_cutoff_cosine = 0.14;
|
||||
if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) {
|
||||
shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
|
||||
shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
|
||||
}
|
||||
|
||||
shadow_int *= f_adj_shadow_strength;
|
||||
|
||||
// calculate fragment color from components:
|
||||
col.rgb =
|
||||
adjusted_night_ratio * col.rgb + // artificial light
|
||||
(1.0 - adjusted_night_ratio) * ( // natural light
|
||||
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
|
||||
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
|
||||
}
|
||||
|
||||
shadow_int = 1.0 - (shadow_int * adj_shadow_strength);
|
||||
|
||||
col.rgb = mix(shadow_color, col.rgb, shadow_int) * shadow_int;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if ENABLE_TONE_MAPPING
|
||||
col = applyToneMapping(col);
|
||||
#endif
|
||||
@@ -389,6 +489,7 @@ void main(void)
|
||||
float clarity = clamp(fogShadingParameter
|
||||
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
|
||||
col = mix(skyBgColor, col, clarity);
|
||||
col = vec4(col.rgb, base.a);
|
||||
|
||||
gl_FragColor = vec4(col.rgb, base.a);
|
||||
gl_FragColor = col;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
uniform mat4 mWorld;
|
||||
|
||||
uniform vec3 dayLight;
|
||||
uniform vec3 eyePosition;
|
||||
uniform float animationTimer;
|
||||
uniform vec4 emissiveColor;
|
||||
uniform vec3 cameraOffset;
|
||||
|
||||
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vPosition;
|
||||
@@ -21,19 +24,54 @@ centroid varying vec2 varTexCoord;
|
||||
uniform float f_shadowfar;
|
||||
uniform float f_shadow_strength;
|
||||
uniform float f_timeofday;
|
||||
uniform vec4 CameraPos;
|
||||
|
||||
varying float cosLight;
|
||||
varying float normalOffsetScale;
|
||||
varying float adj_shadow_strength;
|
||||
varying float f_normal_length;
|
||||
varying vec3 shadow_position;
|
||||
varying float perspective_factor;
|
||||
#endif
|
||||
|
||||
varying vec3 eyeVec;
|
||||
varying float nightRatio;
|
||||
// Color of the light emitted by the light sources.
|
||||
const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
|
||||
varying float vIDiff;
|
||||
|
||||
const float e = 2.718281828459;
|
||||
const float BS = 10.0;
|
||||
uniform float xyPerspectiveBias0;
|
||||
uniform float xyPerspectiveBias1;
|
||||
uniform float zPerspectiveBias;
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
|
||||
vec4 getRelativePosition(in vec4 position)
|
||||
{
|
||||
vec2 l = position.xy - CameraPos.xy;
|
||||
vec2 s = l / abs(l);
|
||||
s = (1.0 - s * CameraPos.xy);
|
||||
l /= s;
|
||||
return vec4(l, s);
|
||||
}
|
||||
|
||||
float getPerspectiveFactor(in vec4 relativePosition)
|
||||
{
|
||||
float pDistance = length(relativePosition.xy);
|
||||
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
|
||||
return pFactor;
|
||||
}
|
||||
|
||||
vec4 applyPerspectiveDistortion(in vec4 position)
|
||||
{
|
||||
vec4 l = getRelativePosition(position);
|
||||
float pFactor = getPerspectiveFactor(l);
|
||||
l.xy /= pFactor;
|
||||
position.xy = l.xy * l.zw + CameraPos.xy;
|
||||
position.z *= zPerspectiveBias;
|
||||
return position;
|
||||
}
|
||||
|
||||
// custom smoothstep implementation because it's not defined in glsl1.2
|
||||
// https://docs.gl/sl4/smoothstep
|
||||
float mtsmoothstep(in float edge0, in float edge1, in float x)
|
||||
@@ -60,7 +98,7 @@ void main(void)
|
||||
gl_Position = mWorldViewProj * inVertexPosition;
|
||||
|
||||
vPosition = gl_Position.xyz;
|
||||
vNormal = inVertexNormal;
|
||||
vNormal = (mWorld * vec4(inVertexNormal, 0.0)).xyz;
|
||||
worldPosition = (mWorld * inVertexPosition).xyz;
|
||||
eyeVec = -(mWorldView * inVertexPosition).xyz;
|
||||
|
||||
@@ -75,29 +113,69 @@ void main(void)
|
||||
#endif
|
||||
|
||||
#ifdef GL_ES
|
||||
varColor = inVertexColor.bgra;
|
||||
vec4 color = inVertexColor.bgra;
|
||||
#else
|
||||
varColor = inVertexColor;
|
||||
vec4 color = inVertexColor;
|
||||
#endif
|
||||
|
||||
color *= emissiveColor;
|
||||
|
||||
// The alpha gives the ratio of sunlight in the incoming light.
|
||||
nightRatio = 1.0 - color.a;
|
||||
color.rgb = color.rgb * (color.a * dayLight.rgb +
|
||||
nightRatio * artificialLight.rgb) * 2.0;
|
||||
color.a = 1.0;
|
||||
|
||||
// Emphase blue a bit in darker places
|
||||
// See C++ implementation in mapblock_mesh.cpp final_color_blend()
|
||||
float brightness = (color.r + color.g + color.b) / 3.0;
|
||||
color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) +
|
||||
0.07 * brightness);
|
||||
|
||||
varColor = clamp(color, 0.0, 1.0);
|
||||
|
||||
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
if (f_shadow_strength > 0.0) {
|
||||
vec3 nNormal = normalize(vNormal);
|
||||
f_normal_length = length(vNormal);
|
||||
|
||||
cosLight = max(0.0, dot(vNormal, -v_LightDirection));
|
||||
float texelSize = 0.51;
|
||||
float slopeScale = clamp(1.0 - cosLight, 0.0, 1.0);
|
||||
normalOffsetScale = texelSize * slopeScale;
|
||||
if (f_timeofday < 0.2) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday));
|
||||
} else if (f_timeofday >= 0.8) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
mtsmoothstep(0.8, 0.83, f_timeofday);
|
||||
} else {
|
||||
adj_shadow_strength = f_shadow_strength *
|
||||
mtsmoothstep(0.20, 0.25, f_timeofday) *
|
||||
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday));
|
||||
/* normalOffsetScale is in world coordinates (1/10th of a meter)
|
||||
z_bias is in light space coordinates */
|
||||
float normalOffsetScale, z_bias;
|
||||
float pFactor = getPerspectiveFactor(getRelativePosition(m_ShadowViewProj * mWorld * inVertexPosition));
|
||||
if (f_normal_length > 0.0) {
|
||||
nNormal = normalize(vNormal);
|
||||
cosLight = dot(nNormal, -v_LightDirection);
|
||||
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
|
||||
normalOffsetScale = 0.1 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) /
|
||||
xyPerspectiveBias1 / f_textureresolution;
|
||||
z_bias = 1e3 * sinLight / cosLight * (0.5 + f_textureresolution / 1024.0);
|
||||
}
|
||||
else {
|
||||
nNormal = vec3(0.0);
|
||||
cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0);
|
||||
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
|
||||
normalOffsetScale = 0.0;
|
||||
z_bias = 3.6e3 * sinLight / cosLight;
|
||||
}
|
||||
z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar;
|
||||
|
||||
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
|
||||
shadow_position.z -= z_bias;
|
||||
perspective_factor = pFactor;
|
||||
|
||||
if (f_timeofday < 0.2) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday));
|
||||
} else if (f_timeofday >= 0.8) {
|
||||
adj_shadow_strength = f_shadow_strength * 0.5 *
|
||||
mtsmoothstep(0.8, 0.83, f_timeofday);
|
||||
} else {
|
||||
adj_shadow_strength = f_shadow_strength *
|
||||
mtsmoothstep(0.20, 0.25, f_timeofday) *
|
||||
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday));
|
||||
}
|
||||
}
|
||||
f_normal_length = length(vNormal);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,28 +1,45 @@
|
||||
uniform mat4 LightMVP; // world matrix
|
||||
uniform vec4 CameraPos;
|
||||
varying vec4 tPos;
|
||||
#ifdef COLORED_SHADOWS
|
||||
varying vec3 varColor;
|
||||
#endif
|
||||
|
||||
const float bias0 = 0.9;
|
||||
const float zPersFactor = 0.5;
|
||||
const float bias1 = 1.0 - bias0 + 1e-6;
|
||||
uniform float xyPerspectiveBias0;
|
||||
uniform float xyPerspectiveBias1;
|
||||
uniform float zPerspectiveBias;
|
||||
|
||||
vec4 getPerspectiveFactor(in vec4 shadowPosition)
|
||||
vec4 getRelativePosition(in vec4 position)
|
||||
{
|
||||
float pDistance = length(shadowPosition.xy);
|
||||
float pFactor = pDistance * bias0 + bias1;
|
||||
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
|
||||
|
||||
return shadowPosition;
|
||||
vec2 l = position.xy - CameraPos.xy;
|
||||
vec2 s = l / abs(l);
|
||||
s = (1.0 - s * CameraPos.xy);
|
||||
l /= s;
|
||||
return vec4(l, s);
|
||||
}
|
||||
|
||||
float getPerspectiveFactor(in vec4 relativePosition)
|
||||
{
|
||||
float pDistance = length(relativePosition.xy);
|
||||
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
|
||||
return pFactor;
|
||||
}
|
||||
|
||||
vec4 applyPerspectiveDistortion(in vec4 position)
|
||||
{
|
||||
vec4 l = getRelativePosition(position);
|
||||
float pFactor = getPerspectiveFactor(l);
|
||||
l.xy /= pFactor;
|
||||
position.xy = l.xy * l.zw + CameraPos.xy;
|
||||
position.z *= zPerspectiveBias;
|
||||
return position;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 pos = LightMVP * gl_Vertex;
|
||||
|
||||
tPos = getPerspectiveFactor(LightMVP * gl_Vertex);
|
||||
tPos = applyPerspectiveDistortion(LightMVP * gl_Vertex);
|
||||
|
||||
gl_Position = vec4(tPos.xyz, 1.0);
|
||||
gl_TexCoord[0].st = gl_MultiTexCoord0.st;
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
uniform mat4 LightMVP; // world matrix
|
||||
uniform vec4 CameraPos; // camera position
|
||||
varying vec4 tPos;
|
||||
|
||||
const float bias0 = 0.9;
|
||||
const float zPersFactor = 0.5;
|
||||
const float bias1 = 1.0 - bias0 + 1e-6;
|
||||
uniform float xyPerspectiveBias0;
|
||||
uniform float xyPerspectiveBias1;
|
||||
uniform float zPerspectiveBias;
|
||||
|
||||
vec4 getPerspectiveFactor(in vec4 shadowPosition)
|
||||
vec4 getRelativePosition(in vec4 position)
|
||||
{
|
||||
float pDistance = length(shadowPosition.xy);
|
||||
float pFactor = pDistance * bias0 + bias1;
|
||||
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
|
||||
|
||||
return shadowPosition;
|
||||
vec2 l = position.xy - CameraPos.xy;
|
||||
vec2 s = l / abs(l);
|
||||
s = (1.0 - s * CameraPos.xy);
|
||||
l /= s;
|
||||
return vec4(l, s);
|
||||
}
|
||||
|
||||
float getPerspectiveFactor(in vec4 relativePosition)
|
||||
{
|
||||
float pDistance = length(relativePosition.xy);
|
||||
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
|
||||
return pFactor;
|
||||
}
|
||||
|
||||
vec4 applyPerspectiveDistortion(in vec4 position)
|
||||
{
|
||||
vec4 l = getRelativePosition(position);
|
||||
float pFactor = getPerspectiveFactor(l);
|
||||
l.xy /= pFactor;
|
||||
position.xy = l.xy * l.zw + CameraPos.xy;
|
||||
position.z *= zPerspectiveBias;
|
||||
return position;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 pos = LightMVP * gl_Vertex;
|
||||
|
||||
tPos = getPerspectiveFactor(pos);
|
||||
tPos = applyPerspectiveDistortion(pos);
|
||||
|
||||
gl_Position = vec4(tPos.xyz, 1.0);
|
||||
gl_TexCoord[0].st = gl_MultiTexCoord0.st;
|
||||
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
|
||||
}
|
||||
|
||||
@@ -165,6 +165,9 @@ core.after(5, function()
|
||||
|
||||
print("[PREVIEW] Find node near: " .. dump(core.find_node_near({x=0, y=20, z=0}, 10,
|
||||
{"group:tree", "default:dirt", "default:stone"})))
|
||||
|
||||
print("[PREVIEW] Settings: preview_csm_test_setting = " ..
|
||||
tostring(core.settings:get_bool("preview_csm_test_setting", false)))
|
||||
end)
|
||||
|
||||
core.register_on_dignode(function(pos, node)
|
||||
|
||||
@@ -1 +1 @@
|
||||
name = preview
|
||||
name = preview
|
||||
|
||||
1
clientmods/preview/settingtypes.txt
Normal file
@@ -0,0 +1 @@
|
||||
preview_csm_test_setting (Test CSM setting) bool false
|
||||
@@ -1,73 +0,0 @@
|
||||
#-------------------------------------------------------------------
|
||||
# The contents of this file are placed in the public domain. Feel
|
||||
# free to make use of it in any way you like.
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# - Try to find OpenGLES and EGL
|
||||
# Once done this will define
|
||||
#
|
||||
# OPENGLES2_FOUND - system has OpenGLES
|
||||
# OPENGLES2_INCLUDE_DIR - the GL include directory
|
||||
# OPENGLES2_LIBRARIES - Link these to use OpenGLES
|
||||
#
|
||||
# EGL_FOUND - system has EGL
|
||||
# EGL_INCLUDE_DIR - the EGL include directory
|
||||
# EGL_LIBRARIES - Link these to use EGL
|
||||
|
||||
# Win32 and Apple are not tested!
|
||||
# Linux tested and works
|
||||
|
||||
if(WIN32)
|
||||
find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h)
|
||||
find_library(OPENGLES2_LIBRARY libGLESv2)
|
||||
elseif(APPLE)
|
||||
create_search_paths(/Developer/Platforms)
|
||||
findpkg_framework(OpenGLES2)
|
||||
set(OPENGLES2_LIBRARY "-framework OpenGLES")
|
||||
else()
|
||||
# Unix
|
||||
find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h
|
||||
PATHS /usr/openwin/share/include
|
||||
/opt/graphics/OpenGL/include
|
||||
/usr/X11R6/include
|
||||
/usr/include
|
||||
)
|
||||
|
||||
find_library(OPENGLES2_LIBRARY
|
||||
NAMES GLESv2
|
||||
PATHS /opt/graphics/OpenGL/lib
|
||||
/usr/openwin/lib
|
||||
/usr/X11R6/lib
|
||||
/usr/lib
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OpenGLES2 DEFAULT_MSG OPENGLES2_LIBRARY OPENGLES2_INCLUDE_DIR)
|
||||
|
||||
find_path(EGL_INCLUDE_DIR EGL/egl.h
|
||||
PATHS /usr/openwin/share/include
|
||||
/opt/graphics/OpenGL/include
|
||||
/usr/X11R6/include
|
||||
/usr/include
|
||||
)
|
||||
|
||||
find_library(EGL_LIBRARY
|
||||
NAMES EGL
|
||||
PATHS /opt/graphics/OpenGL/lib
|
||||
/usr/openwin/lib
|
||||
/usr/X11R6/lib
|
||||
/usr/lib
|
||||
)
|
||||
|
||||
find_package_handle_standard_args(EGL DEFAULT_MSG EGL_LIBRARY EGL_INCLUDE_DIR)
|
||||
endif()
|
||||
|
||||
set(OPENGLES2_LIBRARIES ${OPENGLES2_LIBRARY})
|
||||
set(EGL_LIBRARIES ${EGL_LIBRARY})
|
||||
|
||||
mark_as_advanced(
|
||||
OPENGLES2_INCLUDE_DIR
|
||||
OPENGLES2_LIBRARY
|
||||
EGL_INCLUDE_DIR
|
||||
EGL_LIBRARY
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR)
|
||||
mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR)
|
||||
|
||||
find_path(SQLITE3_INCLUDE_DIR sqlite3.h)
|
||||
|
||||
|
||||
@@ -29,18 +29,17 @@ else(NOT GP2XWIZ)
|
||||
find_package_handle_standard_args(Vorbis DEFAULT_MSG
|
||||
VORBIS_INCLUDE_DIR VORBIS_LIBRARY)
|
||||
endif(NOT GP2XWIZ)
|
||||
|
||||
|
||||
if(VORBIS_FOUND)
|
||||
if(NOT GP2XWIZ)
|
||||
set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY}
|
||||
${OGG_LIBRARY})
|
||||
else(NOT GP2XWIZ)
|
||||
set(VORBIS_LIBRARIES ${VORBIS_LIBRARY})
|
||||
endif(NOT GP2XWIZ)
|
||||
if(NOT GP2XWIZ)
|
||||
set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY}
|
||||
${OGG_LIBRARY})
|
||||
else(NOT GP2XWIZ)
|
||||
set(VORBIS_LIBRARIES ${VORBIS_LIBRARY})
|
||||
endif(NOT GP2XWIZ)
|
||||
else(VORBIS_FOUND)
|
||||
set(VORBIS_LIBRARIES)
|
||||
set(VORBIS_LIBRARIES)
|
||||
endif(VORBIS_FOUND)
|
||||
|
||||
mark_as_advanced(OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR)
|
||||
mark_as_advanced(OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY)
|
||||
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
# Locate Irrlicht or IrrlichtMt headers on system.
|
||||
# Locate IrrlichtMt headers on system.
|
||||
|
||||
foreach(libname IN ITEMS IrrlichtMt Irrlicht)
|
||||
string(TOLOWER "${libname}" libname2)
|
||||
|
||||
find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
|
||||
DOC "Path to the directory with IrrlichtMt includes"
|
||||
PATHS
|
||||
/usr/local/include/${libname2}
|
||||
/usr/include/${libname2}
|
||||
/system/develop/headers/${libname2} #Haiku
|
||||
PATH_SUFFIXES "include/${libname2}"
|
||||
)
|
||||
|
||||
if(IRRLICHT_INCLUDE_DIR)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
|
||||
DOC "Path to the directory with IrrlichtMt includes"
|
||||
PATHS
|
||||
/usr/local/include/irrlichtmt
|
||||
/usr/include/irrlichtmt
|
||||
/system/develop/headers/irrlichtmt #Haiku
|
||||
PATH_SUFFIXES "include/irrlichtmt"
|
||||
)
|
||||
|
||||
# Handholding for users
|
||||
if(IRRLICHT_INCLUDE_DIR AND (NOT IS_DIRECTORY "${IRRLICHT_INCLUDE_DIR}" OR
|
||||
|
||||
8
doc/breakages.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Minetest Major Breakages List
|
||||
|
||||
This document contains a list of breaking changes to be made in the next major version.
|
||||
|
||||
* Remove attachment space multiplier (*10)
|
||||
* `get_sky()` returns a table (without arg)
|
||||
* `game.conf` name/id mess
|
||||
* remove `depends.txt` / `description.txt` (would simplify ContentDB and Minetest code a little)
|
||||
@@ -1,4 +1,4 @@
|
||||
Minetest Lua Client Modding API Reference 5.5.0
|
||||
Minetest Lua Client Modding API Reference 5.6.0
|
||||
================================================
|
||||
* More information at <http://www.minetest.net/>
|
||||
* Developer Wiki: <http://dev.minetest.net/>
|
||||
@@ -580,6 +580,7 @@ Spatial Vectors
|
||||
* `vector.floor(v)`: returns a vector, each dimension rounded down
|
||||
* `vector.round(v)`: returns a vector, each dimension rounded to nearest int
|
||||
* `vector.apply(v, func)`: returns a vector
|
||||
* `vector.combine(v, w, func)`: returns a vector
|
||||
* `vector.equals(v1, v2)`: returns a boolean
|
||||
|
||||
For the following functions `x` can be either a vector or a number:
|
||||
@@ -937,6 +938,14 @@ Call these functions only at load time!
|
||||
* `minetest.display_chat_message(message)` returns true on success
|
||||
* Shows a chat message to the current player.
|
||||
|
||||
Setting-related
|
||||
---------------
|
||||
|
||||
* `minetest.settings`: Settings object containing all of the settings from the
|
||||
main config file (`minetest.conf`). Check lua_api.txt for class reference.
|
||||
* `minetest.setting_get_pos(name)`: Loads a setting from the main settings and
|
||||
parses it as a position (in the format `(1,2,3)`). Returns a position or nil.
|
||||
|
||||
Class reference
|
||||
---------------
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
@@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
@@ -158,7 +158,7 @@ Library.
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
@@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
@@ -267,7 +267,7 @@ Library will still fall under Section 6.)
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
@@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
@@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
@@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
@@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
|
||||
1402
doc/lua_api.txt
@@ -1,4 +1,4 @@
|
||||
Minetest Lua Mainmenu API Reference 5.5.0
|
||||
Minetest Lua Mainmenu API Reference 5.6.0
|
||||
=========================================
|
||||
|
||||
Introduction
|
||||
@@ -46,6 +46,8 @@ core.open_url(url)
|
||||
core.open_dir(path)
|
||||
^ opens the path in the system file browser/explorer, returns false on failure.
|
||||
^ Must be an existing directory.
|
||||
core.share_file(path)
|
||||
^ Android only. Shares file using the share popup
|
||||
core.get_version() (possible in async calls)
|
||||
^ returns current core version
|
||||
|
||||
@@ -221,13 +223,24 @@ Package - content which is downloadable from the content db, may or may not be i
|
||||
* returns path to global user data,
|
||||
the directory that contains user-provided mods, worlds, games, and texture packs.
|
||||
* core.get_modpath() (possible in async calls)
|
||||
* returns path to global modpath, where mods can be installed
|
||||
* returns path to global modpath in the user path, where mods can be installed
|
||||
* core.get_modpaths() (possible in async calls)
|
||||
* returns list of paths to global modpaths, where mods have been installed
|
||||
|
||||
* returns table of virtual path to global modpaths, where mods have been installed
|
||||
The difference with "core.get_modpath" is that no mods should be installed in these
|
||||
directories by Minetest -- they might be read-only.
|
||||
|
||||
Ex:
|
||||
|
||||
```
|
||||
{
|
||||
mods = "/home/user/.minetest/mods",
|
||||
share = "/usr/share/minetest/mods",
|
||||
|
||||
-- Custom dirs can be specified by the MINETEST_MOD_DIR env variable
|
||||
["/path/to/custom/dir"] = "/path/to/custom/dir",
|
||||
}
|
||||
```
|
||||
|
||||
* core.get_clientmodpath() (possible in async calls)
|
||||
* returns path to global client-side modpath
|
||||
* core.get_gamepath() (possible in async calls)
|
||||
@@ -235,13 +248,14 @@ Package - content which is downloadable from the content db, may or may not be i
|
||||
* core.get_texturepath() (possible in async calls)
|
||||
* returns path to default textures
|
||||
* core.get_game(index)
|
||||
* `name` in return value is deprecated, use `title` instead.
|
||||
* returns:
|
||||
|
||||
{
|
||||
id = <id>,
|
||||
path = <full path to game>,
|
||||
gamemods_path = <path>,
|
||||
name = <name of game>,
|
||||
title = <title of game>,
|
||||
menuicon_path = <full path to menuicon>,
|
||||
author = "author",
|
||||
DEPRECATED:
|
||||
@@ -253,15 +267,27 @@ Package - content which is downloadable from the content db, may or may not be i
|
||||
* returns
|
||||
|
||||
{
|
||||
name = "name of content",
|
||||
name = "technical_id",
|
||||
type = "mod" or "modpack" or "game" or "txp",
|
||||
title = "Human readable title",
|
||||
description = "description",
|
||||
author = "author",
|
||||
path = "path/to/content",
|
||||
depends = {"mod", "names"}, -- mods only
|
||||
optional_depends = {"mod", "names"}, -- mods only
|
||||
}
|
||||
* core.check_mod_configuration(world_path, mod_paths)
|
||||
* Checks whether configuration is valid.
|
||||
* `world_path`: path to the world
|
||||
* `mod_paths`: list of enabled mod paths
|
||||
* returns:
|
||||
|
||||
{
|
||||
is_consistent = true, -- true is consistent, false otherwise
|
||||
unsatisfied_mods = {}, -- list of mod specs
|
||||
satisfied_mods = {}, -- list of mod specs
|
||||
error_message = "", -- message or nil
|
||||
}
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
@@ -92,8 +92,6 @@ Set password from contents of file
|
||||
.B \-\-random\-input
|
||||
Enable random user input, for testing (client only)
|
||||
.TP
|
||||
.B \-\-videomodes
|
||||
List available video modes (client only)
|
||||
.TP
|
||||
.B \-\-speedtests
|
||||
Run speed tests
|
||||
|
||||
@@ -129,9 +129,34 @@ Example content (added indentation and - explanations):
|
||||
backend = sqlite3 - which DB backend to use for blocks (sqlite3, dummy, leveldb, redis, postgresql)
|
||||
player_backend = sqlite3 - which DB backend to use for player data
|
||||
readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder)
|
||||
auth_backend = files - which DB backend to use for authentication data
|
||||
server_announce = false - whether the server is publicly announced or not
|
||||
load_mod_<mod> = false - whether <mod> is to be loaded in this world
|
||||
auth_backend = files - which DB backend to use for authentication data
|
||||
|
||||
For load_mod_<mod>, the possible values are:
|
||||
|
||||
* `false` - Do not load the mod.
|
||||
* `true` - Load the mod from wherever it is found (may cause conflicts if the same mod appears also in some other place).
|
||||
* `mods/modpack/moddir` - Relative path to the mod
|
||||
* Must be one of the following:
|
||||
* `mods/`: mods in the user path's mods folder (ex `/home/user/.minetest/mods`)
|
||||
* `share/`: mods in the share's mods folder (ex: `/usr/share/minetest/mods`)
|
||||
* `/path/to/env`: you can use absolute paths to mods inside folders specified with the `MINETEST_MOD_PATH` env variable.
|
||||
* Other locations and absolute paths are not supported
|
||||
* Note that `moddir` is the directory name, not the mod name specified in mod.conf.
|
||||
|
||||
PostgreSQL backend specific settings:
|
||||
pgsql_connection = host=127.0.0.1 port=5432 user=mt_user password=mt_password dbname=minetest
|
||||
pgsql_player_connection = (same parameters as above)
|
||||
pgsql_readonly_connection = (same parameters as above)
|
||||
pgsql_auth_connection = (same parameters as above)
|
||||
|
||||
Redis backend specific settings:
|
||||
redis_address = 127.0.0.1 - Redis server address
|
||||
redis_hash = foo - Database hash
|
||||
redis_port = 6379 - (optional) connection port
|
||||
redis_password = hunter2 - (optional) server password
|
||||
|
||||
|
||||
Player File Format
|
||||
===================
|
||||
@@ -333,6 +358,9 @@ if map format version >= 29:
|
||||
- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time
|
||||
difference when loaded
|
||||
|
||||
u8 name_id_mapping_version
|
||||
- Should be zero for map format version 29.
|
||||
|
||||
u16 num_name_id_mappings
|
||||
foreach num_name_id_mappings
|
||||
u16 id
|
||||
@@ -434,7 +462,7 @@ if map format version < 29:
|
||||
u8[name_len] name
|
||||
|
||||
- Node timers
|
||||
if map format version == 25:
|
||||
if map format version >= 25:
|
||||
u8 length of the data of a single timer (always 2+4+4=10)
|
||||
u16 num_of_timers
|
||||
foreach num_of_timers:
|
||||
|
||||
43
games/devtest/.luacheckrc
Normal file
@@ -0,0 +1,43 @@
|
||||
unused_args = false
|
||||
allow_defined_top = true
|
||||
max_string_line_length = false
|
||||
max_line_length = false
|
||||
|
||||
ignore = {
|
||||
"131", -- Unused global variable
|
||||
"211", -- Unused local variable
|
||||
"231", -- Local variable never accessed
|
||||
"311", -- Value assigned to a local variable is unused
|
||||
"412", -- Redefining an argument
|
||||
"421", -- Shadowing a local variable
|
||||
"431", -- Shadowing an upvalue
|
||||
"432", -- Shadowing an upvalue argument
|
||||
"611", -- Line contains only whitespace
|
||||
}
|
||||
|
||||
read_globals = {
|
||||
"ItemStack",
|
||||
"INIT",
|
||||
"DIR_DELIM",
|
||||
"dump", "dump2",
|
||||
"fgettext", "fgettext_ne",
|
||||
"vector",
|
||||
"VoxelArea",
|
||||
"profiler",
|
||||
"Settings",
|
||||
"check",
|
||||
"PseudoRandom",
|
||||
|
||||
string = {fields = {"split", "trim"}},
|
||||
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
|
||||
math = {fields = {"hypot", "round"}},
|
||||
}
|
||||
|
||||
globals = {
|
||||
"aborted",
|
||||
"minetest",
|
||||
"core",
|
||||
os = { fields = { "tempfolder" } },
|
||||
"_",
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
name = Development Test
|
||||
title = Development Test
|
||||
description = Testing environment to help with testing the engine features of Minetest. It can also be helpful in mod development.
|
||||
|
||||
@@ -279,50 +279,135 @@ minetest.register_tool("basetools:sword_wood", {
|
||||
})
|
||||
minetest.register_tool("basetools:sword_stone", {
|
||||
description = "Stone Sword".."\n"..
|
||||
"Damage: fleshy=4",
|
||||
"Damage: fleshy=5",
|
||||
inventory_image = "basetools_stonesword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
max_drop_level=0,
|
||||
damage_groups = {fleshy=4},
|
||||
damage_groups = {fleshy=5},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:sword_steel", {
|
||||
description = "Steel Sword".."\n"..
|
||||
"Damage: fleshy=6",
|
||||
"Damage: fleshy=10",
|
||||
inventory_image = "basetools_steelsword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
max_drop_level=1,
|
||||
damage_groups = {fleshy=6},
|
||||
damage_groups = {fleshy=10},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:sword_titanium", {
|
||||
description = "Titanium Sword".."\n"..
|
||||
"Damage: fleshy=100",
|
||||
inventory_image = "basetools_titaniumsword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
max_drop_level=1,
|
||||
damage_groups = {fleshy=100},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:sword_blood", {
|
||||
description = "Blood Sword".."\n"..
|
||||
"Damage: fleshy=1000",
|
||||
inventory_image = "basetools_bloodsword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
max_drop_level=1,
|
||||
damage_groups = {fleshy=1000},
|
||||
}
|
||||
})
|
||||
|
||||
-- Max. damage sword
|
||||
minetest.register_tool("basetools:sword_mese", {
|
||||
description = "Mese Sword".."\n"..
|
||||
"Damage: fleshy=32767, fiery=32767, icy=32767".."\n"..
|
||||
"Full Punch Interval: 0.0s",
|
||||
inventory_image = "basetools_mesesword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.0,
|
||||
max_drop_level=1,
|
||||
damage_groups = {fleshy=32767, fiery=32767, icy=32767},
|
||||
}
|
||||
})
|
||||
|
||||
-- Fire/Ice sword: Deal damage to non-fleshy damage groups
|
||||
minetest.register_tool("basetools:sword_fire", {
|
||||
description = "Fire Sword".."\n"..
|
||||
"Damage: icy=6",
|
||||
"Damage: icy=10",
|
||||
inventory_image = "basetools_firesword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
max_drop_level=0,
|
||||
damage_groups = {icy=6},
|
||||
damage_groups = {icy=10},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:sword_ice", {
|
||||
description = "Ice Sword".."\n"..
|
||||
"Damage: fiery=6",
|
||||
"Damage: fiery=10",
|
||||
inventory_image = "basetools_icesword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
max_drop_level=0,
|
||||
damage_groups = {fiery=6},
|
||||
damage_groups = {fiery=10},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:sword_elemental", {
|
||||
description = "Elemental Sword".."\n"..
|
||||
"Damage: fiery=10, icy=10",
|
||||
inventory_image = "basetools_elementalsword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
max_drop_level=0,
|
||||
damage_groups = {fiery=10, icy=10},
|
||||
}
|
||||
})
|
||||
|
||||
-- Healing weapons: heal HP
|
||||
minetest.register_tool("basetools:dagger_heal", {
|
||||
description = "Healing Dagger".."\n"..
|
||||
"Heal: fleshy=1".."\n"..
|
||||
"Full Punch Interval: 0.5s",
|
||||
inventory_image = "basetools_healdagger.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.5,
|
||||
damage_groups = {fleshy=-1},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:sword_heal", {
|
||||
description = "Healing Sword".."\n"..
|
||||
"Heal: fleshy=10",
|
||||
inventory_image = "basetools_healsword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy=-10},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:sword_heal_super", {
|
||||
description = "Super Healing Sword".."\n"..
|
||||
"Heal: fleshy=32768, fiery=32768, icy=32768",
|
||||
inventory_image = "basetools_superhealsword.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy=-32768, fiery=-32768, icy=-32768},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
--
|
||||
-- Dagger: Low damage, fast punch interval
|
||||
--
|
||||
minetest.register_tool("basetools:dagger_wood", {
|
||||
description = "Wooden Dagger".."\n"..
|
||||
"Damage: fleshy=1".."\n"..
|
||||
"Full Punch Interval: 0.5s",
|
||||
inventory_image = "basetools_wooddagger.png",
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.5,
|
||||
max_drop_level=0,
|
||||
damage_groups = {fleshy=1},
|
||||
}
|
||||
})
|
||||
minetest.register_tool("basetools:dagger_steel", {
|
||||
description = "Steel Dagger".."\n"..
|
||||
"Damage: fleshy=2".."\n"..
|
||||
@@ -343,7 +428,7 @@ for i=1, #uses do
|
||||
minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), {
|
||||
description = u.."-Uses Pickaxe".."\n"..
|
||||
"Digs cracky=3",
|
||||
inventory_image = "basetools_steelpick.png^[colorize:"..color..":127",
|
||||
inventory_image = "basetools_usespick.png^[colorize:"..color..":127",
|
||||
tool_capabilities = {
|
||||
max_drop_level=0,
|
||||
groupcaps={
|
||||
@@ -355,7 +440,7 @@ for i=1, #uses do
|
||||
minetest.register_tool("basetools:sword_uses_"..string.format("%05d", u), {
|
||||
description = u.."-Uses Sword".."\n"..
|
||||
"Damage: fleshy=1",
|
||||
inventory_image = "basetools_woodsword.png^[colorize:"..color..":127",
|
||||
inventory_image = "basetools_usessword.png^[colorize:"..color..":127",
|
||||
tool_capabilities = {
|
||||
damage_groups = {fleshy=1},
|
||||
punch_attack_uses = u,
|
||||
|
||||
BIN
games/devtest/mods/basetools/textures/basetools_bloodsword.png
Normal file
|
After Width: | Height: | Size: 165 B |
|
After Width: | Height: | Size: 177 B |
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 166 B |
BIN
games/devtest/mods/basetools/textures/basetools_healdagger.png
Normal file
|
After Width: | Height: | Size: 162 B |
BIN
games/devtest/mods/basetools/textures/basetools_healsword.png
Normal file
|
After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 156 B |