Compare commits

..

176 Commits

Author SHA1 Message Date
rubenwardy
25a56f11c8 Bump version to 5.4.2 2021-10-22 23:06:51 +01:00
rubenwardy
8271c6481f Fix manifest and various things 2021-10-20 15:27:30 +01:00
sfan5
83e26f839d Reserve vectors before pushing and other code quality changes (#11161) 2021-10-20 14:25:39 +01:00
sfan5
c3f7905d82 Remove broken timeout behaviour
Code that relies on `resend_count` was added in 7ea4a03 and 247a1eb, but never worked.
This was fixed in #11607 which caused the problem to surface.
Hence undo the first commit entirely and change the logic of the second.
2021-10-19 19:23:00 +01:00
sfan5
05b54a8d18 Shave off buffer copies in networking code (#11607) 2021-10-19 19:22:46 +01:00
rubenwardy
e5cfdd369e Update deps ref 2021-10-18 18:33:25 +01:00
rubenwardy
c61d8cfb85 Use scoped app storage on Android (#11466)
From November 2021, the Play Store will no longer be accepting
apps which use the deprecated getExternalStorageDirectory() API.

Therefore, this commit replaces uses of deprecated API with the new
scoped API (`getExternalFilesDir()` and `getExternalCacheDir()`).
It also provides a temporary migration to move user data from the
shared external directory to new storage.

Fixes #2097,  #11417 and #11118
2021-10-18 18:12:03 +01:00
NeroBurner
27f4195471 Move build/android directory to root of project (#11283) 2021-10-18 18:11:33 +01:00
sfan5
b2596eda32 Bump version to 5.4.1 2021-04-10 18:41:12 +02:00
waxtatect
9379440fcb Translated using Weblate (French)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
Brian Gaucher
b7c502c8d1 Translated using Weblate (French)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
Markus Mikkonen
3c16d2d9b4 Translated using Weblate (Finnish)
Currently translated at 3.2% (44 of 1356 strings)
2021-04-10 17:51:40 +02:00
Tviljan
6cbc03a418 Translated using Weblate (Finnish)
Currently translated at 3.2% (44 of 1356 strings)
2021-04-10 17:51:40 +02:00
Edward
15ecc0fa65 Translated using Weblate (Russian)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
ssantos
7f2f8cdad1 Translated using Weblate (Portuguese)
Currently translated at 93.2% (1264 of 1356 strings)
2021-04-10 17:51:40 +02:00
waxtatect
a64646cb7b Translated using Weblate (French)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
David Leal
05f531c538 Translated using Weblate (Spanish)
Currently translated at 79.7% (1081 of 1356 strings)
2021-04-10 17:51:40 +02:00
François Delpierre
7ca335446b Translated using Weblate (French)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
waxtatect
99e96a6581 Translated using Weblate (French)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
BreadW
7200d8f90e Translated using Weblate (Japanese)
Currently translated at 99.8% (1354 of 1356 strings)
2021-04-10 17:51:40 +02:00
GnuPGを使うべきだ
7038837aca Translated using Weblate (Japanese)
Currently translated at 99.7% (1353 of 1356 strings)
2021-04-10 17:51:40 +02:00
ItsWidee
541dcc0e5a Translated using Weblate (French)
Currently translated at 98.0% (1330 of 1356 strings)
2021-04-10 17:51:40 +02:00
François Delpierre
1060b5aabf Translated using Weblate (French)
Currently translated at 96.6% (1311 of 1356 strings)
2021-04-10 17:51:40 +02:00
Dainis
6ea73c4982 Translated using Weblate (Latvian)
Currently translated at 28.6% (388 of 1356 strings)
2021-04-10 17:51:40 +02:00
Konstantin Yeliseyev
cff847273a Translated using Weblate (Russian)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
ResuUman
e669f8db9b Translated using Weblate (Polish)
Currently translated at 72.4% (982 of 1356 strings)
2021-04-10 17:51:40 +02:00
Mateusz Mendel
461cc30842 Translated using Weblate (Polish)
Currently translated at 72.4% (982 of 1356 strings)
2021-04-10 17:51:40 +02:00
gnu-ewm
13d7cb957c Translated using Weblate (Polish)
Currently translated at 71.6% (972 of 1356 strings)
2021-04-10 17:51:40 +02:00
Alessandro Mandelli
47a439a905 Translated using Weblate (Italian)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:40 +02:00
Hatlábú Farkas
053fb96694 Translated using Weblate (Hungarian)
Currently translated at 75.8% (1028 of 1356 strings)
2021-04-10 17:51:39 +02:00
ItsWidee
b72c1f7367 Translated using Weblate (French)
Currently translated at 96.5% (1309 of 1356 strings)
2021-04-10 17:51:39 +02:00
matiasC
e90738575f Translated using Weblate (Spanish)
Currently translated at 79.5% (1079 of 1356 strings)
2021-04-10 17:51:39 +02:00
Joaquín Villalba
35718eec9c Translated using Weblate (Spanish)
Currently translated at 79.5% (1079 of 1356 strings)
2021-04-10 17:51:39 +02:00
AnthonyDe
8285a53152 Translated using Weblate (Spanish)
Currently translated at 79.5% (1079 of 1356 strings)
2021-04-10 17:51:39 +02:00
Michalis
116fe7815b Translated using Weblate (Greek)
Currently translated at 8.8% (120 of 1356 strings)
2021-04-10 17:51:39 +02:00
Yangjun Wang
4c2efd7da3 Translated using Weblate (Chinese (Simplified))
Currently translated at 94.7% (1285 of 1356 strings)
2021-04-10 17:51:39 +02:00
Liu Tao
1a433e3185 Translated using Weblate (Chinese (Simplified))
Currently translated at 94.7% (1285 of 1356 strings)
2021-04-10 17:51:39 +02:00
Yangjun Wang
fa98a00916 Translated using Weblate (Chinese (Simplified))
Currently translated at 92.5% (1255 of 1356 strings)
2021-04-10 17:51:39 +02:00
David Leal
f4dd46ad60 Translated using Weblate (Spanish)
Currently translated at 79.0% (1072 of 1356 strings)
2021-04-10 17:51:39 +02:00
Joaquín Villalba
41825ccfbb Translated using Weblate (Spanish)
Currently translated at 78.0% (1059 of 1356 strings)
2021-04-10 17:51:39 +02:00
David Leal
4d1a5f12c0 Translated using Weblate (Spanish)
Currently translated at 78.0% (1059 of 1356 strings)
2021-04-10 17:51:39 +02:00
Agustin Calderon
cc58d56c6d Translated using Weblate (Spanish)
Currently translated at 78.0% (1059 of 1356 strings)
2021-04-10 17:51:39 +02:00
abidin toumi
683ef07312 Translated using Weblate (Arabic)
Currently translated at 25.7% (349 of 1356 strings)
2021-04-10 17:51:39 +02:00
Tirifto
c77c78cef5 Translated using Weblate (Esperanto)
Currently translated at 94.6% (1283 of 1356 strings)
2021-04-10 17:51:39 +02:00
Oğuz Ersen
88517030a4 Translated using Weblate (Turkish)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:39 +02:00
THANOS SIOURDAKIS
45471e8e63 Translated using Weblate (Greek)
Currently translated at 8.8% (120 of 1356 strings)
2021-04-10 17:51:39 +02:00
Yaya - Nurul Azeera Hidayah @ Muhammad Nur Hidayat Yasuyoshi
4e620beb75 Translated using Weblate (Malay)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:39 +02:00
winniepee
e7ab875b47 Translated using Weblate (Chinese (Simplified))
Currently translated at 92.4% (1254 of 1356 strings)
2021-04-10 17:51:39 +02:00
Ayes
be74ed9ab6 Translated using Weblate (Estonian)
Currently translated at 39.5% (536 of 1356 strings)
2021-04-10 17:51:39 +02:00
Wuzzy
52b2eacd37 Translated using Weblate (German)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:39 +02:00
Marian
6b4210a2ea Translated using Weblate (Slovak)
Currently translated at 100.0% (1356 of 1356 strings)
2021-04-10 17:51:39 +02:00
narrnika
740d0da5ee Translated using Weblate (Russian)
Currently translated at 99.3% (1347 of 1356 strings)
2021-04-10 17:51:07 +02:00
Mateusz Mendel
34883d356e Translated using Weblate (Polish)
Currently translated at 71.2% (966 of 1356 strings)
2021-04-10 17:51:07 +02:00
Giov4
aeafcce314 Translated using Weblate (Italian)
Currently translated at 99.7% (1352 of 1356 strings)
2021-04-10 17:51:07 +02:00
savilli
ae1d82c325 Fix hud_change and hud_remove after hud_add (#10997) 2021-04-09 22:05:22 +02:00
Vitaliy
1c89a07226 Restore minimal normal texture support (for minimap shading) 2021-04-09 22:04:51 +02:00
sfan5
43e262f13e Don't apply connection timeout limit to locally hosted servers
fixes #11085
2021-04-05 16:02:47 +02:00
sfan5
e5f802ab5c Fix server favorites not saving when client/serverlist/ doesn't exist already (#11152) 2021-04-05 16:02:32 +02:00
Lars Müller
847860fc5c Block & report player self-interaction (#11137) 2021-04-05 16:01:27 +02:00
SmallJoker
77e936445f Protect dropping from far node inventories
Also changes if/if to switch/case
2021-04-05 16:01:21 +02:00
SmallJoker
41beb74ef7 Protect per-player detached inventory actions 2021-04-05 16:01:15 +02:00
Elias Fleckenstein
67be50b706 Make pkgmgr handle modpacks containing modpacks properly
fixes #10550
2021-04-05 16:01:10 +02:00
rubenwardy
cd840b7c9d pkgmgr: Fix crash when .conf release field is invalid
Fixes #10942
2021-04-05 16:01:03 +02:00
sfan5
07903949ec Merge remote-tracking branch 'origin/stable-5' into HEAD 2021-02-23 20:18:23 +01:00
sfan5
d2156ddaad Merge remote-tracking branch 'origin/stable-5' into HEAD 2020-07-09 22:21:48 +02:00
sfan5
a84ff4b3ff Merge remote-tracking branch 'origin/stable-5' into HEAD 2020-04-05 18:44:51 +02:00
rubenwardy
c02f13d33f Release 5.1.1 2020-01-11 18:29:02 +00:00
rubenwardy
f6490859fd Translated using Weblate (Japanese (Kansai))
Currently translated at 0.2% (2 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
4b7816dbf4 Translated using Weblate (Burmese)
Currently translated at 0.2% (2 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
86ffbc3ec5 Translated using Weblate (Kazakh)
Currently translated at 0.2% (2 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
67257f44a5 Translated using Weblate (Arabic)
Currently translated at 6.1% (78 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
3086f5567a Translated using Weblate (Vietnamese)
Currently translated at 2.5% (32 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
bb8acb095d Translated using Weblate (Portuguese)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
24be3cbb5f Translated using Weblate (Basque)
Currently translated at 15.1% (193 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
9773191103 Translated using Weblate (Greek)
Currently translated at 1.4% (18 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
ddc703c3ec Translated using Weblate (Filipino)
Currently translated at 0.2% (2 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
09ec204e4b Translated using Weblate (Thai)
Currently translated at 66.9% (852 of 1274 strings)
2020-01-11 18:29:02 +00:00
rubenwardy
8b6cafa0e0 Translated using Weblate (Lao)
Currently translated at 0.2% (2 of 1274 strings)
2020-01-11 18:29:02 +00:00
Osoitz
1ee8be9d43 Translated using Weblate (Basque)
Currently translated at 15.1% (192 of 1274 strings)
2020-01-10 18:57:47 +00:00
Dhimas Wnz
91bc190d21 Translated using Weblate (Indonesian)
Currently translated at 96.9% (1234 of 1274 strings)
2020-01-10 18:57:47 +00:00
THANOS SIOURDAKIS
4dc833b642 Translated using Weblate (Greek)
Currently translated at 1.3% (17 of 1274 strings)
2020-01-10 18:57:47 +00:00
universales
895e9f8d5c Translated using Weblate (Spanish)
Currently translated at 61.9% (789 of 1274 strings)
2020-01-10 18:57:47 +00:00
Osoitz
12906ff631 Translated using Weblate (Basque)
Currently translated at 9.7% (123 of 1274 strings)
2020-01-10 18:57:47 +00:00
Osoitz
9c9bcff107 Added translation using Weblate (Basque) 2020-01-10 18:57:47 +00:00
Hotower
443ca5c63b Translated using Weblate (Chinese (Simplified))
Currently translated at 65.1% (830 of 1274 strings)
2020-01-10 18:57:47 +00:00
Stas Kies
1102b1fc4c Translated using Weblate (German)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:47 +00:00
abidin toumi
0d089eab21 Translated using Weblate (Arabic)
Currently translated at 6.0% (77 of 1274 strings)
2020-01-10 18:57:47 +00:00
zaoqi
b44fada29e Translated using Weblate (Chinese (Simplified))
Currently translated at 65.1% (830 of 1274 strings)
2020-01-10 18:57:47 +00:00
Yangjun Wang
859ea160e6 Translated using Weblate (Chinese (Simplified))
Currently translated at 63.2% (805 of 1274 strings)
2020-01-10 18:57:47 +00:00
Ács Zoltán
1c49e2ffc0 Translated using Weblate (Hungarian)
Currently translated at 61.9% (789 of 1274 strings)
2020-01-10 18:57:47 +00:00
Tirifto
140245c58d Translated using Weblate (Esperanto)
Currently translated at 97.2% (1238 of 1274 strings)
2020-01-10 18:57:47 +00:00
Petter Reinholdtsen
47adcced60 Translated using Weblate (Norwegian Bokmål)
Currently translated at 43.3% (552 of 1274 strings)
2020-01-10 18:57:47 +00:00
Luboš Nečas
a2054deb12 Translated using Weblate (Czech)
Currently translated at 48.8% (622 of 1274 strings)
2020-01-10 18:57:47 +00:00
Tirifto
30fdfd9ded Translated using Weblate (Esperanto)
Currently translated at 94.9% (1209 of 1274 strings)
2020-01-10 18:57:47 +00:00
ramon.venson
a87a86eb92 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.9% (1235 of 1274 strings)
2020-01-10 18:57:47 +00:00
Daniel Mancini
49dfbcfbc8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.9% (1235 of 1274 strings)
2020-01-10 18:57:47 +00:00
Andrei Stepanov
c558a4f4f6 Translated using Weblate (Russian)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:46 +00:00
Andrei Stepanov
4f49a2248f Translated using Weblate (Russian)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:46 +00:00
Andrei Stepanov
4e57da42c1 Translated using Weblate (Russian)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:46 +00:00
Andrei Stepanov
4f6c9e206c Translated using Weblate (Russian)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:46 +00:00
Andrei Stepanov
699e1d4b77 Translated using Weblate (Russian)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:46 +00:00
Matej Mlinar
fa2978b3b9 Translated using Weblate (Slovenian)
Currently translated at 43.9% (559 of 1274 strings)
2020-01-10 18:57:46 +00:00
abidin toumi
ffcdf741fc Translated using Weblate (Arabic)
Currently translated at 5.7% (73 of 1274 strings)
2020-01-10 18:57:46 +00:00
Fixer
429d7f33d4 Translated using Weblate (Ukrainian)
Currently translated at 42.1% (536 of 1274 strings)
2020-01-10 18:57:46 +00:00
Andrei Stepanov
b6a229775e Translated using Weblate (Russian)
Currently translated at 81.9% (1044 of 1274 strings)
2020-01-10 18:57:46 +00:00
abidin toumi
0a4580368a Translated using Weblate (Arabic)
Currently translated at 5.6% (71 of 1274 strings)
2020-01-10 18:57:46 +00:00
abidin toumi
e5b191e1f1 Translated using Weblate (Arabic)
Currently translated at 4.2% (53 of 1274 strings)
2020-01-10 18:57:46 +00:00
Andrei Stepanov
a07acc067b Translated using Weblate (Russian)
Currently translated at 81.7% (1041 of 1274 strings)
2020-01-10 18:57:46 +00:00
abidin toumi
01e763f879 Added translation using Weblate (Arabic) 2020-01-10 18:57:46 +00:00
Viktar Vauchkevich
a95e75261a Translated using Weblate (Belarusian)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:46 +00:00
Julien Maulny
dcaa7ac609 Translated using Weblate (French)
Currently translated at 97.0% (1236 of 1274 strings)
2020-01-10 18:57:46 +00:00
Jacques Lagrange
7d2ae10849 Translated using Weblate (Italian)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
642cac3759 Translated using Weblate (Japanese (Kansai))
Currently translated at 0.1% (1 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
662af2edba Translated using Weblate (Dhivehi)
Currently translated at 8.2% (105 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
6f97e15a8f Translated using Weblate (Chinese (Simplified))
Currently translated at 63.0% (803 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
ab2fa2ca9d Translated using Weblate (Burmese)
Currently translated at 0.1% (1 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
4666c99b27 Translated using Weblate (Kyrgyz)
Currently translated at 8.6% (110 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
e38415c56d Translated using Weblate (Slovenian)
Currently translated at 42.0% (535 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
3f3fce4664 Translated using Weblate (Kannada)
Currently translated at 3.0% (38 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
1407a7bc8a Translated using Weblate (Kazakh)
Currently translated at 0.1% (1 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
1d268f4b83 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 31.2% (398 of 1274 strings)
2020-01-10 18:57:46 +00:00
Krock
92aab79d27 Translated using Weblate (Italian)
Currently translated at 96.9% (1234 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
0823bb1276 Translated using Weblate (Hebrew)
Currently translated at 4.3% (55 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
18e42efa01 Translated using Weblate (Hungarian)
Currently translated at 61.9% (788 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
0ea16328df Translated using Weblate (Estonian)
Currently translated at 14.4% (183 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
b037efc5c6 Translated using Weblate (Esperanto)
Currently translated at 79.6% (1014 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
839bb71389 Translated using Weblate (Greek)
Currently translated at 1.1% (14 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
2ef7df5cfe Translated using Weblate (Danish)
Currently translated at 49.5% (631 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
f5d2d79f75 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.4% (1228 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
8e5fa96ffc Translated using Weblate (Filipino)
Currently translated at 0.1% (1 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
6dbbe07d50 Translated using Weblate (Thai)
Currently translated at 66.8% (851 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
3c23410753 Translated using Weblate (Lithuanian)
Currently translated at 15.4% (196 of 1274 strings)
2020-01-10 18:57:45 +00:00
Krock
8e35ab78b4 Translated using Weblate (Lao)
Currently translated at 0.1% (1 of 1274 strings)
2020-01-10 18:57:45 +00:00
Allan Nordhøy
7f2911b7f0 Translated using Weblate (Italian)
Currently translated at 96.9% (1234 of 1274 strings)
2020-01-10 18:57:45 +00:00
Mateusz Mendel
d877e90301 Translated using Weblate (Polish)
Currently translated at 81.1% (1033 of 1274 strings)
2020-01-10 18:57:45 +00:00
Vicente Carrasco Alvarez
93db4be726 Translated using Weblate (Spanish)
Currently translated at 61.5% (783 of 1274 strings)
2020-01-10 18:57:45 +00:00
Mateusz Mendel
1f419ad19f Translated using Weblate (Polish)
Currently translated at 81.1% (1033 of 1274 strings)
2020-01-10 18:57:45 +00:00
Muhammad Nur Hidayat Yasuyoshi
3c7f6508ad Translated using Weblate (Malay)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:45 +00:00
Mattias Münster
39449d4b63 Translated using Weblate (Swedish)
Currently translated at 32.7% (417 of 1274 strings)
2020-01-10 18:57:45 +00:00
Jacques Lagrange
28e05295fa Translated using Weblate (Italian)
Currently translated at 96.9% (1234 of 1274 strings)
2020-01-10 18:57:45 +00:00
Ács Zoltán
60c18d3550 Translated using Weblate (Hungarian)
Currently translated at 61.9% (788 of 1274 strings)
2020-01-10 18:57:45 +00:00
ssantos
482ab186a2 Translated using Weblate (Portuguese)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:45 +00:00
BreadW
4814195dc4 Translated using Weblate (Japanese)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:45 +00:00
Wuzzy
d215b7a10e Translated using Weblate (German)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:45 +00:00
nautilusx
0e52b78590 Translated using Weblate (German)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:45 +00:00
monolifed
b3a9d607c4 Translated using Weblate (Turkish)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:45 +00:00
Oguz Ersen
c00081b62c Translated using Weblate (Turkish)
Currently translated at 100.0% (1274 of 1274 strings)
2020-01-10 18:57:44 +00:00
William Breathitt Gray
5e4739b460 Fix find_path for newer jsoncpp installations
The upstream JsonCpp project has renamed the `json/features.h` file to
`json/json_features.h`. This patch fixes the JsonCpp installation search
by looking for `json/allocator.h` which has not been renamed on newer
versions of JsonCpp.

Fixes: https://github.com/minetest/minetest/issues/9119
2019-12-31 21:39:16 +00:00
SmallJoker
0d8f598df2 Fix LocalPlayer-bound sound playback broken by 81c2370 2019-12-31 21:31:53 +00:00
Wuzzy
2ef04cc308 Fix item eat sound not played if last item (#9239) 2019-12-31 21:31:53 +00:00
rubenwardy
7a0884e2cd Fix spaces breaking formspec_version[] tag 2019-12-31 21:31:53 +00:00
ANAND
fa858530cc Use a safer implementation of gsub in core.chat_format_message (#9133)
This search-and-replace implementation does not use Lua pattern-matching
2019-12-31 21:31:53 +00:00
sfan5
1c61fe5ed9 Rework packet receiving in ServerThread
Notably it tries to receive all queued packets
between server steps, not just one.
2019-12-31 21:31:53 +00:00
Dmitry Marakasov
57409ef382 Fix build issue due to conflicting s64 type definitions (#9064)
See comment in irrlichttypes.h and https://sourceforge.net/p/irrlicht/bugs/433/
2019-12-31 21:31:52 +00:00
SmallJoker
06ba826803 Formspecs: Reset version number on rebuild 2019-12-26 17:24:27 +00:00
rubenwardy
6e6ef9489f Continue with 5.1.1-dev 2019-12-26 17:24:23 +00:00
Loic Blot
45bbe39d1f Add arm64-v8a but it's not sufficient for 64bit build 2019-11-09 12:50:53 +01:00
Loic Blot
6c9fc13083 Bump to version code 25 2019-11-09 11:37:46 +01:00
MoNTE48
4c8a642388 Android: build fixes & compat fixes 2019-11-09 11:23:31 +01:00
sfan5
6208c9d64f Merge remote-tracking branch 'origin/stable-5' into HEAD 2019-10-12 15:59:36 +02:00
sfan5
76325d0ba9 Bump version to 5.0.1 2019-03-31 22:57:45 +02:00
rubenwardy
cf1802a6de Prevent multi-line chat messages server-side (#8420) 2019-03-28 21:49:03 +00:00
sfan5
538a7b12bd Fix texture rotation for wallmounted nodeboxes
fixes #8358
2019-03-19 22:37:11 +01:00
sfan5
57e0f52aaa httpfetch: Disable IPv6 here too if requested by settings (#8399) 2019-03-19 02:18:34 +00:00
Paramat
1ae0335b62 num_emerge_threads: Initialise value to cope with setting syntax error (#8396) 2019-03-19 02:18:26 +00:00
paramat
ca1bff6b66 num_emerge_threads: Fix documentation of automatic selection 2019-03-19 02:18:17 +00:00
Paramat
10cc62d2ca num_emerge_threads: Warn of crashes when > 1 (#8357) 2019-03-14 17:58:19 +00:00
rubenwardy
dd451a8a00 Fix cast from const by accessing string data directly (#8354)
Fixes #8327
2019-03-12 20:25:55 +00:00
rubenwardy
444ec1e412 HPChange Reason: Fix push after free, and type being overwritten (#8359)
* HPChange Reason: Fix push after free, and type being overwritten

Fixes #8227 and #8344
2019-03-12 20:25:48 +00:00
Paramat
82739f4d7d Change 'num_emerge_threads' default to 1 (#8303) 2019-03-11 22:07:19 +00:00
sofar
19825d853e getS16NoEx() returns true unless syntactical error in conf. (#8304)
The getS16NoEx() handler will return true unless there is a
`[num_emerge_threads]` line in the `minetest.conf` at which
point the excption handler part is reached. Due to the fact that
`defaultsettings.cpp` has a default value set for this setting,
that never will happen.

Because of this, the code will never check the number of threads on
the system, and keep `nthreads = 0`. If that happens, the value is
changed to `1` and only 1 emerge thread will be used.

The default should be set to `1` instead, due to the potential unsafe
consequences for the standard sqlite map files, but that should be a
separate commit that also adds documentation for that setting. This
commit focuses on removing this `hiding` bug instead.
2019-03-11 22:07:19 +00:00
rubenwardy
d8ece2e3e9 Fix serialization of std::time_t by casting to u64 first (#8353)
Fixes #8332
2019-03-11 22:07:19 +00:00
Paramat
bf4deb0ce6 Confirm registration GUI: Remove positional strings to fix Windows bug (#8258)
Positional strings don't work on some Windows builds.
Remove server address string, leave player name string present.
2019-03-11 22:07:19 +00:00
rubenwardy
da4739a26c Fix detach inventory serialisation (#8331) 2019-03-11 22:07:19 +00:00
rubenwardy
fc24bf0915 Fix incorrect string length check after cast 2019-03-11 22:07:19 +00:00
rubenwardy
9329b99cba Continue with 5.0.1-dev 2019-03-11 22:07:12 +00:00
978 changed files with 107529 additions and 219333 deletions

View File

@@ -1,4 +0,0 @@
./cmake-build-*
./build/*
./cache/*
Dockerfile

View File

@@ -1,9 +0,0 @@
[*]
end_of_line = lf
[*.{cpp,h,lua,txt,glsl,md,c,cmake,java,gradle}]
charset = utf8
indent_size = 4
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true

View File

@@ -10,31 +10,13 @@ Contributions are welcome! Here's how you can help:
## Code
1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository and
[clone](https://help.github.com/articles/cloning-a-repository/) your fork.
1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository and [clone](https://help.github.com/articles/cloning-a-repository/) your fork.
2. Before you start coding, consider opening an
[issue at Github](https://github.com/minetest/minetest/issues) to discuss the
suitability and implementation of your intended contribution with the core
developers.
Any Pull Request that isn't a bug fix and isn't covered by
[the roadmap](../doc/direction.md) will be closed within a week unless it
receives a concept approval from a Core Developer. For this reason, it is
recommended that you open an issue for any such pull requests before doing
the work, to avoid disappointment.
You may also benefit from discussing on our IRC development channel
[#minetest-dev](http://www.minetest.net/irc/). Note that a proper IRC client
is required to speak on this channel.
2. Before you start coding, consider opening an [issue at Github](https://github.com/minetest/minetest/issues) to discuss the suitability and implementation of your intended contribution with the core developers. If you are planning to start some very significant coding, you would benefit from first discussing on our IRC development channel [#minetest-dev](http://www.minetest.net/irc/). Note that a proper IRC client is required to speak on this channel.
3. Start coding!
- Refer to the
[Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt),
[Developer Wiki](http://dev.minetest.net/Main_Page) and other
[documentation](https://github.com/minetest/minetest/tree/master/doc).
- Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and
[Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
- Refer to the [Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt), [Developer Wiki](http://dev.minetest.net/Main_Page) and other [documentation](https://github.com/minetest/minetest/tree/master/doc).
- Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and [Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
- Check your code works as expected and document any changes to the Lua API.
4. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch)
@@ -51,115 +33,67 @@ Contributions are welcome! Here's how you can help:
5. Once you are happy with your changes, submit a pull request.
- Open the [pull-request form](https://github.com/minetest/minetest/pull/new/master).
- Add a description explaining what you've done (or if it's a
work-in-progress - what you need to do).
- Make sure to fill out the pull request template.
- Add a description explaining what you've done (or if it's a work-in-progress - what you need to do).
### A pull-request is considered merge-able when:
1. It follows [the roadmap](../doc/direction.md) in some way and fits the whole
picture of the project.
1. It follows the roadmap in some way and fits the whole picture of the project: [roadmap introduction](http://c55.me/blog/?p=1491), [roadmap continued](https://forum.minetest.net/viewtopic.php?t=9177)
2. It works.
3. It follows the code style for
[C/C++](http://dev.minetest.net/Code_style_guidelines) or
[Lua](http://dev.minetest.net/Lua_code_style_guidelines).
4. The code's interfaces are well designed, regardless of other aspects that
might need more work in the future.
3. It follows the code style for [C/C++](http://dev.minetest.net/Code_style_guidelines) or [Lua](http://dev.minetest.net/Lua_code_style_guidelines).
4. The code's interfaces are well designed, regardless of other aspects that might need more work in the future.
5. It uses protocols and formats which include the required compatibility.
### Important note about automated GitHub checks
When you submit a pull request, GitHub automatically runs checks on the Minetest
Engine combined with your changes. One of these checks is called 'cpp lint /
clang format', which checks code formatting. Because formatting for readability
requires human judgement this check often fails and often makes unsuitable
formatting requests which make code readability worse.
When you submit a pull request, GitHub automatically runs checks on the Minetest Engine combined with your changes. One of these checks is called 'cpp lint / clang format', which checks code formatting. Because formatting for readability requires human judgement this check often fails and often makes unsuitable formatting requests which make code readability worse.
If this check fails, look at the details to check for any clear mistakes and
correct those. However, you should not apply everything ClangFormat requests.
Ignore requests that make code readability worse and any other clearly
unsuitable requests. Discuss in the pull request with a core developer about how
to progress.
If this check fails, look at the details to check for any clear mistakes and correct those. However, you should not apply everything ClangFormat requests. Ignore requests that make code readability worse and any other clearly unsuitable requests. Discuss in the pull request with a core developer about how to progress.
## Issues
If you experience an issue, we would like to know the details - especially when
a stable release is on the way.
If you experience an issue, we would like to know the details - especially when a stable release is on the way.
1. Do a quick search on GitHub to check if the issue has already been reported.
2. Is it an issue with the Minetest *engine*? If not, report it
[elsewhere](http://www.minetest.net/development/#reporting-issues).
3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe
the issue you are having - you could include:
2. Is it an issue with the Minetest *engine*? If not, report it [elsewhere](http://www.minetest.net/development/#reporting-issues).
3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe the issue you are having - you could include:
- Error logs (check the bottom of the `debug.txt` file).
- Screenshots.
- Ways you have tried to solve the issue, and whether they worked or not.
- Your Minetest version and the content (games, mods or texture packs) you have installed.
- Your platform (e.g. Windows 10 or Ubuntu 15.04 x64).
After reporting you should aim to answer questions or clarifications as this
helps pinpoint the cause of the issue (if you don't do this your issue may be
closed after 1 month).
After reporting you should aim to answer questions or clarifications as this helps pinpoint the cause of the issue (if you don't do this your issue may be closed after 1 month).
## Feature requests
Feature requests are welcome but take a moment to see if your idea follows
[the roadmap](../doc/direction.md) in some way and fits the whole picture of
the project. You should provide a clear explanation with as much detail as
possible.
Feature requests are welcome but take a moment to see if your idea follows the roadmap in some way and fits the whole picture of the project: [roadmap introduction](http://c55.me/blog/?p=1491), [roadmap continued](https://forum.minetest.net/viewtopic.php?t=9177). You should provide a clear explanation with as much detail as possible.
## Translations
The core translations of Minetest are performed using Weblate. You can access
the project page with a list of current languages
[here](https://hosted.weblate.org/projects/minetest/minetest/).
Builtin (the component which contains things like server messages, chat command
descriptions, privilege descriptions) is translated separately; it needs to be
translated by editing a `.tr` text file. See
[Translation](https://dev.minetest.net/Translation) for more information.
Translations of Minetest are performed using Weblate. You can access the project page with a list of current languages [here](https://hosted.weblate.org/projects/minetest/minetest/).
## Donations
If you'd like to monetarily support Minetest development, you can find donation
methods on [our website](http://www.minetest.net/development/#donate).
If you'd like to monetarily support Minetest development, you can find donation methods on [our website](http://www.minetest.net/development/#donate).
# Maintaining
* This is a concise version of the
[Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
*This is a concise version of the [Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
These notes are for those who have push access Minetest (core developers / maintainers).
- See the [project organisation](http://dev.minetest.net/Organisation) for the people involved.
## Concept approvals and roadmaps
If a Pull Request is not a bug fix:
* If it matches a goal in [the roadmap](../doc/direction.md), then the PR should
be labeled as "Roadmap" and the goal stated by number in the description.
* If it doesn't match a goal, then it needs to receive a concept approval within
a week of being opened to remain open. This 1 week deadline does not apply to
PRs opened before the roadmap was adopted; instead, they may remain open or be
closed as needed. Use the "Concept Approved" label. Issues can be marked as
"Concept Approved" to give preapproval to future PRs.
## Reviewing pull requests
Pull requests should be reviewed and, if appropriate, checked if they achieve
their intended purpose. You can show that you are in the process of, or will
review the pull request by commenting *"Looks good"* or something similar.
Pull requests should be reviewed and, if appropriate, checked if they achieve their intended purpose. You can show that you are in the process of, or will review the pull request by commenting *"Looks good"* or something similar.
**If the pull-request is not [merge-able](#a-pull-request-is-considered-merge-able-when):**
Submit a comment explaining to the author what they need to change to make the
pull-request merge-able.
Submit a comment explaining to the author what they need to change to make the pull-request merge-able.
- If the author comments or makes changes to the pull-request, it can be
reviewed again.
- If no response is made from the author within 1 month (when improvements are
suggested or a question is asked), it can be closed.
- If the author comments or makes changes to the pull-request, it can be reviewed again.
- If no response is made from the author within 1 month (when improvements are suggested or a question is asked), it can be closed.
**If the pull-request is [merge-able](#a-pull-request-is-considered-merge-able-when):**

View File

@@ -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
<!-- Explain how the problem has happened, providing a minimal test (i.e. a code snippet reduced to the bone) where possible -->
<!-- For bug reports or build issues, explain how the problem happened -->

View File

@@ -3,7 +3,6 @@ Add compact, short information about your PR for easier understanding:
- Goal of the PR
- How does the PR work?
- Does it resolve any reported issue?
- Does this relate to a goal in [the roadmap](../doc/direction.md)?
- If not a bug fix, why is this PR needed? What usecases does it solve?
## To do

View File

@@ -21,32 +21,22 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gettext openjdk-11-jdk-headless
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: cd android; ./gradlew assemblerelease
- name: Save armeabi artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
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@v3
uses: actions/upload-artifact@v2
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

View File

@@ -13,8 +13,6 @@ on:
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
- 'Dockerfile'
- '.dockerignore'
pull_request:
paths:
- 'lib/**.[ch]'
@@ -26,66 +24,59 @@ on:
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
- 'Dockerfile'
- '.dockerignore'
env:
MINETEST_POSTGRESQL_CONNECT_STRING: 'host=localhost user=minetest password=minetest dbname=minetest'
jobs:
# Older gcc version (should be close to our minimum supported version)
gcc_5:
# This is our minor gcc compiler
gcc_6:
runs-on: ubuntu-18.04
if: "false" # FIXME
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps g++-5
install_linux_deps g++-6
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-5
CXX: g++-5
CC: gcc-6
CXX: g++-6
- name: Test
run: |
./bin/minetest --run-unittests
# Current gcc version
gcc_12:
runs-on: ubuntu-22.04
# This is the current gcc compiler (available in bionic)
gcc_8:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps g++-12 libluajit-5.1-dev
install_linux_deps g++-8
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-12
CXX: g++-12
CC: gcc-8
CXX: g++-8
- name: Test
run: |
./bin/minetest --run-unittests
# Older clang version (should be close to our minimum supported version)
# This is our minor clang compiler
clang_3_9:
runs-on: ubuntu-18.04
if: "false" # FIXME
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-3.9 valgrind
install_linux_deps clang-3.9
- name: Build
run: |
@@ -94,7 +85,29 @@ jobs:
CC: clang-3.9
CXX: clang++-3.9
- name: Unittest
- name: Test
run: |
./bin/minetest --run-unittests
# This is the current clang version
clang_9:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-9 valgrind libluajit-5.1-dev
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-9
CXX: clang++-9
CMAKE_FLAGS: "-DREQUIRE_LUAJIT=1"
- name: Test
run: |
./bin/minetest --run-unittests
@@ -102,37 +115,12 @@ jobs:
run: |
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
# Current clang version
clang_14:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-14 gdb
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-14
CXX: clang++-14
- name: Test
run: |
./bin/minetest --run-unittests
- name: Integration test + devtest
run: |
./util/test_multiplayer.sh
# Build with prometheus-cpp (server-only)
clang_9_prometheus:
name: "clang_9 (PROMETHEUS=1)"
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
@@ -154,25 +142,47 @@ jobs:
run: |
./bin/minetestserver --run-unittests
# Build without freetype (client-only)
clang_9_no_freetype:
name: "clang_9 (FREETYPE=0)"
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-9
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-9
CXX: clang++-9
CMAKE_FLAGS: "-DENABLE_FREETYPE=0 -DBUILD_SERVER=0"
- name: Test
run: |
./bin/minetest --run-unittests
docker:
name: "Docker image"
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Build docker image
run: |
docker build . -t minetest:latest
docker run --rm minetest:latest /usr/local/bin/minetestserver --version
docker build .
win32:
name: "MinGW cross-compiler (32-bit)"
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install compiler
run: |
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 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 tar -xaf mingw.tar.xz -C /usr
- name: Build
@@ -184,13 +194,13 @@ jobs:
win64:
name: "MinGW cross-compiler (64-bit)"
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install compiler
run: |
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 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 tar -xaf mingw.tar.xz -C /usr
- name: Build
@@ -202,11 +212,11 @@ jobs:
msvc:
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
runs-on: windows-2019
runs-on: windows-2019
env:
VCPKG_VERSION: 5cf60186a241e84e8232641ee973395d4fde90e1
# 2022.02
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0
# 2020.11
vcpkg_packages: irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit
strategy:
fail-fast: false
matrix:
@@ -227,15 +237,11 @@ jobs:
# Enable it, when working on the installer.
steps:
- 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: Checkout
uses: actions/checkout@v2
- name: Restore from cache and run vcpkg
uses: lukka/run-vcpkg@v7
uses: lukka/run-vcpkg@v5
with:
vcpkgArguments: ${{env.vcpkg_packages}}
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
@@ -243,7 +249,7 @@ jobs:
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
- name: Minetest CMake
- name: CMake
run: |
cmake ${{matrix.config.generator}} `
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
@@ -251,7 +257,7 @@ jobs:
-DENABLE_POSTGRESQL=OFF `
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
- name: Build Minetest
- name: Build
run: cmake --build . --config Release
- name: CPack
@@ -270,7 +276,7 @@ jobs:
- name: Package Clean
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v1
with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
path: .\Package\

View File

@@ -24,31 +24,30 @@ on:
- '.github/workflows/**.yml'
jobs:
clang_format:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install clang-format
run: |
sudo apt-get install clang-format-9 -qyy
# clang_format:
# runs-on: ubuntu-20.04
# steps:
# - uses: actions/checkout@v3
# - name: Install clang-format
# run: |
# sudo apt-get update
# sudo apt-get install -y clang-format-9
#
# - name: Run clang-format
# run: |
# source ./util/ci/clang-format.sh
# check_format
# env:
# CLANG_FORMAT: clang-format-9
- name: Run clang-format
run: |
source ./util/ci/lint.sh
perform_lint
env:
CLANG_FORMAT: clang-format-9
clang_tidy:
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt-get install clang-tidy-9 -qyy
source ./util/ci/common.sh
install_linux_deps clang-tidy-9
install_linux_deps
- name: Run clang-tidy
run: |

View File

@@ -1,72 +0,0 @@
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 Normal file
View File

@@ -0,0 +1,32 @@
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

View File

@@ -1,68 +0,0 @@
name: macos
# build on c/cpp changes or workflow changes
on:
push:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- '.github/workflows/macos.yml'
pull_request:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- '.github/workflows/macos.yml'
env:
MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git
MINETEST_GAME_BRANCH: master
MINETEST_GAME_NAME: minetest_game
jobs:
build:
runs-on: macos-11
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_macos_deps
- name: Build
run: |
git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME
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_GETTEXT=TRUE \
-DINSTALL_DEVTEST=TRUE
make -j2
make install
- name: Test
run: |
./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests
# 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/*.zip

26
.gitignore vendored
View File

@@ -30,8 +30,6 @@ gtags.files
build/.cmake/
# Gradle
.gradle
# Clang
.cache
## Files related to Minetest development cycle
/*.patch
@@ -43,17 +41,17 @@ build/.cmake/
/bin/
/games/*
!/games/devtest/
/games/devtest/mods/soundstuff/sounds/gitignored_sounds/*
!/games/devtest/mods/soundstuff/sounds/gitignored_sounds/custom_sounds_here.txt
/cache
/textures/*
!/textures/base/
/screenshots
/sounds
/mods/*
!/mods/mods_here.txt
/worlds/*
!/worlds/worlds_here.txt
!/mods/minetest/
/mods/minetest/*
!/mods/minetest/mods_here.txt
/worlds
/world/
/clientmods/*
!/clientmods/preview/
/client/mod_storage/
@@ -89,8 +87,8 @@ src/test_config.h
src/cmake_config.h
src/cmake_config_githash.h
src/unittest/test_world/world.mt
games/devtest/mods/testnodes/textures/testnodes_generated_*.png
/locale/
src/lua/build/
locale/
.directory
*.cbp
*.layout
@@ -109,13 +107,3 @@ CMakeDoxy*
compile_commands.json
*.apk
*.zip
# Visual Studio
*.vcxproj*
*.sln
.vs/
# Optional user provided library folder
lib/irrlichtmt
# Generated mod storage database
client/mod_storage.sqlite

View File

@@ -14,14 +14,12 @@ variables:
.build_template:
stage: build
before_script:
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential gettext git cmake libpng-dev libjpeg-dev libxi-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 libluajit-5.1-dev
script:
- 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 ..
- make -j $(($(nproc) + 1))
- mkdir cmakebuild
- mkdir -p artifact/minetest/usr/
- cd cmakebuild
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DENABLE_SYSTEM_JSONCPP=TRUE -DBUILD_SERVER=TRUE ..
- make -j2
- make install
artifacts:
when: on_success
@@ -29,29 +27,172 @@ variables:
paths:
- artifact/*
.debpkg_template:
stage: package
before_script:
- apt-get update -y
- apt-get install -y git
- mkdir -p build/deb/minetest/DEBIAN/
- cp misc/debpkg-control build/deb/minetest/DEBIAN/control
- cp -a artifact/minetest/usr build/deb/minetest/
script:
- git clone $MINETEST_GAME_REPO build/deb/minetest/usr/share/minetest/games/minetest_game
- rm -rf build/deb/minetest/usr/share/minetest/games/minetest/.git
- sed -i 's/DATEPLACEHOLDER/'$(date +%y.%m.%d)'/g' build/deb/minetest/DEBIAN/control
- sed -i 's/LEVELDB_PLACEHOLDER/'$LEVELDB_PKG'/g' build/deb/minetest/DEBIAN/control
- cd build/deb/ && dpkg-deb -b minetest/ && mv minetest.deb ../../
artifacts:
expire_in: 90 day
paths:
- ./*.deb
.debpkg_install:
stage: deploy
before_script:
- apt-get update -y
script:
- apt-get install -y ./*.deb
- minetest --version
##
## Ubuntu (prerequisite for AppImage build)
## Debian
##
build:ubuntu-20.04:
# Stretch
build:debian-9:
extends: .build_template
image: debian:9
before_script:
- apt-get update -y
- apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
package:debian-9:
extends: .debpkg_template
image: debian:9
needs:
- build:debian-9
variables:
LEVELDB_PKG: libleveldb1v5
deploy:debian-9:
extends: .debpkg_install
image: debian:9
needs:
- package:debian-9
# Buster
build:debian-10:
extends: .build_template
image: debian:10
before_script:
- apt-get update -y
- apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
package:debian-10:
extends: .debpkg_template
image: debian:10
needs:
- build:debian-10
variables:
LEVELDB_PKG: libleveldb1d
deploy:debian-10:
extends: .debpkg_install
image: debian:10
needs:
- package:debian-10
##
## Ubuntu
##
# Xenial
build:ubuntu-16.04:
extends: .build_template
image: ubuntu:focal
image: ubuntu:xenial
before_script:
- apt-get update -y
- apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
package:ubuntu-16.04:
extends: .debpkg_template
image: ubuntu:xenial
needs:
- build:ubuntu-16.04
variables:
LEVELDB_PKG: libleveldb1v5
deploy:ubuntu-16.04:
extends: .debpkg_install
image: ubuntu:xenial
needs:
- package:ubuntu-16.04
# Bionic
build:ubuntu-18.04:
extends: .build_template
image: ubuntu:bionic
before_script:
- apt-get update -y
- apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
package:ubuntu-18.04:
extends: .debpkg_template
image: ubuntu:bionic
needs:
- build:ubuntu-18.04
variables:
LEVELDB_PKG: libleveldb1v5
deploy:ubuntu-18.04:
extends: .debpkg_install
image: ubuntu:bionic
needs:
- package:ubuntu-18.04
##
## Fedora
##
# Fedora 28 <-> RHEL 8
build:fedora-28:
extends: .build_template
image: fedora:28
before_script:
- dnf -y 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 irrlicht-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel
##
## MinGW for Windows
##
.generic_win_template:
image: ubuntu:focal
image: ubuntu:bionic
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}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
- apt-get update -y
- 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
- tar -xaf mingw.tar.xz -C /usr
.build_win_template:
extends: .generic_win_template
stage: build
artifacts:
expire_in: 1h
paths:
- build/minetest/_build/*
.package_win_template:
extends: .generic_win_template
stage: package
script:
- unzip build/minetest/_build/minetest-*.zip
- cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libgcc*.dll minetest-*-win*/bin/
- cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libstdc++*.dll minetest-*-win*/bin/
- cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libwinpthread*.dll minetest-*-win*/bin/
artifacts:
expire_in: 90 day
paths:
@@ -60,16 +201,29 @@ build:ubuntu-20.04:
build:win32:
extends: .build_win_template
script:
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build
- unzip -q build/build/*.zip
- ./util/buildbot/buildwin32.sh build
variables:
WIN_ARCH: "i686"
package:win32:
extends: .package_win_template
needs:
- build:win32
variables:
WIN_ARCH: "i686"
build:win64:
extends: .build_win_template
script:
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build
- unzip -q build/build/*.zip
- ./util/buildbot/buildwin64.sh build
variables:
WIN_ARCH: "x86_64"
package:win64:
extends: .package_win_template
needs:
- build:win64
variables:
WIN_ARCH: "x86_64"
@@ -85,7 +239,10 @@ package:docker:
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
script:
- ./util/ci/docker.sh
- docker build . -t ${CONTAINER_IMAGE}/server:$CI_COMMIT_SHA -t ${CONTAINER_IMAGE}/server:$CI_COMMIT_REF_NAME -t ${CONTAINER_IMAGE}/server:latest
- docker push ${CONTAINER_IMAGE}/server:$CI_COMMIT_SHA
- docker push ${CONTAINER_IMAGE}/server:$CI_COMMIT_REF_NAME
- docker push ${CONTAINER_IMAGE}/server:latest
##
## Gitlab Pages (Lua API documentation)
@@ -95,7 +252,9 @@ pages:
stage: deploy
image: python:3.8
before_script:
- pip install -U -r doc/mkdocs/requirements.txt
- pip install git+https://github.com/Python-Markdown/markdown.git
- pip install git+https://github.com/mkdocs/mkdocs.git
- pip install pygments
script:
- cd doc/mkdocs && ./build.sh
artifacts:
@@ -112,21 +271,22 @@ package:appimage-client:
stage: package
image: appimagecrafters/appimage-builder
needs:
- build:ubuntu-20.04
- build:ubuntu-18.04
before_script:
- apt-get update
- apt-get install -y git
- apt-get update -y
- apt-get install -y git wget
# Collect files
- mkdir AppDir
- cp -a artifact/minetest/usr/ AppDir/usr/
- rm AppDir/usr/bin/minetestserver
- cp -a clientmods AppDir/usr/share/minetest
script:
- git clone $MINETEST_GAME_REPO AppDir/usr/share/minetest/games/minetest_game
- rm -rf AppDir/usr/share/minetest/games/minetest_game/.git
- rm -rf AppDir/usr/share/minetest/games/minetest/.git
- export VERSION=$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
# Remove PrefersNonDefaultGPU property due to validation errors
- sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/net.minetest.minetest.desktop
script:
- export VERSION=$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
- appimage-builder --skip-test --recipe misc/AppImageBuilder.yml
- appimage-builder --skip-test
artifacts:
expire_in: 90 day
paths:

View File

@@ -20,7 +20,7 @@ read_globals = {
string = {fields = {"split", "trim"}},
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
math = {fields = {"hypot", "round"}},
math = {fields = {"hypot"}},
}
globals = {

View File

@@ -37,7 +37,7 @@ numzero <numzer0@yandex.ru> <silverunicorn2011@yandex.ru>
Jean-Patrick Guerrero <kilbith@users.noreply.github.com>
Jean-Patrick Guerrero <kilbith@users.noreply.github.com> <jeanpatrick.guerrero@gmail.com>
HybridDog <3192173+HybridDog@users.noreply.github.com> <ovvv@web.de>
srifqi <muhammadrifqipriyosusanto@gmail.com>
srfqi <muhammadrifqipriyosusanto@gmail.com>
Dániel Juhász <juhdanad@gmail.com>
rubenwardy <rw@rubenwardy.com>
rubenwardy <rw@rubenwardy.com> <rubenwardy@gmail.com>
@@ -48,17 +48,15 @@ ClobberXD <ClobberXD@gmail.com> <ClobberXD@protonmail.com>
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>
DS <ds.desour@proton.me>
DS <ds.desour@proton.me> <vorunbekannt75@web.de>
Nathanaëlle Courant <Ekdohibs@users.noreply.github.com> <nathanael.courant@laposte.net>
Desour <vorunbekannt75@web.de>
Nathanaël 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 <Wuzzy@disroot.org> <wuzzy2@mail.ru>
Wuzzy <Wuzzy@disroot.org> <Wuzzy2@mail.ru>
Wuzzy <Wuzzy@disroot.org> <almikes@aol.com>
Wuzzy <wuzzy2@mail.ru> <Wuzzy2@mail.ru>
Wuzzy <wuzzy2@mail.ru> <almikes@aol.com>
Jordach <jordach.snelling@gmail.com>
MoNTE48 <MoNTE48@mail.ua>
v-rob <robinsonvincent89@gmail.com>
@@ -67,8 +65,3 @@ 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>
Lars Müller <appgurulars@gmx.de>
Lars Müller <appgurulars@gmx.de> <34514239+appgurueu@users.noreply.github.com>
ROllerozxa <rollerozxa@voxelmanip.se>
ROllerozxa <rollerozxa@voxelmanip.se> <temporaryemail4meh+github@gmail.com>

View File

@@ -4,7 +4,7 @@ AppDir:
path: ./AppDir
app_info:
id: net.minetest.minetest
id: minetest
name: Minetest
icon: minetest
version: !ENV ${VERSION}
@@ -17,35 +17,33 @@ AppDir:
apt:
arch: amd64
sources:
- sourceline: deb http://archive.ubuntu.com/ubuntu/ focal main universe
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic main universe
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32'
- sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates main universe
- sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-backports main universe
- sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-security main universe
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates main universe
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-backports main universe
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security main universe
include:
- libc6
- libcrypt1
- libirrlicht1.8
- libxxf86vm1
- libgl1-mesa-glx
- libsqlite3-0
- libogg0
- libvorbis0a
- libopenal1
- libcurl3-gnutls
- libfreetype6
- libgl1
- libjpeg-turbo8
- libjsoncpp1
- libleveldb1d
- libluajit-5.1-2
- libopenal1
- libpng16-16
- libsqlite3-0
- libstdc++6
- libvorbisfile3
- libx11-6
- libxi6
- zlib1g
- libgmp10
- libjsoncpp1
files:
exclude:
- usr/share/man
- usr/share/doc
- usr/share/doc/*/README.*
- usr/share/doc/*/changelog.*
- usr/share/doc/*/NEWS.*
- usr/share/doc/*/TODO.*
AppImage:
update-information: None

View File

@@ -1,25 +1,19 @@
cmake_minimum_required(VERSION 3.5)
# Set policies up to 3.9 since we want to enable the IPO option
if(${CMAKE_VERSION} VERSION_LESS 3.9)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.9)
endif()
cmake_policy(SET CMP0025 OLD)
# This can be read from ${PROJECT_NAME} after project() is called
project(minetest)
set(PROJECT_NAME_CAPITALIZED "Minetest")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(GCC_MINIMUM_VERSION "5.1")
set(CLANG_MINIMUM_VERSION "3.5")
set(CMAKE_CXX_STANDARD 11)
set(GCC_MINIMUM_VERSION "4.8")
set(CLANG_MINIMUM_VERSION "3.4")
# You should not need to edit these manually, use util/bump_version.sh
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
set(VERSION_MAJOR 5)
set(VERSION_MINOR 7)
set(VERSION_PATCH 0)
set(VERSION_MINOR 4)
set(VERSION_PATCH 2)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
# Change to false for releases
@@ -27,7 +21,7 @@ set(DEVELOPMENT_BUILD FALSE)
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
if(VERSION_EXTRA)
set(VERSION_STRING "${VERSION_STRING}-${VERSION_EXTRA}")
set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA})
elseif(DEVELOPMENT_BUILD)
set(VERSION_STRING "${VERSION_STRING}-dev")
endif()
@@ -52,7 +46,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")
@@ -61,92 +55,13 @@ 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/")
set(IRRLICHTMT_BUILD_DIR "" CACHE PATH "Path to IrrlichtMt build directory.")
if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
find_package(IrrlichtMt QUIET
PATHS "${IRRLICHTMT_BUILD_DIR}"
NO_DEFAULT_PATH
)
# This is done here so that relative search paths are more reasonable
find_package(Irrlicht)
if(NOT TARGET IrrlichtMt::IrrlichtMt)
# find_package() searches certain subdirectories. ${PATH}/cmake is not
# the only one, but it is the one where IrrlichtMt is supposed to export
# IrrlichtMtConfig.cmake
message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
endif()
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
if(BUILD_CLIENT)
# tell IrrlichtMt to create a static library
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared library" FORCE)
add_subdirectory(lib/irrlichtmt EXCLUDE_FROM_ALL)
unset(BUILD_SHARED_LIBS CACHE)
if(NOT TARGET IrrlichtMt)
message(FATAL_ERROR "IrrlichtMt project is missing a CMake target?!")
endif()
else()
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt/include")
endif()
else()
find_package(IrrlichtMt QUIET)
if(NOT TARGET IrrlichtMt::IrrlichtMt)
string(CONCAT explanation_msg
"The Minetest team has forked Irrlicht to make their own customizations. "
"It can be found here: https://github.com/minetest/irrlicht\n"
"For example use: git clone --depth=1 https://github.com/minetest/irrlicht lib/irrlichtmt\n")
if(BUILD_CLIENT)
message(FATAL_ERROR "IrrlichtMt is required to build the client, but it was not found.\n${explanation_msg}")
endif()
include(MinetestFindIrrlichtHeaders)
if(NOT IRRLICHT_INCLUDE_DIR)
message(FATAL_ERROR "IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
endif()
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
INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}")
endif()
endif()
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.0mt10)
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()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. "
"Version ${GCC_MINIMUM_VERSION} or higher is required.")
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient clang version, found ${CMAKE_CXX_COMPILER_VERSION}. "
"Version ${CLANG_MINIMUM_VERSION} or higher is required.")
endif()
endif()
# Installation
@@ -176,16 +91,15 @@ elseif(UNIX) # Linux, BSD etc
set(ICONDIR "unix/icons")
set(LOCALEDIR "locale")
else()
include(GNUInstallDirs)
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}")
set(BINDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
set(DOCDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DOCDIR}")
set(MANDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}")
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}")
set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
set(EXAMPLE_CONF_DIR ${DOCDIR})
set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications")
set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
set(ICONDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons")
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}")
set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications")
set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/metainfo")
set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons")
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale")
endif()
endif()
@@ -249,17 +163,13 @@ endif()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/minetest_game" DESTINATION "${SHAREDIR}/games/"
COMPONENT "SUBGAME_MINETEST_GAME" OPTIONAL PATTERN ".git*" EXCLUDE )
set(INSTALL_DEVTEST FALSE CACHE BOOL "Install Development Test")
if(INSTALL_DEVTEST)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/devtest" DESTINATION "${SHAREDIR}/games/"
PATTERN ".git*" EXCLUDE )
endif()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/devtest" DESTINATION "${SHAREDIR}/games/"
COMPONENT "SUBGAME_MINIMAL" OPTIONAL PATTERN ".git*" EXCLUDE )
if(BUILD_CLIENT)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/shaders" DESTINATION "${SHAREDIR}/client")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/textures/base/pack" DESTINATION "${SHAREDIR}/textures/base")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/fonts" DESTINATION "${SHAREDIR}")
if(RUN_IN_PLACE)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/clientmods" DESTINATION "${SHAREDIR}")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/serverlist" DESTINATION "${SHAREDIR}/client")
@@ -293,18 +203,30 @@ endif()
find_package(GMP REQUIRED)
find_package(Json REQUIRED)
find_package(Lua REQUIRED)
if(NOT USE_LUAJIT)
add_subdirectory(lib/bitop)
# JsonCPP doesn't compile well on GCC 4.8
if(NOT ENABLE_SYSTEM_JSONCPP)
set(GCC_MINIMUM_VERSION "4.9")
endif()
if(BUILD_BENCHMARKS)
add_subdirectory(lib/catch2)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. "
"Version ${GCC_MINIMUM_VERSION} or higher is required.")
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient clang version, found ${CMAKE_CXX_COMPILER_VERSION}. "
"Version ${CLANG_MINIMUM_VERSION} or higher is required.")
endif()
endif()
# Subdirectories
# Be sure to add all relevant definitions above this
add_subdirectory(src)
# CPack
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A free open-source voxel game engine with easy modding and game creation.")
@@ -327,12 +249,19 @@ cpack_add_component(SUBGAME_MINETEST_GAME
GROUP "Games"
)
cpack_add_component(SUBGAME_MINIMAL
DISPLAY_NAME "Development Test"
DESCRIPTION "A basic testing environment used for engine development and sometimes for testing mods."
DISABLED #DISABLED does not mean it is disabled, and is just not selected by default.
GROUP "Games"
)
cpack_add_component_group(Subgames
DESCRIPTION "Games for the Minetest engine."
)
if(WIN32)
# Include all dynamically linked runtime libraries such as MSVCRxxx.dll
# Include all dynamically linked runtime libaries such as MSVCRxxx.dll
include(InstallRequiredSystemLibraries)
if(RUN_IN_PLACE)

View File

@@ -1,502 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
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
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
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
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
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
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
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.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
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
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
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
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
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
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
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
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
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
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library 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 library 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 library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@@ -1,39 +1,6 @@
ARG DOCKER_IMAGE=alpine:3.16
FROM $DOCKER_IMAGE AS builder
FROM alpine:3.11
ENV MINETEST_GAME_VERSION master
ENV IRRLICHT_VERSION master
ENV SPATIALINDEX_VERSION 1.9.3
ENV LUAJIT_VERSION v2.1
RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \
sqlite-dev postgresql-dev hiredis-dev leveldb-dev \
gmp-dev jsoncpp-dev ninja ca-certificates
WORKDIR /usr/src/
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 && \
cmake --build build && \
cmake --install build && \
cd /usr/src/ && \
git clone --recursive https://github.com/libspatialindex/libspatialindex -b ${SPATIALINDEX_VERSION} && \
cd libspatialindex && \
cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local && \
cmake --build build && \
cmake --install build && \
cd /usr/src/ && \
git clone --recursive https://luajit.org/git/luajit.git -b ${LUAJIT_VERSION} && \
cd luajit && \
make && make install && \
cd /usr/src/ && \
git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
cp -r irrlicht/include /usr/include/irrlichtmt
COPY .git /usr/src/minetest/.git
COPY CMakeLists.txt /usr/src/minetest/CMakeLists.txt
@@ -50,38 +17,52 @@ COPY src /usr/src/minetest/src
COPY textures /usr/src/minetest/textures
WORKDIR /usr/src/minetest
RUN git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
rm -fr ./games/minetest_game/.git && \
cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SERVER=TRUE \
-DENABLE_PROMETHEUS=TRUE \
-DBUILD_UNITTESTS=FALSE \
-DBUILD_CLIENT=FALSE \
-GNinja && \
cmake --build build && \
cmake --install build
ARG DOCKER_IMAGE=alpine:3.16
FROM $DOCKER_IMAGE AS runtime
RUN apk add --no-cache git build-base irrlicht-dev cmake bzip2-dev 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 postgresql-dev luajit-dev ca-certificates && \
git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
rm -fr ./games/minetest_game/.git
RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \
sqlite-libs postgresql hiredis leveldb && \
WORKDIR /usr/src/
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
mkdir prometheus-cpp/build && \
cd prometheus-cpp/build && \
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_TESTING=0 && \
make -j2 && \
make install
WORKDIR /usr/src/minetest
RUN mkdir build && \
cd build && \
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SERVER=TRUE \
-DENABLE_PROMETHEUS=TRUE \
-DBUILD_UNITTESTS=FALSE \
-DBUILD_CLIENT=FALSE && \
make -j2 && \
make install
FROM alpine:3.11
RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq luajit && \
adduser -D minetest --uid 30000 -h /var/lib/minetest && \
chown -R minetest:minetest /var/lib/minetest
WORKDIR /var/lib/minetest
COPY --from=builder /usr/local/share/minetest /usr/local/share/minetest
COPY --from=builder /usr/local/bin/minetestserver /usr/local/bin/minetestserver
COPY --from=builder /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/minetest.conf
COPY --from=builder /usr/local/lib/libspatialindex* /usr/local/lib/
COPY --from=builder /usr/local/lib/libluajit* /usr/local/lib/
COPY --from=0 /usr/local/share/minetest /usr/local/share/minetest
COPY --from=0 /usr/local/bin/minetestserver /usr/local/bin/minetestserver
COPY --from=0 /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/minetest.conf
USER minetest:minetest
EXPOSE 30000/udp 30000/tcp
VOLUME /var/lib/minetest/ /etc/minetest/
ENTRYPOINT ["/usr/local/bin/minetestserver"]
CMD ["--config", "/etc/minetest/minetest.conf"]
CMD ["/usr/local/bin/minetestserver", "--config", "/etc/minetest/minetest.conf"]

View File

@@ -14,12 +14,6 @@ https://www.apache.org/licenses/LICENSE-2.0.html
Textures by Zughy are under CC BY-SA 4.0
https://creativecommons.org/licenses/by-sa/4.0/
Media files by DS are under CC BY-SA 4.0
https://creativecommons.org/licenses/by-sa/4.0/
textures/base/pack/server_public.png is under CC-BY 4.0, taken from Twitter's Twemoji set
https://creativecommons.org/licenses/by/4.0/
Authors of media files
-----------------------
Everything not listed in here:
@@ -45,10 +39,10 @@ erlehmann:
misc/minetest.svg
textures/base/pack/logo.png
JRottm:
JRottm
textures/base/pack/player_marker.png
srifqi:
srifqi
textures/base/pack/chat_hide_btn.png
textures/base/pack/chat_show_btn.png
textures/base/pack/joystick_bg.png
@@ -58,28 +52,12 @@ srifqi:
Zughy:
textures/base/pack/cdb_add.png
textures/base/pack/cdb_clear.png
textures/base/pack/cdb_downloading.png
textures/base/pack/cdb_queued.png
textures/base/pack/cdb_update.png
textures/base/pack/cdb_viewonline.png
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/cdb_clear.png
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
DS:
games/devtest/mods/soundstuff/textures/soundstuff_jukebox.png
games/devtest/mods/testtools/textures/testtools_branding_iron.png
License of Minetest source code
-------------------------------
@@ -103,8 +81,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Irrlicht
---------------
This program uses IrrlichtMt, Minetest's fork of
the Irrlicht Engine. http://irrlicht.sourceforge.net/
This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/
The Irrlicht Engine License

187
README.md
View File

@@ -7,7 +7,7 @@ Minetest
Minetest is a free open-source voxel game engine with easy modding and game creation.
Copyright (C) 2010-2022 Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2010-2020 Perttu Ahola <celeron55@gmail.com>
and contributors (see source file comments and the version control log)
In case you downloaded the source code
@@ -61,12 +61,14 @@ Some can be changed in the key config dialog in the settings tab.
| T | Chat |
| / | Command |
| Esc | Pause menu/abort/exit (pauses only singleplayer game) |
| R | Enable/disable full range view |
| + | Increase view range |
| - | Decrease view range |
| K | Enable/disable fly mode (needs fly privilege) |
| P | Enable/disable pitch move mode |
| J | Enable/disable fast mode (needs fast privilege) |
| H | Enable/disable noclip mode (needs noclip privilege) |
| E | Aux1 (Move fast in fast mode. Games may add special features) |
| E | Move fast in fast mode |
| C | Cycle through camera modes |
| V | Cycle through minimap modes |
| Shift + V | Change minimap orientation |
@@ -96,15 +98,15 @@ Where each location is on each platform:
* Windows installed:
* `bin` = `C:\Program Files\Minetest\bin (Depends on the install location)`
* `share` = `C:\Program Files\Minetest (Depends on the install location)`
* `user` = `%APPDATA%\Minetest` or `%MINETEST_USER_PATH%`
* `user` = `%APPDATA%\Minetest`
* Linux installed:
* `bin` = `/usr/bin`
* `share` = `/usr/share/minetest`
* `user` = `~/.minetest` or `$MINETEST_USER_PATH`
* `user` = `~/.minetest`
* macOS:
* `bin` = `Contents/MacOS`
* `share` = `Contents/Resources`
* `user` = `Contents/User` or `~/Library/Application Support/minetest` or `$MINETEST_USER_PATH`
* `user` = `Contents/User OR ~/Library/Application Support/minetest`
Worlds can be found as separate folders in: `user/worlds/`
@@ -130,31 +132,29 @@ Compiling
| Dependency | Version | Commentary |
|------------|---------|------------|
| GCC | 5.1+ | or Clang 3.5+ |
| CMake | 3.5+ | |
| IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht |
| Freetype | 2.0+ | |
| SQLite3 | 3+ | |
| Zstd | 1.0+ | |
| GCC | 4.9+ | Can be replaced with Clang 3.4+ |
| CMake | 2.6+ | |
| Irrlicht | 1.7.3+ | |
| SQLite3 | 3.0+ | |
| LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present |
| GMP | 5.0.0+ | Bundled mini-GMP is used if not present |
| JsonCPP | 1.0.0+ | Bundled JsonCPP is used if not present |
For Debian/Ubuntu users:
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
sudo apt install g++ make libc6-dev libirrlicht-dev cmake libbz2-dev 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
For Fedora users:
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libpng-devel libjpeg-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
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 irrlicht-devel bzip2-libs gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel doxygen spatialindex-devel bzip2-devel
For Arch users:
sudo pacman -S base-devel libcurl-gnutls cmake libxi libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm irrlicht libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses
For Alpine users:
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
sudo apk add build-base irrlicht-dev cmake bzip2-dev 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
#### Download
@@ -168,34 +168,22 @@ For Fedora users:
sudo dnf install git
For Arch users:
sudo pacman -S git
For Alpine users:
sudo apk add git
Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
git clone --depth 1 https://github.com/minetest/minetest.git
cd minetest
Download Minetest Game (otherwise only the "Development Test" game is available) using Git:
Download minetest_game (otherwise only the "Development Test" game is available) using Git:
git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
Download IrrlichtMt to `lib/irrlichtmt`, it will be used to satisfy the IrrlichtMt dependency that way:
git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
Download source, without using Git:
wget https://github.com/minetest/minetest/archive/master.tar.gz
tar xf master.tar.gz
cd minetest-master
Download Minetest Game, without using Git:
Download minetest_game, without using Git:
cd games/
wget https://github.com/minetest/minetest_game/archive/master.tar.gz
@@ -203,14 +191,6 @@ Download Minetest Game, without using Git:
mv minetest_game-master minetest_game
cd ..
Download IrrlichtMt, without using Git:
cd lib/
wget https://github.com/minetest/irrlicht/archive/master.tar.gz
tar xf master.tar.gz
mv irrlicht-master irrlichtmt
cd ..
#### Build
Build a version that runs directly from the source directory:
@@ -229,15 +209,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 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
2. `${PROJECT_SOURCE_DIR}/lib/irrlichtmt` (if existent)
3. Installation of IrrlichtMt in the system-specific library paths
4. For server builds with disabled `BUILD_CLIENT` variable, the headers from `IRRLICHT_INCLUDE_DIR` will be used.
- NOTE: Changing the IrrlichtMt build directory (includes system installs) requires regenerating the CMake cache (`rm CMakeCache.txt`)
- If you build a bare server you don't need to have Irrlicht installed.
- In that case use `-DIRRLICHT_SOURCE_DIR=/the/irrlicht/source`.
### CMake options
@@ -246,7 +219,6 @@ 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
@@ -255,8 +227,9 @@ General options and their default values:
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
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_FREETYPE=ON - Build with FreeType2; Allows using TTF fonts
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
ENABLE_GLES=OFF - Enable extra support code for OpenGL ES (requires support by IrrlichtMt)
ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by Irrlicht)
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
@@ -265,33 +238,33 @@ General options and their default values:
ENABLE_LUAJIT=ON - Build with LuaJIT (much faster than non-JIT Lua)
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
ENABLE_SYSTEM_JSONCPP=OFF - 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
INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest
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)
Library specific options:
BZIP2_INCLUDE_DIR - Linux only; directory where bzlib.h is located
BZIP2_LIBRARY - Linux only; path to libbz2.a/libbz2.so
CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
EXTRA_DLL - Only on Windows; optional paths to additional DLLs that should be packaged
FREETYPE_INCLUDE_DIR_freetype2 - Directory that contains files such as ftimage.h
FREETYPE_INCLUDE_DIR_ft2build - Directory that contains ft2build.h
FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib
FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll
GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs
GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains libintl.h
GETTEXT_LIBRARY - Optional/platform-dependent with gettext; path to libintl.so/libintl.dll.a
FREETYPE_INCLUDE_DIR_freetype2 - Only if building with FreeType 2; directory that contains an freetype directory with files such as ftimage.h in it
FREETYPE_INCLUDE_DIR_ft2build - Only if building with FreeType 2; directory that contains ft2build.h
FREETYPE_LIBRARY - Only if building with FreeType 2; path to libfreetype.a/libfreetype.so/freetype.lib
FREETYPE_DLL - Only if building with FreeType 2 on Windows; path to libfreetype.dll
GETTEXT_DLL - Only when building with gettext on Windows; path to libintl3.dll
GETTEXT_ICONV_DLL - Only when building with gettext on Windows; path to libiconv2.dll
GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h
GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a
GETTEXT_MSGFMT - Only when building with gettext; path to msgfmt/msgfmt.exe
ICONV_LIBRARY - Optional/platform-dependent; path to libiconv.so/libiconv.dylib
IRRLICHT_DLL - Only on Windows; path to IrrlichtMt.dll
IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h (usable for server build only)
IRRLICHT_DLL - Only on Windows; path to Irrlicht.dll
IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h
IRRLICHT_LIBRARY - Path to libIrrlicht.a/libIrrlicht.so/libIrrlicht.dll.a/Irrlicht.lib
LEVELDB_INCLUDE_DIR - Only when building with LevelDB; directory that contains db.h
LEVELDB_LIBRARY - Only when building with LevelDB; path to libleveldb.a/libleveldb.so/libleveldb.dll.a
LEVELDB_DLL - Only when building with LevelDB on Windows; path to libleveldb.dll
@@ -300,29 +273,31 @@ Library specific options:
REDIS_INCLUDE_DIR - Only when building with Redis; directory that contains hiredis.h
REDIS_LIBRARY - Only when building with Redis; path to libhiredis.a/libhiredis.so
SPATIAL_INCLUDE_DIR - Only when building with LibSpatial; directory that contains spatialindex/SpatialIndex.h
SPATIAL_LIBRARY - Only when building with LibSpatial; path to libspatialindex.so/spatialindex-32.lib
SPATIAL_LIBRARY - Only when building with LibSpatial; path to libspatialindex_c.so/spatialindex-32.lib
LUA_INCLUDE_DIR - Only if you want to use LuaJIT; directory where luajit.h is located
LUA_LIBRARY - Only if you want to use LuaJIT; path to libluajit.a/libluajit.so
MINGWM10_DLL - Only if compiling with MinGW; path to mingwm10.dll
OGG_DLL - Only if building with sound on Windows; path to libogg.dll
OGG_INCLUDE_DIR - Only if building with sound; directory that contains an ogg directory which contains ogg.h
OGG_LIBRARY - Only if building with sound; path to libogg.a/libogg.so/libogg.dll.a
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_DLL - Only if building with sound on Windows; path to libvorbisfile-3.dll
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_DLL - Only if building with sound on Windows; path to libvorbis-0.dll
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
ZSTD_DLL - Only on Windows; path to libzstd.dll
ZSTD_INCLUDE_DIR - Directory that contains zstd.h
ZSTD_LIBRARY - Path to libzstd.a/libzstd.so/ztd.lib
### Compiling on Windows using MSVC
### Compiling on Windows
### Requirements
@@ -335,14 +310,16 @@ Library specific options:
It is highly recommended to use vcpkg as package manager.
#### a) Using vcpkg to install dependencies
After you successfully built vcpkg you can easily install the required libraries:
```powershell
vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry --triplet x64-windows
vcpkg install irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit gmp jsoncpp --triplet x64-windows
```
- **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt` as described in the Linux section.
- `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store.
- `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound.
- `freetype` is optional, it allows true-type font rendering.
- `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter.
- `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled
@@ -350,6 +327,10 @@ There are other optional libraries, but they are not tested if they can build an
Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-windows`.
#### b) Compile the dependencies on your own
This is outdated and not recommended. Follow the instructions on https://dev.minetest.net/Build_Win32_Minetest_including_all_required_libraries#VS2012_Build
### Compile Minetest
#### a) Using the vcpkg toolchain and CMake GUI
@@ -373,11 +354,17 @@ Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-window
Run the following script in PowerShell:
```powershell
cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF
cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF -DENABLE_SYSTEM_JSONCPP=ON
cmake --build . --config Release
```
Make sure that the right compiler is selected and the path to the vcpkg toolchain is correct.
#### c) Using your own compiled libraries
**This is outdated and not recommended**
Follow the instructions on https://dev.minetest.net/Build_Win32_Minetest_including_all_required_libraries#VS2012_Build
### Windows Installer using WiX Toolset
Requirements:
@@ -391,60 +378,6 @@ Build the binaries as described above, but make sure you unselect `RUN_IN_PLACE`
Open the generated project file with Visual Studio. Right-click **Package** and choose **Generate**.
It may take some minutes to generate the installer.
### Compiling on MacOS
#### Requirements
- [Homebrew](https://brew.sh/)
- [Git](https://git-scm.com/downloads)
Install dependencies with homebrew:
```
brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd
```
#### Download
Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
```bash
git clone --depth 1 https://github.com/minetest/minetest.git
cd minetest
```
Download Minetest Game (otherwise only the "Development Test" game is available) using Git:
```
git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
```
Download Minetest's fork of Irrlicht:
```
git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
```
#### Build
```bash
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_GETTEXT=TRUE
make -j$(sysctl -n hw.logicalcpu)
make install
```
#### Run
```
open ./build/macos/minetest.app
```
Docker
------

View File

@@ -1,13 +1,12 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 33
buildToolsVersion '33.0.2'
ndkVersion "$ndk_version"
compileSdkVersion 29
buildToolsVersion '30.0.3'
ndkVersion '22.0.7026061'
defaultConfig {
applicationId 'net.minetest.minetest'
minSdkVersion 21
targetSdkVersion 33
minSdkVersion 16
targetSdkVersion 29
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
versionCode project.versionCode
}
@@ -41,7 +40,7 @@ android {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
include 'armeabi-v7a', 'arm64-v8a'
}
}
@@ -53,59 +52,48 @@ android {
task prepareAssets() {
def assetsFolder = "build/assets"
def projRoot = rootDir.parent
def projRoot = "../.."
def gameToCopy = "minetest_game"
doFirst {
logger.lifecycle('Preparing assets at {}', assetsFolder)
copy {
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
}
doLast {
copy {
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
}
copy {
from "${projRoot}/doc/lgpl-2.1.txt" into assetsFolder
}
copy {
from "${projRoot}/builtin" into "${assetsFolder}/builtin"
}
copy {
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
}
copy {
from "../native/deps/armeabi-v7a/Irrlicht/Shaders" into "${assetsFolder}/client/shaders/Irrlicht"
}
copy {
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
}
copy {
from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}"
}
copy {
from "${projRoot}/textures" into "${assetsFolder}/textures"
}
// compile translations
fileTree("${projRoot}/po").include("**/*.po").forEach { poFile ->
def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/"
file(moPath).mkdirs()
exec {
commandLine 'msgfmt', '-o', "${moPath}/minetest.mo", poFile
}
}
file("${assetsFolder}/.nomedia").text = ""
copy {
from "${projRoot}/doc/lgpl-2.1.txt" into "${assetsFolder}"
}
copy {
from "${projRoot}/builtin" into "${assetsFolder}/builtin"
}
copy {
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
}
copy {
from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht"
}
copy {
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
}
copy {
from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}"
}
/*copy {
// ToDo: fix broken locales
from "${projRoot}/po" into "${assetsFolder}/po"
}*/
copy {
from "${projRoot}/textures" into "${assetsFolder}/textures"
}
task zipAssets(dependsOn: prepareAssets, type: Zip) {
archiveFileName = "Minetest.zip"
from assetsFolder
destinationDirectory = file("src/main/assets")
file("${assetsFolder}/.nomedia").text = "";
task zipAssets(type: Zip) {
archiveName "Minetest.zip"
from "${assetsFolder}"
destinationDir file("src/main/assets")
}
}
preBuild.dependsOn zipAssets
prepareAssets.dependsOn ':native:getDeps'
// Map for the version code that gives each ABI a value.
import com.android.build.OutputFile
@@ -121,5 +109,5 @@ android.applicationVariants.all { variant ->
dependencies {
implementation project(':native')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
}

View File

@@ -6,7 +6,6 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:glEsVersion="0x00020000" />
<!--
`android:requestLegacyExternalStorage="true"` is workaround for using `/sdcard`
@@ -20,6 +19,8 @@
android:label="@string/label"
android:requestLegacyExternalStorage="true"
android:resizeableActivity="false"
android:hasFragileUserData="true"
android:isGame="true"
tools:ignore="UnusedAttribute">
<meta-data
@@ -31,8 +32,7 @@
android:configChanges="orientation|keyboardHidden|navigation|screenSize"
android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape"
android:theme="@style/AppTheme"
android:exported="true">
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -46,8 +46,7 @@
android:launchMode="singleTask"
android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape"
android:theme="@style/AppTheme"
android:exported="true">
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@@ -60,17 +59,6 @@
android:name=".UnzipService"
android:enabled="true"
android:exported="false" />
<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>
</application>
</manifest>

View File

@@ -26,26 +26,16 @@ 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;
import android.view.inputmethod.InputMethodManager;
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
@SuppressWarnings("unused")
public class GameActivity extends NativeActivity {
static {
System.loadLibrary("c++_shared");
@@ -95,19 +85,9 @@ public class GameActivity extends NativeActivity {
private void showDialogUI(String hint, String current, int editType) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
LinearLayout container = new LinearLayout(this);
container.setOrientation(LinearLayout.VERTICAL);
builder.setView(container);
EditText editText = new CustomEditText(this);
builder.setView(editText);
AlertDialog alertDialog = builder.create();
EditText editText;
// For multi-line, do not close the dialog after pressing back button
if (editType == 1) {
editText = new EditText(this);
} else {
editText = new CustomEditText(this);
}
container.addView(editText);
editText.setMaxLines(8);
editText.requestFocus();
editText.setHint(hint);
editText.setText(current);
@@ -123,9 +103,8 @@ public class GameActivity extends NativeActivity {
else
editText.setInputType(InputType.TYPE_CLASS_TEXT);
editText.setSelection(editText.getText().length());
editText.setOnKeyListener((view, keyCode, event) -> {
// For multi-line, do not submit the text after pressing Enter key
if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) {
editText.setOnKeyListener((view, KeyCode, event) -> {
if (KeyCode == KeyEvent.KEYCODE_ENTER) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
messageReturnCode = 0;
messageReturnValue = editText.getText().toString();
@@ -134,18 +113,6 @@ public class GameActivity extends NativeActivity {
}
return false;
});
// For multi-line, add Done button since Enter key does not submit text
if (editType == 1) {
Button doneButton = new Button(this);
container.addView(doneButton);
doneButton.setText(R.string.ime_dialog_done);
doneButton.setOnClickListener((view -> {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
messageReturnCode = 0;
messageReturnValue = editText.getText().toString();
alertDialog.dismiss();
}));
}
alertDialog.show();
alertDialog.setOnCancelListener(dialog -> {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
@@ -187,22 +154,4 @@ 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);
}
}

View File

@@ -101,8 +101,7 @@ public class MainActivity extends AppCompatActivity {
mTextView = findViewById(R.id.textView);
sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
checkPermission();
else
checkAppVersion();
@@ -127,12 +126,8 @@ public class MainActivity extends AppCompatActivity {
}
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {

View File

@@ -29,7 +29,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -77,6 +76,9 @@ public class UnzipService extends IntentService {
try {
setIsRunning(true);
File userDataDirectory = Utils.getUserDataDirectory(this);
if (userDataDirectory == null) {
throw new IOException("Unable to find user data directory");
}
try (InputStream in = this.getAssets().open(zipFile.getName())) {
try (OutputStream out = new FileOutputStream(zipFile)) {
@@ -95,9 +97,7 @@ public class UnzipService extends IntentService {
failureMessage = e.getLocalizedMessage();
} finally {
setIsRunning(false);
if (!zipFile.delete()) {
Log.w("UnzipService", "Minetest installation ZIP cannot be deleted");
}
zipFile.delete();
}
}
@@ -130,12 +130,8 @@ public class UnzipService extends IntentService {
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
int pendingIntentFlag = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pendingIntentFlag = PendingIntent.FLAG_MUTABLE;
}
PendingIntent intent = PendingIntent.getActivity(this, 0,
notificationIntent, pendingIntentFlag);
notificationIntent, 0);
builder.setContentTitle(getString(R.string.notification_title))
.setSmallIcon(R.mipmap.ic_launcher)
@@ -204,18 +200,12 @@ public class UnzipService extends IntentService {
* Migrates user data from deprecated external storage to app scoped storage
*/
private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return;
}
File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest");
if (!oldLocation.isDirectory())
return;
publishProgress(notificationBuilder, R.string.migrating, 0);
if (!newLocation.mkdir()) {
Log.e("UnzipService", "New installation folder cannot be made");
}
newLocation.mkdir();
String[] dirs = new String[] { "worlds", "games", "mods", "textures", "client" };
for (int i = 0; i < dirs.length; i++) {
@@ -233,9 +223,7 @@ public class UnzipService extends IntentService {
}
}
if (!recursivelyDeleteDirectory(oldLocation)) {
Log.w("UnzipService", "Old installation files cannot be deleted successfully");
}
recursivelyDeleteDirectory(oldLocation);
}
private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {

View File

@@ -1,43 +1,36 @@
package net.minetest.minetest;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.util.Objects;
public class Utils {
@NonNull
public static File createDirs(@NonNull File root, @NonNull String dir) {
public static @NonNull File createDirs(File root, String dir) {
File f = new File(root, dir);
if (!f.isDirectory())
if (!f.mkdirs())
Log.e("Utils", "Directory " + dir + " cannot be created");
f.mkdirs();
return f;
}
@NonNull
public static File getUserDataDirectory(@NonNull Context context) {
File extDir = Objects.requireNonNull(
context.getExternalFilesDir(null),
"Cannot get external file directory"
);
public static @Nullable File getUserDataDirectory(Context context) {
File extDir = context.getExternalFilesDir(null);
if (extDir == null) {
return null;
}
return createDirs(extDir, "Minetest");
}
@NonNull
public static File getCacheDirectory(@NonNull Context context) {
return Objects.requireNonNull(
context.getCacheDir(),
"Cannot get cache directory"
);
public static @Nullable File getCacheDirectory(Context context) {
return context.getCacheDir();
}
public static boolean isInstallValid(@NonNull Context context) {
public static boolean isInstallValid(Context context) {
File userDataDirectory = getUserDataDirectory(context);
return userDataDirectory.isDirectory() &&
return userDataDirectory != null && userDataDirectory.isDirectory() &&
new File(userDataDirectory, "games").isDirectory() &&
new File(userDataDirectory, "builtin").isDirectory() &&
new File(userDataDirectory, "client").isDirectory() &&

View File

@@ -1,3 +0,0 @@
<paths>
<external-files-path path="Minetest/" name="minetest" />
</paths>

View File

@@ -1,23 +1,21 @@
// 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", 7) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch
project.ext.set("versionMinor", 4) // Version Minor
project.ext.set("versionPatch", 2) // Version Patch
project.ext.set("versionExtra", "") // Version Extra
project.ext.set("versionCode", 44) // Android Version Code
project.ext.set("developmentBuild", 0) // Whether it is a development build, or a release
project.ext.set("versionCode", 36) // Android Version Code
// NOTE: +2 after each release!
// +1 for ARM and +1 for ARM64 APK's, because
// each APK must have a larger `versionCode` than the previous
buildscript {
ext.ndk_version = '25.2.9519653'
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'de.undercouch:gradle-download-task:4.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -27,10 +25,11 @@ buildscript {
allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
delete 'native/deps'
}

View File

@@ -1,5 +1,6 @@
#Fri Jan 08 17:52:00 UTC 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip

2
android/gradlew vendored
View File

@@ -98,7 +98,7 @@ location of your Java installation."
fi
else
JAVACMD="java"
command -v java >/dev/null || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."

411
android/icons/aux_btn.svg Normal file
View File

@@ -0,0 +1,411 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="512"
height="512"
viewBox="0 0 135.46666 135.46667"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="aux_btn.svg"
inkscape:export-filename="/home/stu/Desktop/icons/png/aux_btn.png"
inkscape:export-xdpi="24.000002"
inkscape:export-ydpi="24.000002">
<defs
id="defs2">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Colorize"
id="filter4628">
<feComposite
in2="SourceGraphic"
operator="arithmetic"
k1="0"
k2="1"
result="composite1"
id="feComposite4614" />
<feColorMatrix
in="composite1"
values="1"
type="saturate"
result="colormatrix1"
id="feColorMatrix4616" />
<feFlood
flood-opacity="1"
flood-color="rgb(158,0,0)"
result="flood1"
id="feFlood4618" />
<feBlend
in="flood1"
in2="colormatrix1"
mode="multiply"
result="blend1"
id="feBlend4620" />
<feBlend
in2="blend1"
mode="screen"
result="blend2"
id="feBlend4622" />
<feColorMatrix
in="blend2"
values="1"
type="saturate"
result="colormatrix2"
id="feColorMatrix4624" />
<feComposite
in="colormatrix2"
in2="SourceGraphic"
operator="in"
k2="1"
result="composite2"
id="feComposite4626" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Sharpen More"
id="filter5109"
inkscape:menu="Image Effects"
inkscape:menu-tooltip="Sharpen edges and boundaries within the object, force=0.3">
<feComposite
in2="SourceGraphic"
operator="arithmetic"
k1="0"
k2="1"
result="composite1"
id="feComposite5095" />
<feColorMatrix
in="composite1"
values="1"
type="saturate"
result="colormatrix1"
id="feColorMatrix5097" />
<feFlood
flood-opacity="1"
flood-color="rgb(158,67,0)"
result="flood1"
id="feFlood5099" />
<feBlend
in="flood1"
in2="colormatrix1"
mode="multiply"
result="blend1"
id="feBlend5101" />
<feBlend
in2="blend1"
mode="screen"
result="blend2"
id="feBlend5103" />
<feColorMatrix
in="blend2"
values="1"
type="saturate"
result="colormatrix2"
id="feColorMatrix5105" />
<feComposite
in="colormatrix2"
in2="SourceGraphic"
operator="in"
k2="1"
result="fbSourceGraphic"
id="feComposite5107" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix5111" />
<feComposite
in2="fbSourceGraphic"
id="feComposite5113"
operator="arithmetic"
k1="0"
k2="1"
result="composite1"
in="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix5115"
in="composite1"
values="1"
type="saturate"
result="colormatrix1" />
<feFlood
id="feFlood5117"
flood-opacity="1"
flood-color="rgb(158,0,0)"
result="flood1" />
<feBlend
in2="colormatrix1"
id="feBlend5119"
in="flood1"
mode="multiply"
result="blend1" />
<feBlend
in2="blend1"
id="feBlend5121"
mode="screen"
result="blend2" />
<feColorMatrix
id="feColorMatrix5123"
in="blend2"
values="1"
type="saturate"
result="colormatrix2" />
<feComposite
in2="fbSourceGraphic"
id="feComposite5125"
in="colormatrix2"
operator="in"
k2="1"
result="fbSourceGraphic" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix7007" />
<feConvolveMatrix
id="feConvolveMatrix7009"
order="3 3"
kernelMatrix="0 -0.15 0 -0.15 1.6 -0.15 0 -0.15 0"
divisor="1"
in="fbSourceGraphic"
targetX="1"
targetY="1"
result="fbSourceGraphic" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix7011" />
<feConvolveMatrix
id="feConvolveMatrix7013"
targetY="1"
targetX="1"
in="fbSourceGraphic"
divisor="1"
kernelMatrix="0 -0.3 0 -0.3 2.2 -0.3 0 -0.3 0"
order="3 3"
result="result1" />
<feBlend
in2="fbSourceGraphic"
id="feBlend7015"
mode="normal"
result="result2" />
</filter>
<marker
style="overflow:visible"
refY="0.0"
refX="0.0"
orient="auto"
id="DistanceX">
<path
id="path7410"
style="stroke:#000000; stroke-width:0.5"
d="M 3,-3 L -3,3 M 0,-5 L 0,5" />
</marker>
<pattern
y="0"
x="0"
width="8"
patternUnits="userSpaceOnUse"
id="Hatch"
height="8">
<path
id="path7413"
stroke-width="0.25"
stroke="#000000"
linecap="square"
d="M8 4 l-4,4" />
<path
id="path7415"
stroke-width="0.25"
stroke="#000000"
linecap="square"
d="M6 2 l-4,4" />
<path
id="path7417"
stroke-width="0.25"
stroke="#000000"
linecap="square"
d="M4 0 l-4,4" />
</pattern>
<symbol
id="*Model_Space" />
<symbol
id="*Paper_Space" />
<symbol
id="*Paper_Space0" />
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Colorize"
id="filter4883">
<feComposite
in2="SourceGraphic"
operator="arithmetic"
k1="0"
k2="1"
result="composite1"
id="feComposite4869" />
<feColorMatrix
in="composite1"
values="1"
type="saturate"
result="colormatrix1"
id="feColorMatrix4871" />
<feFlood
flood-opacity="1"
flood-color="rgb(158,21,0)"
result="flood1"
id="feFlood4873" />
<feBlend
in="flood1"
in2="colormatrix1"
mode="multiply"
result="blend1"
id="feBlend4875" />
<feBlend
in2="blend1"
mode="screen"
result="blend2"
id="feBlend4877" />
<feColorMatrix
in="blend2"
values="1"
type="saturate"
result="colormatrix2"
id="feColorMatrix4879" />
<feComposite
in="colormatrix2"
in2="SourceGraphic"
operator="in"
k2="1"
result="composite2"
id="feComposite4881" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#404040"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="-341.34157"
inkscape:cy="210.02973"
inkscape:document-units="mm"
inkscape:current-layer="layer2"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1023"
inkscape:window-x="0"
inkscape:window-y="34"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="false"
inkscape:snap-grids="false"
inkscape:snap-page="true"
showguides="true"
inkscape:snap-bbox="true"
inkscape:snap-to-guides="true"
inkscape:snap-object-midpoints="false"
inkscape:snap-others="true"
inkscape:snap-bbox-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid16"
spacingx="0.26458333"
spacingy="0.26458333"
empspacing="4"
color="#40ff40"
opacity="0.1254902"
empcolor="#40ff40"
empopacity="0.25098039" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
style="display:inline">
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d=""
id="path7055"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d=""
id="path7035"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d=""
id="path7005"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d=""
id="path5127"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48.47851181px;line-height:1.25;font-family:'Bitstream Vera Sans';-inkscape-font-specification:'Bitstream Vera Sans';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#d9d9d9;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="67.78315"
y="85.59491"
id="text4716"
transform="scale(1.0078883,0.99217343)"><tspan
sodipodi:role="line"
id="tspan4714"
x="67.78315"
y="85.59491"
style="fill:#d9d9d9;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">AUX</tspan></text>
<flowRoot
xml:space="preserve"
id="flowRoot4718"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:'Bitstream Vera Sans';-inkscape-font-specification:'Bitstream Vera Sans';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"
transform="scale(0.26458333)"><flowRegion
id="flowRegion4720"
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"><rect
id="rect4722"
width="157.5838"
height="136.37059"
x="264.65997"
y="124.10143"
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" /></flowRegion><flowPara
id="flowPara4724" /></flowRoot> </g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -2,20 +2,19 @@ apply plugin: 'com.android.library'
apply plugin: 'de.undercouch.download'
android {
compileSdkVersion 33
buildToolsVersion '33.0.2'
ndkVersion "$ndk_version"
compileSdkVersion 29
buildToolsVersion '30.0.3'
ndkVersion '22.0.7026061'
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
minSdkVersion 16
targetSdkVersion 29
externalNativeBuild {
ndkBuild {
arguments '-j' + Runtime.getRuntime().availableProcessors(),
"versionMajor=${versionMajor}",
"versionMinor=${versionMinor}",
"versionPatch=${versionPatch}",
"versionExtra=${versionExtra}",
"developmentBuild=${developmentBuild}"
"versionExtra=${versionExtra}"
}
}
}
@@ -31,7 +30,7 @@ android {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
include 'armeabi-v7a', 'arm64-v8a'//, 'x86'
}
}
@@ -51,24 +50,54 @@ android {
}
// get precompiled deps
def folder = 'minetest_android_deps_binaries'
def deps_ref = "342eb18b7512462585a33bc9eef0b68298087151"
task downloadDeps(type: Download) {
def depsDir = new File(buildDir.parent, 'deps')
def depsZip = new File(buildDir, 'deps.zip')
src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps.zip'
dest depsZip
src 'https://github.com/minetest/' + folder + '/archive/' + deps_ref + '.zip'
dest new File(buildDir, 'deps.zip')
overwrite false
}
task getDeps(dependsOn: downloadDeps, type: Copy) {
depsDir.mkdir()
from zipTree(depsZip)
into depsDir
doFirst { logger.lifecycle('Extracting to {}', depsDir) }
task getDeps(dependsOn: downloadDeps, type: Copy) {
def deps = file('deps')
def f = file("$buildDir/" + folder + "-" + deps_ref)
if (!deps.exists() && !f.exists()) {
from zipTree(downloadDeps.dest)
into buildDir
}
doLast {
if (!deps.exists()) {
file(f).renameTo(file(deps))
}
}
}
// get sqlite
def sqlite_ver = '3340000'
task downloadSqlite(dependsOn: getDeps, type: Download) {
src 'https://www.sqlite.org/2020/sqlite-amalgamation-' + sqlite_ver + '.zip'
dest new File(buildDir, 'sqlite.zip')
overwrite false
}
task getSqlite(dependsOn: downloadSqlite, type: Copy) {
def sqlite = file('deps/Android/sqlite')
def f = file("$buildDir/sqlite-amalgamation-" + sqlite_ver)
if (!sqlite.exists() && !f.exists()) {
from zipTree(downloadSqlite.dest)
into buildDir
}
doLast {
if (!sqlite.exists()) {
file(f).renameTo(file(sqlite))
}
}
}
preBuild.dependsOn getDeps
clean {
delete new File(buildDir.parent, 'deps')
}
preBuild.dependsOn getSqlite

View File

@@ -1,100 +1,67 @@
LOCAL_PATH := $(call my-dir)/..
#LOCAL_ADDRESS_SANITIZER:=true
#USE_BUILTIN_LUA:=true
include $(CLEAR_VARS)
LOCAL_MODULE := Curl
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libmbedcrypto
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libmbedtls
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedtls.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libmbedx509
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedx509.a
LOCAL_SRC_FILES := deps/Android/Curl/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Freetype
LOCAL_SRC_FILES := deps/$(APP_ABI)/Freetype/libfreetype.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Iconv
LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libiconv.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libcharset
LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libcharset.a
LOCAL_SRC_FILES := deps/Android/Freetype/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libfreetype.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Irrlicht
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libIrrlichtMt.a
LOCAL_SRC_FILES := deps/Android/Irrlicht/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libIrrlicht.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 := LevelDB
#LOCAL_SRC_FILES := deps/Android/LevelDB/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libleveldb.a
#include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := LuaJIT
LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a
LOCAL_SRC_FILES := deps/Android/LuaJIT/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libluajit.a
include $(PREBUILT_STATIC_LIBRARY)
endif
include $(CLEAR_VARS)
LOCAL_MODULE := mbedTLS
LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedtls.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := mbedx509
LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedx509.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := mbedcrypto
LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := OpenAL
LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/libopenal.a
LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Gettext
LOCAL_SRC_FILES := deps/$(APP_ABI)/Gettext/libintl.a
include $(PREBUILT_STATIC_LIBRARY)
# You can use `OpenSSL and Crypto` instead `mbedTLS mbedx509 mbedcrypto`,
#but it increase APK size on ~0.7MB
#include $(CLEAR_VARS)
#LOCAL_MODULE := OpenSSL
#LOCAL_SRC_FILES := deps/Android/OpenSSL/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libssl.a
#include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := SQLite3
LOCAL_SRC_FILES := deps/$(APP_ABI)/SQLite/libsqlite3.a
include $(PREBUILT_STATIC_LIBRARY)
#include $(CLEAR_VARS)
#LOCAL_MODULE := Crypto
#LOCAL_SRC_FILES := deps/Android/OpenSSL/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcrypto.a
#include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Vorbis
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbis.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libvorbisfile
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbisfile.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libogg
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libogg.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Zstd
LOCAL_SRC_FILES := deps/$(APP_ABI)/Zstd/libzstd.a
LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
@@ -106,21 +73,15 @@ LOCAL_CFLAGS += \
-DENABLE_GLES=1 \
-DUSE_CURL=1 \
-DUSE_SOUND=1 \
-DUSE_FREETYPE=1 \
-DUSE_LEVELDB=0 \
-DUSE_GETTEXT=1 \
-DUSE_LUAJIT=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
@@ -136,23 +97,16 @@ LOCAL_C_INCLUDES := \
../../src/script \
../../lib/gmp \
../../lib/jsoncpp \
deps/$(APP_ABI)/Curl/include \
deps/$(APP_ABI)/Freetype/include/freetype2 \
deps/$(APP_ABI)/Irrlicht/include \
deps/$(APP_ABI)/Gettext/include \
deps/$(APP_ABI)/Iconv/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
deps/Android/Curl/include \
deps/Android/Freetype/include \
deps/Android/Irrlicht/include \
deps/Android/LevelDB/include \
deps/Android/libiconv/include \
deps/Android/libiconv/libcharset/include \
deps/Android/LuaJIT/src \
deps/Android/OpenAL-Soft/include \
deps/Android/sqlite \
deps/Android/Vorbis/include
LOCAL_SRC_FILES := \
$(wildcard ../../src/client/*.cpp) \
@@ -194,7 +148,6 @@ LOCAL_SRC_FILES := \
../../src/itemdef.cpp \
../../src/itemstackmetadata.cpp \
../../src/light.cpp \
../../src/lighting.cpp \
../../src/log.cpp \
../../src/main.cpp \
../../src/map.cpp \
@@ -236,40 +189,8 @@ 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
# LevelDB backend is disabled
# ../../src/database/database-leveldb.cpp
# GMP
LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c
@@ -277,22 +198,18 @@ LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c
# JSONCPP
LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp
LOCAL_STATIC_LIBRARIES += \
Curl libmbedcrypto libmbedtls libmbedx509 \
Freetype \
Iconv libcharset \
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)
# iconv
LOCAL_SRC_FILES += \
deps/Android/libiconv/lib/iconv.c \
deps/Android/libiconv/libcharset/lib/localcharset.c
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES -lz
# SQLite3
LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c
LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT android_native_app_glue $(PROFILER_LIBS) #LevelDB
#OpenSSL Crypto
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES
include $(BUILD_SHARED_LIBRARY)

View File

@@ -5,22 +5,22 @@ NDK_TOOLCHAIN_VERSION := clang
APP_SHORT_COMMANDS := true
APP_MODULES := Minetest
APP_CPPFLAGS := -O2 -fvisibility=hidden
APP_CPPFLAGS := -Ofast -fvisibility=hidden -fexceptions -Wno-deprecated-declarations -Wno-extra-tokens
ifeq ($(APP_ABI),armeabi-v7a)
APP_CPPFLAGS += -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb
APP_CPPFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb
endif
ifeq ($(APP_ABI),x86)
APP_CPPFLAGS += -mssse3 -mfpmath=sse -funroll-loops
endif
#ifeq ($(APP_ABI),x86)
#APP_CPPFLAGS += -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -funroll-loops
#endif
ifndef NDEBUG
APP_CPPFLAGS := -g -Og -fno-omit-frame-pointer
APP_CPPFLAGS := -g -D_DEBUG -O0 -fno-omit-frame-pointer -fexceptions
endif
APP_CFLAGS := $(APP_CPPFLAGS) -Wno-inconsistent-missing-override -Wno-parentheses-equality
APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++14
APP_CFLAGS := $(APP_CPPFLAGS) -Wno-parentheses-equality #-Werror=shorten-64-to-32
APP_CXXFLAGS := $(APP_CPPFLAGS) -frtti -std=gnu++17
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe
ifeq ($(APP_ABI),arm64-v8a)

View File

@@ -1,64 +0,0 @@
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
local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM
local builtin_shared = {}
dofile(gamepath .. "constants.lua")
assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared)
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)
builtin_shared.cache_content_ids()

17
builtin/async/init.lua Normal file
View File

@@ -0,0 +1,17 @@
core.log("info", "Initializing Asynchronous environment")
function core.job_processor(serialized_func, serialized_param)
local func = loadstring(serialized_func)
local param = core.deserialize(serialized_param)
local retval = nil
if type(func) == "function" then
retval = core.serialize(func(param))
else
core.log("error", "ASYNC WORKER: Unable to deserialize function")
end
return retval or core.serialize(nil)
end

View File

@@ -1,9 +0,0 @@
core.log("info", "Initializing asynchronous environment")
function core.job_processor(func, serialized_param)
local param = core.deserialize(serialized_param)
local retval = core.serialize(func(param))
return retval or core.serialize(nil)
end

View File

@@ -1,5 +1,6 @@
-- Minetest: builtin/client/chatcommands.lua
core.register_on_sending_chat_message(function(message)
if message:sub(1,2) == ".." then
return false
@@ -7,7 +8,7 @@ core.register_on_sending_chat_message(function(message)
local first_char = message:sub(1,1)
if first_char == "/" or first_char == "." then
core.display_chat_message(core.gettext("Issued command: ") .. message)
core.display_chat_message(core.gettext("issued command: ") .. message)
end
if first_char ~= "." then
@@ -18,7 +19,7 @@ core.register_on_sending_chat_message(function(message)
param = param or ""
if not cmd then
core.display_chat_message("-!- " .. core.gettext("Empty command."))
core.display_chat_message(core.gettext("-!- Empty command"))
return true
end
@@ -35,7 +36,7 @@ core.register_on_sending_chat_message(function(message)
core.display_chat_message(result)
end
else
core.display_chat_message("-!- " .. core.gettext("Invalid command: ") .. cmd)
core.display_chat_message(core.gettext("-!- Invalid command: ") .. cmd)
end
return true
@@ -65,7 +66,7 @@ core.register_chatcommand("clear_chat_queue", {
description = core.gettext("Clear the out chat queue"),
func = function(param)
core.clear_out_chat_queue()
return true, core.gettext("The out chat queue is now empty.")
return true, core.gettext("The out chat queue is now empty")
end,
})

View File

@@ -2,6 +2,7 @@
-- handled by the engine.
core.register_on_death(function()
core.display_chat_message("You died.")
local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
"label[4.85,1.35;" .. fgettext("You died") ..
"]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"

View File

@@ -5,9 +5,7 @@ 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(commonpath .. "vector.lua")
dofile(clientpath .. "death_formspec.lua")
dofile(clientpath .. "misc.lua")
assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions

View File

@@ -1,7 +0,0 @@
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

View File

@@ -1,3 +1,4 @@
core.callback_origins = {}
local getinfo = debug.getinfo

View File

@@ -37,14 +37,7 @@ function core.after(after, func, ...)
arg = {...},
mod_origin = core.get_last_run_mod(),
}
jobs[#jobs + 1] = new_job
time_next = math.min(time_next, expire)
return {
cancel = function()
new_job.func = function() end
new_job.args = {}
end
}
return { cancel = function() new_job.func = function() end end }
end

View File

@@ -1,47 +1,7 @@
-- Minetest: builtin/common/chatcommands.lua
-- For server-side translations (if INIT == "game")
-- Otherwise, use core.gettext
local S = core.get_translator("__builtin")
core.registered_chatcommands = {}
-- Interpret the parameters of a command, separating options and arguments.
-- Input: command, param
-- command: name of command
-- param: parameters of command
-- Returns: opts, args
-- opts is a string of option letters, or false on error
-- args is an array with the non-option arguments in order, or an error message
-- Example: for this command line:
-- /command a b -cd e f -g
-- the function would receive:
-- a b -cd e f -g
-- and it would return:
-- "cdg", {"a", "b", "e", "f"}
-- Negative numbers are taken as arguments. Long options (--option) are
-- currently rejected as reserved.
local function getopts(command, param)
local opts = ""
local args = {}
for match in param:gmatch("%S+") do
if match:byte(1) == 45 then -- 45 = '-'
local second = match:byte(2)
if second == 45 then
return false, S("Invalid parameters (see /help @1).", command)
elseif second and (second < 48 or second > 57) then -- 48 = '0', 57 = '9'
opts = opts .. match:sub(2)
else
-- numeric, add it to args
args[#args + 1] = match
end
else
args[#args + 1] = match
end
end
return opts, args
end
function core.register_chatcommand(cmd, def)
def = def or {}
def.params = def.params or ""
@@ -69,30 +29,35 @@ function core.override_chatcommand(name, redefinition)
core.registered_chatcommands[name] = chatcommand
end
local function format_help_line(cmd, def)
local cmd_marker = INIT == "client" and "." or "/"
local msg = core.colorize("#00ffff", cmd_marker .. cmd)
if def.params and def.params ~= "" then
msg = msg .. " " .. def.params
end
if def.description and def.description ~= "" then
msg = msg .. ": " .. def.description
end
return msg
local cmd_marker = "/"
local function gettext(...)
return ...
end
local function gettext_replace(text, replace)
return text:gsub("$1", replace)
end
if INIT == "client" then
cmd_marker = "."
gettext = core.gettext
gettext_replace = fgettext_ne
end
local function do_help_cmd(name, param)
local opts, args = getopts("help", param)
if not opts then
return false, args
local function format_help_line(cmd, def)
local msg = core.colorize("#00ffff", cmd_marker .. cmd)
if def.params and def.params ~= "" then
msg = msg .. " " .. def.params
end
if def.description and def.description ~= "" then
msg = msg .. ": " .. def.description
end
return msg
end
if #args > 1 then
return false, S("Too many arguments, try using just /help <command>")
end
local use_gui = INIT ~= "client" and core.get_player_by_name(name)
use_gui = use_gui and not opts:find("t")
if #args == 0 and not use_gui then
if param == "" then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then
@@ -100,25 +65,10 @@ local function do_help_cmd(name, param)
end
end
table.sort(cmds)
local msg
if INIT == "game" then
msg = S("Available commands: @1",
table.concat(cmds, " ")) .. "\n"
.. S("Use '/help <cmd>' to get more "
.. "information, or '/help all' to list "
.. "everything.")
else
msg = core.gettext("Available commands: ")
.. table.concat(cmds, " ") .. "\n"
.. core.gettext("Use '.help <cmd>' to get more "
.. "information, or '.help all' to list "
.. "everything.")
end
return true, msg
elseif #args == 0 or (args[1] == "all" and use_gui) then
core.show_general_help_formspec(name)
return true
elseif args[1] == "all" then
return true, gettext("Available commands: ") .. table.concat(cmds, " ") .. "\n"
.. gettext_replace("Use '$1help <cmd>' to get more information,"
.. " or '$1help all' to list everything.", cmd_marker)
elseif param == "all" then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then
@@ -126,35 +76,19 @@ local function do_help_cmd(name, param)
end
end
table.sort(cmds)
local msg
if INIT == "game" then
msg = S("Available commands:")
else
msg = core.gettext("Available commands:")
end
return true, msg.."\n"..table.concat(cmds, "\n")
elseif INIT == "game" and args[1] == "privs" then
if use_gui then
core.show_privs_help_formspec(name)
return true
end
return true, gettext("Available commands:").."\n"..table.concat(cmds, "\n")
elseif INIT == "game" and param == "privs" then
local privs = {}
for priv, def in pairs(core.registered_privileges) do
privs[#privs + 1] = priv .. ": " .. def.description
end
table.sort(privs)
return true, S("Available privileges:").."\n"..table.concat(privs, "\n")
return true, "Available privileges:\n"..table.concat(privs, "\n")
else
local cmd = args[1]
local cmd = param
local def = core.registered_chatcommands[cmd]
if not def then
local msg
if INIT == "game" then
msg = S("Command not available: @1", cmd)
else
msg = core.gettext("Command not available: ") .. cmd
end
return false, msg
return false, gettext("Command not available: ")..cmd
else
return true, format_help_line(cmd, def)
end
@@ -163,16 +97,16 @@ end
if INIT == "client" then
core.register_chatcommand("help", {
params = core.gettext("[all | <cmd>]"),
description = core.gettext("Get help for commands"),
params = gettext("[all | <cmd>]"),
description = gettext("Get help for commands"),
func = function(param)
return do_help_cmd(nil, param)
end,
})
else
core.register_chatcommand("help", {
params = S("[all | privs | <cmd>] [-t]"),
description = S("Get help for commands or list privileges (-t: output in chat)"),
params = "[all | privs | <cmd>]",
description = "Get help for commands or list privileges",
func = do_help_cmd,
})
end

View File

@@ -20,8 +20,8 @@
-- TODO code cleanup --
-- Generic implementation of a filter/sortable list --
-- Usage: --
-- Filterlist needs to be initialized on creation. To achieve this you need --
-- to pass following functions: --
-- Filterlist needs to be initialized on creation. To achieve this you need to --
-- pass following functions: --
-- raw_fct() (mandatory): --
-- function returning a table containing the elements to be filtered --
-- compare_fct(element1,element2) (mandatory): --
@@ -31,7 +31,7 @@
-- filter_fct(element,filtercriteria) (optional) --
-- function returning true/false if filtercriteria met to element --
-- fetch_param (optional) --
-- parameter passed to raw_fct to acquire correct raw data --
-- parameter passed to raw_fct to aquire correct raw data --
-- --
--------------------------------------------------------------------------------
filterlist = {}

View File

@@ -20,8 +20,7 @@ local LIST_FORMSPEC_DESCRIPTION = [[
button_exit[5,7;3,1;quit;%s]
]]
local F = core.formspec_escape
local S = core.get_translator("__builtin")
local formspec_escape = core.formspec_escape
local check_player_privs = core.check_player_privs
@@ -52,23 +51,22 @@ core.after(0, load_mod_command_tree)
local function build_chatcommands_formspec(name, sel, copy)
local rows = {}
rows[1] = "#FFF,0,"..F(S("Command"))..","..F(S("Parameters"))
rows[1] = "#FFF,0,Command,Parameters"
local description = S("For more information, click on "
.. "any entry in the list.").. "\n" ..
S("Double-click to copy the entry to the chat history.")
local description = "For more information, click on any entry in the list.\n" ..
"Double-click to copy the entry to the chat history."
for i, data in ipairs(mod_cmds) do
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
rows[#rows + 1] = COLOR_BLUE .. ",0," .. formspec_escape(data[1]) .. ","
for j, cmds in ipairs(data[2]) do
local has_priv = check_player_privs(name, 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))
cmds[1], formspec_escape(cmds[2].params))
if sel == #rows then
description = cmds[2].description
if copy then
core.chat_send_player(name, S("Command: @1 @2",
core.chat_send_player(name, ("Command: %s %s"):format(
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params))
end
end
@@ -76,9 +74,9 @@ local function build_chatcommands_formspec(name, sel, copy)
end
return LIST_FORMSPEC_DESCRIPTION:format(
F(S("Available commands: (see also: /help <cmd>)")),
"Available commands: (see also: /help <cmd>)",
table.concat(rows, ","), sel or 0,
F(description), F(S("Close"))
description, "Close"
)
end
@@ -93,19 +91,19 @@ local function build_privs_formspec(name)
table.sort(privs, function(a, b) return a[1] < b[1] end)
local rows = {}
rows[1] = "#FFF,0,"..F(S("Privilege"))..","..F(S("Description"))
rows[1] = "#FFF,0,Privilege,Description"
local player_privs = core.get_player_privs(name)
for i, data in ipairs(privs) do
rows[#rows + 1] = ("%s,0,%s,%s"):format(
player_privs[data[1]] and COLOR_GREEN or COLOR_GRAY,
data[1], F(data[2].description))
data[1], formspec_escape(data[2].description))
end
return LIST_FORMSPEC:format(
F(S("Available privileges:")),
"Available privileges:",
table.concat(rows, ","),
F(S("Close"))
"Close"
)
end
@@ -125,12 +123,30 @@ core.register_on_player_receive_fields(function(player, formname, fields)
end
end)
function core.show_general_help_formspec(name)
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name))
local help_command = core.registered_chatcommands["help"]
local old_help_func = help_command.func
help_command.func = function(name, param)
local admin = core.settings:get("name")
-- If the admin ran help, put the output in the chat buffer as well to
-- work with the server terminal
if param == "privs" then
core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name))
if name ~= admin then
return true
end
end
if param == "" or param == "all" then
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name))
if name ~= admin then
return true
end
end
return old_help_func(name, param)
end
function core.show_privs_help_formspec(name)
core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name))
end

View File

@@ -1,239 +0,0 @@
-- 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.)
local builtin_shared = ...
--
-- 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_fourdir(dir)
if 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
function core.fourdir_to_dir(fourdir)
return facedir_to_dir[facedir_to_dir_map[fourdir % 4]]
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 == "color4dir") 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 == "color4dir" then
param2 = math.floor(param2 / 4) * 4
elseif paramtype2 == "colorwallmounted" then
param2 = math.floor(param2 / 8) * 8
elseif paramtype2 == "colordegrotate" then
param2 = math.floor(param2 / 32) * 32
end
-- paramtype2 == "color" requires no modification.
return param2
end
-- Content ID caching
local old_get_content_id = core.get_content_id
local old_get_name_from_content_id = core.get_name_from_content_id
local name2content = setmetatable({}, {
__index = function(self, name)
return old_get_content_id(name)
end,
})
local content2name = setmetatable({}, {
__index = function(self, id)
return old_get_name_from_content_id(id)
end,
})
function core.get_content_id(name)
return name2content[name]
end
function core.get_name_from_content_id(id)
return content2name[id]
end
-- Cache content IDs after they have stopped changing.
function builtin_shared.cache_content_ids()
for name in pairs(core.registered_nodes) do
local id = old_get_content_id(name)
name2content[name] = id
content2name[id] = name
end
-- unknown is not in the registered node list.
local unknown_name = old_get_name_from_content_id(core.CONTENT_UNKNOWN)
name2content[unknown_name] = core.CONTENT_UNKNOWN
content2name[core.CONTENT_UNKNOWN] = unknown_name
for name in pairs(core.registered_aliases) do
if core.registered_nodes[name] then
name2content[name] = old_get_content_id(name)
end
end
end
if core.set_read_node and core.set_push_node then
local function read_node(node)
return name2content[node.name], node.param1, node.param2
end
core.set_read_node(read_node)
core.set_read_node = nil
local function push_node(content, param1, param2)
return {name = content2name[content], param1 = param1, param2 = param2}
end
core.set_push_node(push_node)
core.set_push_node = nil
end

View File

@@ -170,9 +170,6 @@ end
--------------------------------------------------------------------------------
function string.split(str, delim, include_empty, max_splits, sep_is_pattern)
delim = delim or ","
if delim == "" then
error("string.split separator is empty", 2)
end
max_splits = max_splits or -2
local items = {}
local pos, len = 1, #str
@@ -207,12 +204,19 @@ end
--------------------------------------------------------------------------------
function string:trim()
return self:match("^%s*(.-)%s*$")
return (self:gsub("^%s*(.-)%s*$", "%1"))
end
--------------------------------------------------------------------------------
function math.hypot(x, y)
return math.sqrt(x * x + y * y)
local t
x = math.abs(x)
y = math.abs(y)
t = math.min(x, y)
x = math.max(x, y)
if x == 0 then return 0 end
t = t / x
return x * math.sqrt(1 + t * t)
end
--------------------------------------------------------------------------------
@@ -240,25 +244,15 @@ function math.factorial(x)
return v
end
function math.round(x)
if x >= 0 then
return math.floor(x + 0.5)
end
return math.ceil(x - 0.5)
end
local formspec_escapes = {
["\\"] = "\\\\",
["["] = "\\[",
["]"] = "\\]",
[";"] = "\\;",
[","] = "\\,",
["$"] = "\\$",
}
function core.formspec_escape(text)
-- Use explicit character set instead of dot here because it doubles the performance
return text and string.gsub(text, "[\\%[%];,$]", formspec_escapes)
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
end
@@ -269,21 +263,18 @@ function core.wrap_text(text, max_length, as_table)
return as_table and {text} or text
end
local line_length = 0
for word in text:gmatch("%S+") do
if line_length > 0 and line_length + #word + 1 >= max_length then
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
-- word wouldn't fit on current line, move to next line
table.insert(result, table.concat(line, " "))
line = {word}
line_length = #word
else
table.insert(line, word)
line_length = line_length + 1 + #word
table.insert(result, table.concat(line, ' '))
line = {}
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
--------------------------------------------------------------------------------
@@ -432,50 +423,56 @@ function core.string_to_pos(value)
return nil
end
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)
local p = {}
p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
if p.x and p.y and p.z then
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
return p
end
p = {}
p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
if p.x and p.y and p.z then
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
return p
end
return nil
end
--------------------------------------------------------------------------------
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)
function core.string_to_area(value)
local p1, p2 = unpack(value:split(") ("))
if p1 == nil or p2 == nil then
return nil
end
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
p1 = core.string_to_pos(p1 .. ")")
p2 = core.string_to_pos("(" .. p2)
if p1 == nil or p2 == nil then
return nil
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 = {}
@@ -523,7 +520,19 @@ end
--------------------------------------------------------------------------------
-- mainmenu only functions
--------------------------------------------------------------------------------
if core.gettext then -- for client and mainmenu
if INIT == "mainmenu" then
function core.get_game(index)
local games = core.get_games()
if index > 0 and index <= #games then
return games[index]
end
return nil
end
end
if INIT == "client" or INIT == "mainmenu" then
function fgettext_ne(text, ...)
text = core.gettext(text)
local arg = {n=select('#', ...), ...}
@@ -692,71 +701,3 @@ 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

View File

@@ -1,19 +0,0 @@
-- 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

View File

@@ -1,238 +1,205 @@
--- Lua module to serialize values as Lua code.
-- From: https://github.com/appgurueu/modlib/blob/master/luon.lua
-- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
-- License: MIT
-- @copyright 2006-2997 Fabien Fleutot <metalua@gmail.com>
-- @author Fabien Fleutot <metalua@gmail.com>
-- @author ShadowNinja <shadowninja@minetest.net>
--------------------------------------------------------------------------------
local next, rawget, pairs, pcall, error, type, setfenv, loadstring
= next, rawget, pairs, pcall, error, type, setfenv, loadstring
--- 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 table_concat, string_dump, string_format, string_match, math_huge
= table.concat, string.dump, string.format, string.match, math.huge
-- Recursively counts occurrences 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
-- 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
end
local function count_values(val)
local type_ = type(val)
if type_ == "boolean" or type_ == "number" then
-- 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)
return
end
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)
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)
end
end
elseif type_ ~= "string" and type_ ~= "function" then
error("unsupported type: " .. type_)
nested[x] = nil
end
end
count_values(value)
return counts
end
-- 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 dumped = {} -- object->varname set
local local_defs = {} -- Dumped local definitions as source code lines
local function quote(string)
return string_format("%q", string)
end
-- Mutually recursive local functions:
local dump_val, dump_or_ref_val
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)
-- 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
-- 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))
-- 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
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)
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
write("}")
return
return "{"..table.concat(vals, ", ").."}"
else
error("Can't serialize data of type "..tp)
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("]")
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
write("=")
dump(v)
write(";")
end
end
write("return ")
dump(value)
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
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)
-- Deserialization
local function safe_loadstring(...)
local func, err = loadstring(...)
if func then
setfenv(func, {})
return func
end
return nil, err
end
local function dummy_func() end
function core.deserialize(str, safe)
-- 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"
if type(str) ~= "string" then
return nil, "Cannot deserialize type '"..type(str)
.."'. Argument must be a string."
end
local t = type(str)
if t ~= "string" then
error(("minetest.deserialize called with %s (expected string)."):format(t))
if str:byte(1) == 0x1B then
return nil, "Bytecode prohibited"
end
local f, err = loadstring(str)
if not f then return nil, err end
local func, err = loadstring(str)
if not func 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})
-- 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
local good, data = pcall(f)
if good then
return data
else
env.loadstring = function(str, ...)
local func, err = loadstring(str, ...)
if func then
setfenv(func, env)
return func
end
return nil, err
end
return nil, data
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

View File

@@ -1,4 +1,9 @@
local getinfo, rawget, rawset = debug.getinfo, rawget, rawset
-- 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
function core.global_exists(name)
if type(name) ~= "string" then
@@ -10,37 +15,43 @@ end
local meta = {}
local declared = {}
-- Key is source file, line, and variable name; separated by NULs
-- Key is source file, line, and variable name; seperated by NULs
local warned = {}
function meta:__newindex(name, value)
rawset(self, name, value)
if declared[name] then
return
end
local info = getinfo(2, "Sl")
local desc = ("%s:%d"):format(info.short_src, info.currentline)
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.")
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.")
:format(name, desc))
warned[warn_key] = true
warned[warn_key] = true
end
declared[name] = true
end
declared[name] = true
-- 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))
end
rawset(self, name, value)
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 warned[warn_key] and info.what ~= "C" then
if not declared[name] and 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)

View File

@@ -1,5 +1,4 @@
_G.core = {}
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")
describe("string", function()
@@ -38,12 +37,6 @@ describe("string", function()
assert.same({ "one", "two" }, string.split("one,two", ",", false, -1, true))
assert.same({ "one", "two", "three" }, string.split("one2two3three", "%d", false, -1, true))
end)
it("rejects empty separator", function()
assert.has.errors(function()
string.split("", "")
end)
end)
end)
end)
@@ -62,8 +55,8 @@ end)
describe("pos", function()
it("from string", function()
assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("10.0, 5.1, -2"))
assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("( 10.0, 5.1, -2)"))
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("10.0, 5.1, -2"))
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("( 10.0, 5.1, -2)"))
assert.is_nil(core.string_to_pos("asd, 5, -2)"))
end)
@@ -72,107 +65,9 @@ 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)

View File

@@ -3,94 +3,39 @@ _G.core = {}
_G.setfenv = require 'busted.compatibility'.setfenv
dofile("builtin/common/serialize.lua")
dofile("builtin/common/vector.lua")
-- 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
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"}})
local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("handles characters", function()
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)
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)
end)
it("handles precise numbers", function()
assert_preserves(0.2695949158945771)
local test_in = 0.2695949158945771
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("handles big integers", function()
assert_preserves(269594915894577)
local test_in = 269594915894577
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("handles recursive structures", function()
local test_in = { hello = "world" }
test_in.foo = test_in
assert_preserves(test_in)
end)
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)
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("strips functions in safe mode", function()
@@ -100,7 +45,6 @@ describe("serialize", function()
end,
foo = "bar"
}
setfenv(test_in.func, _G)
local str = core.serialize(test_in)
assert.not_nil(str:find("loadstring"))
@@ -109,80 +53,4 @@ describe("serialize", function()
assert.is_nil(test_out.func)
assert.equals(test_out.foo, "bar")
end)
it("vectors work", function()
local v = vector.new(1, 2, 3)
assert_preserves({v})
assert_preserves(v)
-- abuse
v = vector.new(1, 2, 3)
v.a = "bla"
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)

View File

@@ -4,20 +4,14 @@ dofile("builtin/common/vector.lua")
describe("vector", function()
describe("new()", function()
it("constructs", function()
assert.same({x = 0, y = 0, z = 0}, vector.new())
assert.same({x = 1, y = 2, z = 3}, vector.new(1, 2, 3))
assert.same({x = 3, y = 2, z = 1}, vector.new({x = 3, y = 2, z = 1}))
assert.is_true(vector.check(vector.new()))
assert.is_true(vector.check(vector.new(1, 2, 3)))
assert.is_true(vector.check(vector.new({x = 3, y = 2, z = 1})))
assert.same({ x = 0, y = 0, z = 0 }, vector.new())
assert.same({ x = 1, y = 2, z = 3 }, vector.new(1, 2, 3))
assert.same({ x = 3, y = 2, z = 1 }, vector.new({ x = 3, y = 2, z = 1 }))
local input = vector.new({ x = 3, y = 2, z = 1 })
local output = vector.new(input)
assert.same(input, output)
assert.equal(input, output)
assert.is_false(rawequal(input, output))
assert.equal(input, input:new())
assert.are_not.equal(input, output)
end)
it("throws on invalid input", function()
@@ -31,294 +25,27 @@ describe("vector", function()
end)
end)
it("zero()", function()
assert.same({x = 0, y = 0, z = 0}, vector.zero())
assert.same(vector.new(), vector.zero())
assert.equal(vector.new(), vector.zero())
assert.is_true(vector.check(vector.zero()))
end)
it("copy()", function()
local v = vector.new(1, 2, 3)
assert.same(v, vector.copy(v))
assert.same(vector.new(v), vector.copy(v))
assert.equal(vector.new(v), vector.copy(v))
assert.is_true(vector.check(vector.copy(v)))
end)
it("indexes", function()
local some_vector = vector.new(24, 42, 13)
assert.equal(24, some_vector[1])
assert.equal(24, some_vector.x)
assert.equal(42, some_vector[2])
assert.equal(42, some_vector.y)
assert.equal(13, some_vector[3])
assert.equal(13, some_vector.z)
some_vector[1] = 100
assert.equal(100, some_vector.x)
some_vector.x = 101
assert.equal(101, some_vector[1])
some_vector[2] = 100
assert.equal(100, some_vector.y)
some_vector.y = 102
assert.equal(102, some_vector[2])
some_vector[3] = 100
assert.equal(100, some_vector.z)
some_vector.z = 103
assert.equal(103, some_vector[3])
end)
it("direction()", function()
local a = vector.new(1, 0, 0)
local b = vector.new(1, 42, 0)
assert.equal(vector.new(0, 1, 0), vector.direction(a, b))
assert.equal(vector.new(0, 1, 0), a:direction(b))
end)
it("distance()", function()
local a = vector.new(1, 0, 0)
local b = vector.new(3, 42, 9)
assert.is_true(math.abs(43 - vector.distance(a, b)) < 1.0e-12)
assert.is_true(math.abs(43 - a:distance(b)) < 1.0e-12)
assert.equal(0, vector.distance(a, a))
assert.equal(0, b:distance(b))
end)
it("length()", function()
local a = vector.new(0, 0, -23)
assert.equal(0, vector.length(vector.new()))
assert.equal(23, vector.length(a))
assert.equal(23, a:length())
end)
it("normalize()", function()
local a = vector.new(0, 0, -23)
assert.equal(vector.new(0, 0, -1), vector.normalize(a))
assert.equal(vector.new(0, 0, -1), a:normalize())
assert.equal(vector.new(), vector.normalize(vector.new()))
end)
it("floor()", function()
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(0, 0, -1), vector.floor(a))
assert.equal(vector.new(0, 0, -1), a:floor())
end)
it("round()", function()
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(0, 1, -1), vector.round(a))
assert.equal(vector.new(0, 1, -1), a:round())
end)
it("apply()", function()
local i = 0
local f = function(x)
i = i + 1
return x + i
end
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil))
assert.equal(vector.new(1, 1, 0), a:apply(math.ceil))
assert.equal(vector.new(0.1, 0.9, 0.5), vector.apply(a, math.abs))
assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs))
assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f))
assert.equal(vector.new(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))
end
local function assertNE(a, b)
assert.is_false(vector.equals(a, b))
end
assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
assertE({x = -1, y = 0, z = 1}, vector.new(-1, 0, 1))
local a = {x = 2, y = 4, z = -10}
assertE(a, a)
assertNE({x = -1, y = 0, z = 1}, a)
assert.equal(vector.new(1, 2, 3), vector.new(1, 2, 3))
assert.is_true(vector.new(1, 2, 3):equals(vector.new(1, 2, 3)))
assert.not_equal(vector.new(1, 2, 3), vector.new(1, 2, 4))
assert.is_true(vector.new(1, 2, 3) == vector.new(1, 2, 3))
assert.is_false(vector.new(1, 2, 3) == vector.new(1, 3, 3))
end)
it("metatable is same", function()
local a = vector.new()
local b = vector.new(1, 2, 3)
assert.equal(true, vector.check(a))
assert.equal(true, vector.check(b))
assert.equal(vector.metatable, getmetatable(a))
assert.equal(vector.metatable, getmetatable(b))
assert.equal(vector.metatable, a.metatable)
end)
it("sort()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(0.5, 232, -2)
local sorted = {vector.new(0.5, 2, -2), vector.new(1, 232, 3)}
assert.same(sorted, {vector.sort(a, b)})
assert.same(sorted, {a:sort(b)})
end)
it("angle()", function()
assert.equal(math.pi, vector.angle(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
assert.equal(math.pi/2, vector.new(0, 1, 0):angle(vector.new(1, 0, 0)))
end)
it("dot()", function()
assert.equal(-14, vector.dot(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
assert.equal(0, vector.new():dot(vector.new(1, 2, 3)))
end)
it("cross()", function()
local a = vector.new(-1, -2, 0)
local b = vector.new(1, 2, 3)
assert.equal(vector.new(-6, 3, 0), vector.cross(a, b))
assert.equal(vector.new(-6, 3, 0), a:cross(b))
end)
it("offset()", function()
assert.same({x = 41, y = 52, z = 63}, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
assert.equal(vector.new(41, 52, 63), vector.offset(vector.new(1, 2, 3), 40, 50, 60))
assert.equal(vector.new(41, 52, 63), vector.new(1, 2, 3):offset(40, 50, 60))
end)
it("is()", function()
local some_table1 = {foo = 13, [42] = 1, "bar", 2}
local some_table2 = {1, 2, 3}
local some_table3 = {x = 1, 2, 3}
local some_table4 = {1, 2, z = 3}
local old = {x = 1, y = 2, z = 3}
local real = vector.new(1, 2, 3)
assert.is_false(vector.check(nil))
assert.is_false(vector.check(1))
assert.is_false(vector.check(true))
assert.is_false(vector.check("foo"))
assert.is_false(vector.check(some_table1))
assert.is_false(vector.check(some_table2))
assert.is_false(vector.check(some_table3))
assert.is_false(vector.check(some_table4))
assert.is_false(vector.check(old))
assert.is_true(vector.check(real))
assert.is_true(real:check())
end)
it("global pairs", function()
local out = {}
local vec = vector.new(10, 20, 30)
for k, v in pairs(vec) do
out[k] = v
end
assert.same({x = 10, y = 20, z = 30}, out)
end)
it("abusing works", function()
local v = vector.new(1, 2, 3)
v.a = 1
assert.equal(1, v.a)
local a_is_there = false
for key, value in pairs(v) do
if key == "a" then
a_is_there = true
assert.equal(value, 1)
break
it("equal()", function()
local function assertE(a, b)
assert.is_true(vector.equals(a, b))
end
end
assert.is_true(a_is_there)
local function assertNE(a, b)
assert.is_false(vector.equals(a, b))
end
assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
local a = { x = 2, y = 4, z = -10 }
assertE(a, a)
assertNE({x = -1, y = 0, z = 1}, a)
end)
it("add()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(1, 4, 3)
local c = vector.new(2, 6, 6)
assert.equal(c, vector.add(a, {x = 1, y = 4, z = 3}))
assert.equal(c, vector.add(a, b))
assert.equal(c, a:add(b))
assert.equal(c, a + b)
assert.equal(c, b + a)
assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 }))
end)
it("subtract()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(2, 4, 3)
local c = vector.new(-1, -2, 0)
assert.equal(c, vector.subtract(a, {x = 2, y = 4, z = 3}))
assert.equal(c, vector.subtract(a, b))
assert.equal(c, a:subtract(b))
assert.equal(c, a - b)
assert.equal(c, -b + a)
end)
it("multiply()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(2, 4, 3)
local c = vector.new(2, 8, 9)
local s = 2
local d = vector.new(2, 4, 6)
assert.equal(c, vector.multiply(a, {x = 2, y = 4, z = 3}))
assert.equal(c, vector.multiply(a, b))
assert.equal(d, vector.multiply(a, s))
assert.equal(d, a:multiply(s))
assert.equal(d, a * s)
assert.equal(d, s * a)
assert.equal(-a, -1 * a)
end)
it("divide()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(2, 4, 3)
local c = vector.new(0.5, 0.5, 1)
local s = 2
local d = vector.new(0.5, 1, 1.5)
assert.equal(c, vector.divide(a, {x = 2, y = 4, z = 3}))
assert.equal(c, vector.divide(a, b))
assert.equal(d, vector.divide(a, s))
assert.equal(d, a:divide(s))
assert.equal(d, a / s)
assert.equal(d, 1/s * a)
assert.equal(-a, a / -1)
end)
it("to_string()", function()
local v = vector.new(1, 2, 3.14)
assert.same("(1, 2, 3.14)", vector.to_string(v))
assert.same("(1, 2, 3.14)", v:to_string())
assert.same("(1, 2, 3.14)", tostring(v))
end)
it("from_string()", function()
local v = vector.new(1, 2, 3.14)
assert.is_true(vector.check(vector.from_string("(1, 2, 3.14)")))
assert.same({v, 13}, {vector.from_string("(1, 2, 3.14)")})
assert.same({v, 12}, {vector.from_string("(1,2 ,3.14)")})
assert.same({v, 12}, {vector.from_string("(1,2,3.14,)")})
assert.same({v, 11}, {vector.from_string("(1 2 3.14)")})
assert.same({v, 15}, {vector.from_string("( 1, 2, 3.14 )")})
assert.same({v, 15}, {vector.from_string(" ( 1, 2, 3.14) ")})
assert.same({vector.new(), 8}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ")})
assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 8)})
assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 9)})
assert.same(nil, vector.from_string("nothing"))
it("offset()", function()
assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
end)
-- This function is needed because of floating point imprecision.

View File

@@ -1,134 +1,73 @@
--[[
Vector helpers
Note: The vector.*-functions must be able to accept old vectors that had no metatables
]]
-- localize functions
local setmetatable = setmetatable
vector = {}
local metatable = {}
vector.metatable = metatable
local xyz = {"x", "y", "z"}
-- only called when rawget(v, key) returns nil
function metatable.__index(v, key)
return rawget(v, xyz[key]) or vector[key]
end
-- only called when rawget(v, key) returns nil
function metatable.__newindex(v, key, value)
rawset(v, xyz[key] or key, value)
end
-- constructors
local function fast_new(x, y, z)
return setmetatable({x = x, y = y, z = z}, metatable)
end
function vector.new(a, b, c)
if a and b and c then
return fast_new(a, b, c)
end
-- deprecated, use vector.copy and vector.zero directly
if type(a) == "table" then
return vector.copy(a)
else
assert(not a, "Invalid arguments for vector.new()")
return vector.zero()
assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
return {x=a.x, y=a.y, z=a.z}
elseif a then
assert(b and c, "Invalid arguments for vector.new()")
return {x=a, y=b, z=c}
end
return {x=0, y=0, z=0}
end
function vector.zero()
return fast_new(0, 0, 0)
end
function vector.copy(v)
assert(v.x and v.y and v.z, "Invalid vector passed to vector.copy()")
return fast_new(v.x, v.y, v.z)
end
function vector.from_string(s, init)
local x, y, z, np = string.match(s, "^%s*%(%s*([^%s,]+)%s*[,%s]%s*([^%s,]+)%s*[,%s]" ..
"%s*([^%s,]+)%s*[,%s]?%s*%)()", init)
x = tonumber(x)
y = tonumber(y)
z = tonumber(z)
if not (x and y and z) then
return nil
end
return fast_new(x, y, z), np
end
function vector.to_string(v)
return string.format("(%g, %g, %g)", v.x, v.y, v.z)
end
metatable.__tostring = vector.to_string
function vector.equals(a, b)
return a.x == b.x and
a.y == b.y and
a.z == b.z
end
metatable.__eq = vector.equals
-- unary operations
function vector.length(v)
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
return math.hypot(v.x, math.hypot(v.y, v.z))
end
-- Note: we cannot use __len because it is already used for primitive table length
function vector.normalize(v)
local len = vector.length(v)
if len == 0 then
return fast_new(0, 0, 0)
return {x=0, y=0, z=0}
else
return vector.divide(v, len)
end
end
function vector.floor(v)
return vector.apply(v, math.floor)
return {
x = math.floor(v.x),
y = math.floor(v.y),
z = math.floor(v.z)
}
end
function vector.round(v)
return fast_new(
math.round(v.x),
math.round(v.y),
math.round(v.z)
)
return {
x = math.floor(v.x + 0.5),
y = math.floor(v.y + 0.5),
z = math.floor(v.z + 0.5)
}
end
function vector.apply(v, func)
return fast_new(
func(v.x),
func(v.y),
func(v.z)
)
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)
)
return {
x = func(v.x),
y = func(v.y),
z = func(v.z)
}
end
function vector.distance(a, b)
local x = a.x - b.x
local y = a.y - b.y
local z = a.z - b.z
return math.sqrt(x * x + y * y + z * z)
return math.hypot(x, math.hypot(y, z))
end
function vector.direction(pos1, pos2)
return vector.subtract(pos2, pos1):normalize()
return vector.normalize({
x = pos2.x - pos1.x,
y = pos2.y - pos1.y,
z = pos2.z - pos1.z
})
end
function vector.angle(a, b)
@@ -143,137 +82,70 @@ function vector.dot(a, b)
end
function vector.cross(a, b)
return fast_new(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
)
return {
x = a.y * b.z - a.z * b.y,
y = a.z * b.x - a.x * b.z,
z = a.x * b.y - a.y * b.x
}
end
function metatable.__unm(v)
return fast_new(-v.x, -v.y, -v.z)
end
-- add, sub, mul, div operations
function vector.add(a, b)
if type(b) == "table" then
return fast_new(
a.x + b.x,
a.y + b.y,
a.z + b.z
)
return {x = a.x + b.x,
y = a.y + b.y,
z = a.z + b.z}
else
return fast_new(
a.x + b,
a.y + b,
a.z + b
)
return {x = a.x + b,
y = a.y + b,
z = a.z + b}
end
end
function metatable.__add(a, b)
return fast_new(
a.x + b.x,
a.y + b.y,
a.z + b.z
)
end
function vector.subtract(a, b)
if type(b) == "table" then
return fast_new(
a.x - b.x,
a.y - b.y,
a.z - b.z
)
return {x = a.x - b.x,
y = a.y - b.y,
z = a.z - b.z}
else
return fast_new(
a.x - b,
a.y - b,
a.z - b
)
return {x = a.x - b,
y = a.y - b,
z = a.z - b}
end
end
function metatable.__sub(a, b)
return fast_new(
a.x - b.x,
a.y - b.y,
a.z - b.z
)
end
function vector.multiply(a, b)
if type(b) == "table" then
return fast_new(
a.x * b.x,
a.y * b.y,
a.z * b.z
)
return {x = a.x * b.x,
y = a.y * b.y,
z = a.z * b.z}
else
return fast_new(
a.x * b,
a.y * b,
a.z * b
)
end
end
function metatable.__mul(a, b)
if type(a) == "table" then
return fast_new(
a.x * b,
a.y * b,
a.z * b
)
else
return fast_new(
a * b.x,
a * b.y,
a * b.z
)
return {x = a.x * b,
y = a.y * b,
z = a.z * b}
end
end
function vector.divide(a, b)
if type(b) == "table" then
return fast_new(
a.x / b.x,
a.y / b.y,
a.z / b.z
)
return {x = a.x / b.x,
y = a.y / b.y,
z = a.z / b.z}
else
return fast_new(
a.x / b,
a.y / b,
a.z / b
)
return {x = a.x / b,
y = a.y / b,
z = a.z / b}
end
end
function metatable.__div(a, b)
-- scalar/vector makes no sense
return fast_new(
a.x / b,
a.y / b,
a.z / b
)
end
-- misc stuff
function vector.offset(v, x, y, z)
return fast_new(
v.x + x,
v.y + y,
v.z + z
)
return {x = v.x + x,
y = v.y + y,
z = v.z + z}
end
function vector.sort(a, b)
return fast_new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z)),
fast_new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
end
function vector.check(v)
return getmetatable(v) == metatable
return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
{x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
end
local function sin(x)
@@ -341,7 +213,7 @@ end
function vector.dir_to_rotation(forward, up)
forward = vector.normalize(forward)
local rot = vector.new(math.asin(forward.y), -math.atan2(forward.x, forward.z), 0)
local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0}
if not up then
return rot
end
@@ -349,14 +221,14 @@ function vector.dir_to_rotation(forward, up)
"Invalid vectors passed to vector.dir_to_rotation().")
up = vector.normalize(up)
-- Calculate vector pointing up with roll = 0, just based on forward vector.
local forwup = vector.rotate(vector.new(0, 1, 0), rot)
local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot)
-- 'forwup' and 'up' are now in a plane with 'forward' as normal.
-- The angle between them is the absolute of the roll value we're looking for.
rot.z = vector.angle(forwup, up)
-- Since vector.angle never returns a negative value or a value greater
-- than math.pi, rot.z has to be inverted sometimes.
-- To determine whether this is the case, we rotate the up vector back around
-- To determine wether this is the case, we rotate the up vector back around
-- the forward vector and check if it worked out.
local back = vector.rotate_around_axis(up, forward, -rot.z)
@@ -368,22 +240,3 @@ function vector.dir_to_rotation(forward, up)
end
return rot
end
if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then
local function read_vector(v)
return v.x, v.y, v.z
end
core.set_read_vector(read_vector)
core.set_read_vector = nil
if rawget(_G, "jit") then
-- This is necessary to prevent trace aborts.
local function push_vector(x, y, z)
return (fast_new(x, y, z))
end
core.set_push_vector(push_vector)
else
core.set_push_vector(fast_new)
end
core.set_push_vector = nil
end

View File

@@ -58,20 +58,26 @@ end
--------------------------------------------------------------------------------
local function get_formspec(self)
if self.hidden or (self.parent ~= nil and self.parent.hidden) then
return ""
local formspec = ""
if not self.hidden and (self.parent == nil or not self.parent.hidden) then
if self.parent == nil then
local tsize = self.tablist[self.last_tab_index].tabsize or
{width=self.width, height=self.height}
formspec = formspec ..
string.format("size[%f,%f,%s]",tsize.width,tsize.height,
dump(self.fixed_size))
end
formspec = formspec .. self:tab_header()
formspec = formspec ..
self.tablist[self.last_tab_index].get_formspec(
self,
self.tablist[self.last_tab_index].name,
self.tablist[self.last_tab_index].tabdata,
self.tablist[self.last_tab_index].tabsize
)
end
local tab = self.tablist[self.last_tab_index]
local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize)
if self.parent == nil and not prepend then
local tsize = tab.tabsize or {width=self.width, height=self.height}
prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height,
dump(self.fixed_size))
end
local formspec = (prepend or "") .. self:tab_header() .. content
return formspec
end
@@ -91,9 +97,14 @@ local function handle_buttons(self,fields)
return true
end
local tab = self.tablist[self.last_tab_index]
if tab.button_handler ~= nil then
return tab.button_handler(self, fields, tab.name, tab.tabdata)
if self.tablist[self.last_tab_index].button_handler ~= nil then
return
self.tablist[self.last_tab_index].button_handler(
self,
fields,
self.tablist[self.last_tab_index].name,
self.tablist[self.last_tab_index].tabdata
)
end
return false
@@ -111,9 +122,14 @@ local function handle_events(self,event)
return true
end
local tab = self.tablist[self.last_tab_index]
if tab.evt_handler ~= nil then
return tab.evt_handler(self, event, tab.name, tab.tabdata)
if self.tablist[self.last_tab_index].evt_handler ~= nil then
return
self.tablist[self.last_tab_index].evt_handler(
self,
event,
self.tablist[self.last_tab_index].name,
self.tablist[self.last_tab_index].tabdata
)
end
return false

View File

@@ -63,7 +63,7 @@ function ui.update()
-- handle errors
if gamedata ~= nil and gamedata.reconnect_requested then
local error_message = core.formspec_escape(
gamedata.errormessage or fgettext("<none available>"))
gamedata.errormessage or "<none available>")
formspec = {
"size[14,8]",
"real_coordinates[true]",

View File

@@ -1,22 +0,0 @@
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

View File

@@ -87,10 +87,6 @@ core.builtin_auth_handler = {
core.settings:get("default_password")))
end
auth_entry.privileges = privileges
core_auth.save(auth_entry)
-- Run grant callbacks
for priv, _ in pairs(privileges) do
if not auth_entry.privileges[priv] then
@@ -104,6 +100,9 @@ core.builtin_auth_handler = {
core.run_priv_callbacks(name, priv, nil, "revoke")
end
end
auth_entry.privileges = privileges
core_auth.save(auth_entry)
core.notify_authentication_modified(name)
end,
reload = function()

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
core.register_entity(":__builtin:falling_node", {
initial_properties = {
visual = "item",
visual_size = vector.new(SCALE, SCALE, SCALE),
visual_size = {x = SCALE, y = SCALE, z = SCALE},
textures = {},
physical = true,
is_visible = false,
@@ -84,6 +84,9 @@ core.register_entity(":__builtin:falling_node", {
local textures
if def.tiles and def.tiles[1] then
local tile = def.tiles[1]
if def.drawtype == "torchlike" and def.paramtype2 ~= "wallmounted" then
tile = def.tiles[2] or def.tiles[1]
end
if type(tile) == "table" then
tile = tile.name
end
@@ -96,7 +99,7 @@ core.register_entity(":__builtin:falling_node", {
local vsize
if def.visual_scale then
local s = def.visual_scale
vsize = vector.new(s, s, s)
vsize = {x = s, y = s, z = s}
end
self.object:set_properties({
is_visible = true,
@@ -111,21 +114,15 @@ core.register_entity(":__builtin:falling_node", {
itemstring = core.itemstring_with_palette(itemstring, node.param2)
end
-- FIXME: solution needed for paramtype2 == "leveled"
-- Calculate size of falling node
local s = {}
s.x = (def.visual_scale or 1) * SCALE
s.y = s.x
s.z = s.x
-- Compensate for wield_scale
if def.wield_scale then
s.x = s.x / def.wield_scale.x
s.y = s.y / def.wield_scale.y
s.z = s.z / def.wield_scale.z
local vsize
if def.visual_scale then
local s = def.visual_scale * SCALE
vsize = {x = s, y = s, z = s}
end
self.object:set_properties({
is_visible = true,
wield_item = itemstring,
visual_size = s,
visual_size = vsize,
glow = def.light_source,
})
end
@@ -150,7 +147,11 @@ core.register_entity(":__builtin:falling_node", {
-- Rotate entity
if def.drawtype == "torchlike" then
self.object:set_yaw(math.pi*0.25)
if def.paramtype2 == "wallmounted" then
self.object:set_yaw(math.pi*0.25)
else
self.object:set_yaw(-math.pi*0.25)
end
elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
and (def.wield_image == "" or def.wield_image == nil))
or def.drawtype == "signlike"
@@ -158,26 +159,14 @@ 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 % 24
local fdir = node.param2 % 32
-- Get rotation from a precalculated lookup table
local euler = facedir_to_euler[fdir + 1]
if euler then
self.object:set_rotation(euler)
end
elseif (def.paramtype2 == "4dir" or def.paramtype2 == "color4dir") then
local fdir = node.param2 % 4
-- Get rotation from a precalculated lookup table
local euler = facedir_to_euler[fdir + 1]
if euler then
self.object:set_rotation(euler)
end
elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and
(def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then
elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") then
local rot = node.param2 % 8
if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then
-- Change rotation to "floor" by default for non-wallmounted paramtype2
rot = 1
end
local pitch, yaw, roll = 0, 0, 0
if def.drawtype == "nodebox" or def.drawtype == "mesh" then
if rot == 0 then
@@ -219,14 +208,6 @@ core.register_entity(":__builtin:falling_node", {
end
end
self.object:set_rotation({x=pitch, y=yaw, z=roll})
elseif (def.drawtype == "mesh" and def.paramtype2 == "degrotate") then
local p2 = (node.param2 - (def.place_param2 or 0)) % 240
local yaw = (p2 / 240) * (math.pi * 2)
self.object:set_yaw(yaw)
elseif (def.drawtype == "mesh" and def.paramtype2 == "colordegrotate") then
local p2 = (node.param2 % 32 - (def.place_param2 or 0) % 32) % 24
local yaw = (p2 / 24) * (math.pi * 2)
self.object:set_yaw(yaw)
end
end
end,
@@ -241,7 +222,7 @@ core.register_entity(":__builtin:falling_node", {
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
self.object:set_acceleration(vector.new(0, -gravity, 0))
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
local ds = core.deserialize(staticdata)
if ds and ds.node then
@@ -271,7 +252,7 @@ core.register_entity(":__builtin:falling_node", {
end
-- Decide if we're replacing the node or placing on top
local np = vector.copy(bcp)
local np = vector.new(bcp)
if bcd and bcd.buildable_to and
(not self.floats or bcd.liquidtype == "none") then
core.remove_node(bcp)
@@ -317,7 +298,7 @@ core.register_entity(":__builtin:falling_node", {
if self.floats then
local pos = self.object:get_pos()
local bcp = pos:offset(0, -0.7, 0):round()
local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z})
local bcn = core.get_node(bcp)
local bcd = core.registered_nodes[bcn.name]
@@ -358,12 +339,13 @@ core.register_entity(":__builtin:falling_node", {
-- TODO: this hack could be avoided in the future if objects
-- could choose who to collide with
local vel = self.object:get_velocity()
self.object:set_velocity(vector.new(
vel.x,
player_collision.old_velocity.y,
vel.z
))
self.object:set_pos(self.object:get_pos():offset(0, -0.5, 0))
self.object:set_velocity({
x = vel.x,
y = player_collision.old_velocity.y,
z = vel.z
})
self.object:set_pos(vector.add(self.object:get_pos(),
{x = 0, y = -0.5, z = 0}))
end
return
elseif bcn.name == "ignore" then
@@ -425,7 +407,7 @@ local function convert_to_falling_node(pos, node)
obj:get_luaentity():set_node(node, metatable)
core.remove_node(pos)
return true, obj
return true
end
function core.spawn_falling_node(pos)
@@ -443,7 +425,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.copy(p)
local pos_copy = {x=p.x, y=p.y, z=p.z}
local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@@ -466,40 +448,16 @@ local function drop_attached_node(p)
end
end
function builtin_shared.check_attached_node(p, n, group_rating)
function builtin_shared.check_attached_node(p, n)
local def = core.registered_nodes[n.name]
local d = vector.zero()
if group_rating == 3 then
-- always attach to floor
d.y = -1
elseif group_rating == 4 then
-- always attach to ceiling
d.y = 1
elseif group_rating == 2 then
-- attach to facedir or 4dir direction
if (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir") then
-- Attach to whatever facedir is "mounted to".
-- For facedir, this is where tile no. 5 point at.
-- The fallback vector here is in case 'facedir to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for facedir.
-- The fallback vector corresponds to param2 = 0.
d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
elseif (def.paramtype2 == "4dir" or
def.paramtype2 == "color4dir") then
-- Similar to facedir handling
d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
end
elseif def.paramtype2 == "wallmounted" or
local d = {x = 0, y = 0, z = 0}
if def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted" then
-- Attach to whatever this node is "mounted to".
-- This where tile no. 2 points at.
-- The fallback vector here is used for the same reason as
-- for facedir nodes.
d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
-- The fallback vector here is in case 'wallmounted to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for wallmounted.
-- The fallback vector corresponds to param2 = 0.
d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0}
else
d.y = -1
end
@@ -519,7 +477,7 @@ end
function core.check_single_for_falling(p)
local n = core.get_node(p)
if core.get_item_group(n.name, "falling_node") ~= 0 then
local p_bottom = vector.offset(p, 0, -1, 0)
local p_bottom = {x = p.x, y = p.y - 1, z = p.z}
-- Only spawn falling node if node below is loaded
local n_bottom = core.get_node_or_nil(p_bottom)
local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
@@ -543,9 +501,8 @@ function core.check_single_for_falling(p)
end
end
local an = core.get_item_group(n.name, "attached_node")
if an ~= 0 then
if not builtin_shared.check_attached_node(p, n, an) then
if core.get_item_group(n.name, "attached_node") ~= 0 then
if not builtin_shared.check_attached_node(p, n) then
drop_attached_node(p)
return true
end
@@ -559,17 +516,17 @@ end
-- Down first as likely case, but always before self. The same with sides.
-- Up must come last, so that things above self will also fall all at once.
local check_for_falling_neighbors = {
vector.new(-1, -1, 0),
vector.new( 1, -1, 0),
vector.new( 0, -1, -1),
vector.new( 0, -1, 1),
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),
vector.new( 0, 0, 0),
vector.new( 0, 1, 0),
{x = -1, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = 0, y = -1, z = -1},
{x = 0, y = -1, z = 1},
{x = 0, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
{x = 0, y = 0, z = 0},
{x = 0, y = 1, z = 0},
}
function core.check_for_falling(p)

View File

@@ -19,14 +19,6 @@ core.features = {
object_step_has_moveresult = true,
direct_velocity_on_players = true,
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,
get_light_data_buffer = true,
mod_storage_on_disk = true,
compress_zstd = true,
}
function core.has_feature(arg)

View File

@@ -33,7 +33,7 @@ local function get_relevant_tables(transient)
end
end
function core.forceload_block(pos, transient, limit)
function core.forceload_block(pos, transient)
-- set changed flag
forceload_blocks_changed = true
@@ -46,8 +46,7 @@ function core.forceload_block(pos, transient, limit)
elseif other_table[hash] ~= nil then
relevant_table[hash] = 1
else
limit = limit or tonumber(core.settings:get("max_forceloaded_blocks")) or 16
if limit >= 0 and total_forceloaded >= limit then
if total_forceloaded >= (tonumber(core.settings:get("max_forceloaded_blocks")) or 16) then
return false
end
total_forceloaded = total_forceloaded+1
@@ -87,6 +86,12 @@ local function read_file(filename)
return core.deserialize(t) or {}
end
local function write_file(filename, table)
local f = io.open(filename, "w")
f:write(core.serialize(table))
f:close()
end
blocks_forceloaded = read_file(wpath.."/force_loaded.txt")
for _, __ in pairs(blocks_forceloaded) do
total_forceloaded = total_forceloaded + 1
@@ -101,8 +106,7 @@ end)
-- persists the currently forceloaded blocks to disk
local function persist_forceloaded_blocks()
local data = core.serialize(blocks_forceloaded)
core.safe_file_write(wpath.."/force_loaded.txt", data)
write_file(wpath.."/force_loaded.txt", blocks_forceloaded)
end
-- periodical forceload persistence

View File

@@ -7,8 +7,9 @@ local gamepath = scriptpath .. "game".. DIR_DELIM
-- not exposed to outer context
local builtin_shared = {}
dofile(commonpath .. "vector.lua")
dofile(gamepath .. "constants.lua")
assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared)
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
dofile(gamepath .. "register.lua")
@@ -17,10 +18,8 @@ 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")
@@ -35,8 +34,5 @@ dofile(gamepath .. "voxelarea.lua")
dofile(gamepath .. "forceloading.lua")
dofile(gamepath .. "statbars.lua")
dofile(gamepath .. "knockback.lua")
dofile(gamepath .. "async.lua")
core.after(0, builtin_shared.cache_content_ids)
profiler = nil

View File

@@ -5,8 +5,8 @@ local builtin_shared = ...
local function copy_pointed_thing(pointed_thing)
return {
type = pointed_thing.type,
above = pointed_thing.above and vector.copy(pointed_thing.above),
under = pointed_thing.under and vector.copy(pointed_thing.under),
above = vector.new(pointed_thing.above),
under = vector.new(pointed_thing.under),
ref = pointed_thing.ref,
}
end
@@ -15,6 +15,15 @@ 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
@@ -28,16 +37,140 @@ function core.get_pointed_thing_position(pointed_thing, above)
end
end
local function has_all_groups(tbl, required_groups)
if type(required_groups) == "string" then
return (tbl[required_groups] or 0) ~= 0
end
for _, group in ipairs(required_groups) do
if (tbl[group] or 0) == 0 then
return false
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
return true
end
-- Table of possible dirs
local facedir_to_dir = {
{x= 0, y=0, z= 1},
{x= 1, y=0, z= 0},
{x= 0, y=0, z=-1},
{x=-1, y=0, z= 0},
{x= 0, y=-1, z= 0},
{x= 0, y=1, z= 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] = {x = 0, y = 1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -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 {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
end
function core.is_colored_paramtype(ptype)
return (ptype == "color") or (ptype == "colorfacedir") or
(ptype == "colorwallmounted")
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
end
-- paramtype2 == "color" requires no modification.
return param2
end
function core.get_node_drops(node, toolname)
@@ -79,7 +212,7 @@ function core.get_node_drops(node, toolname)
if item.rarity ~= nil then
good_rarity = item.rarity < 1 or math.random(item.rarity) == 1
end
if item.tools ~= nil or item.tool_groups ~= nil then
if item.tools ~= nil then
good_tool = false
end
if item.tools ~= nil and toolname then
@@ -94,27 +227,6 @@ function core.get_node_drops(node, toolname)
end
end
end
if item.tool_groups ~= nil and toolname then
local tooldef = core.registered_items[toolname]
if tooldef ~= nil and type(tooldef.groups) == "table" then
if type(item.tool_groups) == "string" then
-- tool_groups can be a string which specifies the required group
good_tool = core.get_item_group(toolname, item.tool_groups) ~= 0
else
-- tool_groups can be a list of sufficient requirements.
-- i.e. if any item in the list can be satisfied then the tool is good
assert(type(item.tool_groups) == "table")
for _, required_groups in ipairs(item.tool_groups) do
-- required_groups can be either a string (a single group),
-- or an array of strings where all must be in tooldef.groups
good_tool = has_all_groups(tooldef.groups, required_groups)
if good_tool then
break
end
end
end
end
end
if good_rarity and good_tool then
got_count = got_count + 1
for _, add_item in ipairs(item.items) do
@@ -176,12 +288,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
end
-- Place above pointed node
local place_to = vector.copy(above)
local place_to = {x = above.x, y = above.y, z = above.z}
-- 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.copy(under)
place_to = {x = under.x, y = under.y, z = under.z}
end
if core.is_protected(place_to, playername) then
@@ -201,16 +313,22 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
newnode.param2 = def.place_param2
elseif (def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted") and not param2 then
local dir = vector.subtract(under, above)
local dir = {
x = under.x - above.x,
y = under.y - above.y,
z = under.z - above.z
}
newnode.param2 = core.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir" or
def.paramtype2 == "4dir" or
def.paramtype2 == "color4dir") and not param2 then
def.paramtype2 == "colorfacedir") and not param2 then
local placer_pos = placer and placer:get_pos()
if placer_pos then
local dir = vector.subtract(above, placer_pos)
local dir = {
x = above.x - placer_pos.x,
y = above.y - placer_pos.y,
z = above.z - placer_pos.z
}
newnode.param2 = core.dir_to_facedir(dir)
log("info", "facedir: " .. newnode.param2)
end
@@ -227,10 +345,6 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
color_divisor = 8
elseif def.paramtype2 == "colorfacedir" then
color_divisor = 32
elseif def.paramtype2 == "color4dir" then
color_divisor = 4
elseif def.paramtype2 == "colordegrotate" then
color_divisor = 32
end
if color_divisor then
local color = math.floor(metatable.palette_index / color_divisor)
@@ -240,11 +354,10 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
end
-- Check if the node is attached and if it can be placed there
local an = core.get_item_group(def.name, "attached_node")
if an ~= 0 and
not builtin_shared.check_attached_node(place_to, newnode, an) then
if core.get_item_group(def.name, "attached_node") ~= 0 and
not builtin_shared.check_attached_node(place_to, newnode) then
log("action", "attached node " .. def.name ..
" cannot be placed at " .. core.pos_to_string(place_to))
" can not be placed at " .. core.pos_to_string(place_to))
return itemstack, nil
end
@@ -267,7 +380,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.copy(place_to)
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if def.after_place_node(place_to_copy, placer, itemstack,
pointed_thing_copy) then
@@ -278,7 +391,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.copy(place_to)
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
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)
@@ -350,64 +463,41 @@ function core.item_drop(itemstack, dropper, pos)
-- environment failed
end
function core.item_pickup(itemstack, picker, pointed_thing, ...)
itemstack = ItemStack(itemstack)
-- Invoke global on_item_pickup callbacks.
for _, callback in ipairs(core.registered_on_item_pickups) do
local result = callback(itemstack, picker, pointed_thing, ...)
if result then
return ItemStack(result)
end
end
-- Pickup item.
local inv = picker and picker:get_inventory()
if inv then
return inv:add_item("main", itemstack)
end
return itemstack
end
function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
for _, callback in ipairs(core.registered_on_item_eats) do
for _, callback in pairs(core.registered_on_item_eats) do
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
if result then
return result
end
end
-- read definition before potentially emptying the stack
local def = itemstack:get_definition()
if itemstack:take_item():is_empty() then
return itemstack
end
if itemstack:take_item() ~= nil then
user:set_hp(user:get_hp() + hp_change)
if def and def.sound and def.sound.eat then
core.sound_play(def.sound.eat, {
pos = user:get_pos(),
max_hear_distance = 16
}, true)
end
if def and def.sound and def.sound.eat then
core.sound_play(def.sound.eat, {
pos = user:get_pos(),
max_hear_distance = 16
}, true)
end
-- Changing hp might kill the player causing mods to do who-knows-what to the
-- inventory, so do this before set_hp().
replace_with_item = itemstack:add_item(replace_with_item)
user:set_wielded_item(itemstack)
if not replace_with_item:is_empty() then
local inv = user:get_inventory()
-- Check if inv is null, since non-players don't have one
if inv then
replace_with_item = inv:add_item("main", replace_with_item)
if replace_with_item then
if itemstack:is_empty() then
itemstack:add_item(replace_with_item)
else
local inv = user:get_inventory()
-- Check if inv is null, since non-players don't have one
if inv and inv:room_for_item("main", {name=replace_with_item}) then
inv:add_item("main", replace_with_item)
else
local pos = user:get_pos()
pos.y = math.floor(pos.y + 0.5)
core.add_item(pos, replace_with_item)
end
end
end
end
if not replace_with_item:is_empty() then
local pos = user:get_pos()
pos.y = math.floor(pos.y + 0.5)
core.add_item(pos, replace_with_item)
end
user:set_hp(user:get_hp() + hp_change)
return nil -- don't overwrite wield item a second time
return itemstack
end
function core.item_eat(hp_change, replace_with_item)
@@ -422,7 +512,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.copy(pos)
local pos_copy = vector.new(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)
@@ -447,11 +537,11 @@ function core.handle_node_drops(pos, drops, digger)
for _, dropped_item in pairs(drops) do
local left = give_item(dropped_item)
if not left:is_empty() then
local p = vector.offset(pos,
math.random()/2-0.25,
math.random()/2-0.25,
math.random()/2-0.25
)
local p = {
x = pos.x + math.random()/2-0.25,
y = pos.y + math.random()/2-0.25,
z = pos.z + math.random()/2-0.25,
}
core.add_item(p, left)
end
end
@@ -463,7 +553,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.copy(pos), digger))) then
(def.can_dig and not def.can_dig(vector.new(pos), digger))) then
log("info", diggername .. " tried to dig "
.. node.name .. " which is not diggable "
.. core.pos_to_string(pos))
@@ -488,7 +578,7 @@ function core.node_dig(pos, node, digger)
if wielded then
local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities()
local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear())
local dp = core.get_dig_params(def and def.groups, tp)
if wdef and wdef.after_use then
wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else
@@ -510,7 +600,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.copy(pos)
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@@ -542,7 +632,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.copy(pos)
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
end
@@ -550,10 +640,12 @@ function core.node_dig(pos, node, digger)
-- Run script hook
for _, callback in ipairs(core.registered_on_dignodes) do
local origin = core.callback_origins[callback]
core.set_last_run_mod(origin.mod)
if origin then
core.set_last_run_mod(origin.mod)
end
-- Copy pos and node because callback can modify them
local pos_copy = vector.copy(pos)
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
callback(pos_copy, node_copy, digger)
end
@@ -596,7 +688,7 @@ core.nodedef_default = {
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),
wield_scale = {x=1,y=1,z=1},
stack_max = default_stack_max,
usable = false,
liquids_pointable = false,
@@ -606,7 +698,6 @@ core.nodedef_default = {
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
on_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup
on_use = nil,
can_dig = nil,
@@ -619,8 +710,13 @@ core.nodedef_default = {
-- Node properties
drawtype = "normal",
visual_scale = 1.0,
tiles = nil,
special_tiles = nil,
-- 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},
--},
post_effect_color = {a=0, r=0, g=0, b=0},
paramtype = "none",
paramtype2 = "none",
@@ -651,7 +747,7 @@ core.craftitemdef_default = {
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),
wield_scale = {x=1,y=1,z=1},
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
@@ -659,7 +755,6 @@ core.craftitemdef_default = {
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
on_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup
on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_use = nil,
}
@@ -671,7 +766,7 @@ core.tooldef_default = {
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),
wield_scale = {x=1,y=1,z=1},
stack_max = 1,
liquids_pointable = false,
tool_capabilities = nil,
@@ -680,7 +775,6 @@ core.tooldef_default = {
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
on_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup
on_use = nil,
}
@@ -691,15 +785,14 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),
wield_scale = {x=1,y=1,z=1},
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_place = redef_wrapper(core, 'item_place'),
on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup
on_drop = nil,
on_use = nil,
}

View File

@@ -58,21 +58,17 @@ 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 + size_bias, y = size + size_bias},
collisionbox = c,
visual_size = {x = size, y = size},
collisionbox = {-size, -size, -size, size, size, size},
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)
@@ -97,13 +93,12 @@ 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,
try_merge_with = function(self, own_stack, object, entity)
if self.age == entity.age then
-- Cannot merge with itself
-- Can not merge with itself
return false
end
@@ -113,7 +108,7 @@ core.register_entity(":__builtin:item", {
own_stack:get_meta() ~= stack:get_meta() or
own_stack:get_wear() ~= stack:get_wear() or
own_stack:get_free_space() == 0 then
-- Cannot merge different or full stack
-- Can not merge different or full stack
return false
end
@@ -168,7 +163,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._collisionbox[2] - 0.05,
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
z = pos.z
})
-- Delete in 'ignore' nodes
@@ -181,7 +176,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._collisionbox
local c = self.object:get_properties().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
@@ -318,29 +313,16 @@ core.register_entity(":__builtin:item", {
end
end,
on_punch = function(self, hitter, ...)
if self.itemstring == "" then
self.object:remove()
return
end
-- Call on_pickup callback in item definition.
local itemstack = ItemStack(self.itemstring)
local callback = itemstack:get_definition().on_pickup
local ret = callback(itemstack, hitter, {type = "object", ref = self.object}, ...)
if not ret then
-- Don't modify (and don't reset rotation)
return
end
itemstack = ItemStack(ret)
-- Handle the leftover itemstack
if itemstack:is_empty() then
self.itemstring = ""
self.object:remove()
else
self:set_item(itemstack)
on_punch = function(self, hitter)
local inv = hitter:get_inventory()
if inv and self.itemstring ~= "" then
local left = inv:add_item("main", self.itemstring)
if left and not left:is_empty() then
self:set_item(left)
return
end
end
self.itemstring = ""
self.object:remove()
end,
})

View File

@@ -1,4 +1,4 @@
-- can be overridden by mods
-- can be overriden by mods
function core.calculate_knockback(player, hitter, time_from_last_punch, tool_capabilities, dir, distance, damage)
if damage == 0 or player:get_armor_groups().immortal then
return 0.0

View File

@@ -1,21 +1,9 @@
-- Minetest: builtin/misc.lua
local S = core.get_translator("__builtin")
--
-- Misc. API functions
--
-- @spec core.kick_player(String, String) :: Boolean
function core.kick_player(player_name, reason)
if type(reason) == "string" then
reason = "Kicked: " .. reason
else
reason = "Kicked."
end
return core.disconnect_player(player_name, reason)
end
function core.check_player_privs(name, ...)
if core.is_player(name) then
name = name:get_player_name()
@@ -54,15 +42,15 @@ end
function core.send_join_message(player_name)
if not core.is_singleplayer() then
core.chat_send_all("*** " .. S("@1 joined the game.", player_name))
core.chat_send_all("*** " .. player_name .. " joined the game.")
end
end
function core.send_leave_message(player_name, timed_out)
local announcement = "*** " .. S("@1 left the game.", player_name)
local announcement = "*** " .. player_name .. " left the game."
if timed_out then
announcement = "*** " .. S("@1 left the game (timed out).", player_name)
announcement = announcement .. " (timed out)"
end
core.chat_send_all(announcement)
end
@@ -121,7 +109,55 @@ function core.get_player_radius_area(player_name, radius)
end
-- To be overridden by protection mods
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 pos = {}
pos.x = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
pos.y = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
pos.z = (hash % 65536) - 32768
return pos
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)
return false
@@ -177,7 +213,7 @@ function core.is_area_protected(minp, maxp, player_name, interval)
local y = math.floor(yf + 0.5)
for xf = minp.x, maxp.x, d.x do
local x = math.floor(xf + 0.5)
local pos = vector.new(x, y, z)
local pos = {x = x, y = y, z = z}
if core.is_protected(pos, player_name) then
return pos
end
@@ -203,7 +239,7 @@ end
-- HTTP callback interface
core.set_http_api_lua(function(httpenv)
function core.http_add_fetch(httpenv)
httpenv.fetch = function(req, callback)
local handle = httpenv.fetch_async(req)
@@ -219,8 +255,7 @@ core.set_http_api_lua(function(httpenv)
end
return httpenv
end)
core.set_http_api_lua = nil
end
function core.close_formspec(player_name, formname)
@@ -233,34 +268,24 @@ function core.cancel_shutdown_requests()
end
-- Used for callback handling with dynamic_add_media
core.dynamic_media_callbacks = {}
-- Callback handling for dynamic_add_media
-- Transfer of certain globals into async environment
-- see builtin/async/game.lua for the other side
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
local dynamic_add_media_raw = core.dynamic_add_media_raw
core.dynamic_add_media_raw = nil
function core.dynamic_add_media(filepath, callback)
local ret = dynamic_add_media_raw(filepath)
if ret == false then
return ret
end
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_
if callback == nil then
core.log("deprecated", "Calling minetest.dynamic_add_media without "..
"a callback is deprecated and will stop working in future versions.")
else
-- At the moment async loading is not actually implemented, so we
-- immediately call the callback ourselves
for _, name in ipairs(ret) do
callback(name)
end
end
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
return true
end

View File

@@ -1,92 +0,0 @@
-- 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
--
-- This must match the implementation in src/script/common/c_converter.h
function core.hash_node_position(pos)
return (pos.z + 0x8000) * 0x100000000 + (pos.y + 0x8000) * 0x10000 + (pos.x + 0x8000)
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

View File

@@ -1,7 +1,5 @@
-- Minetest: builtin/privileges.lua
local S = core.get_translator("__builtin")
--
-- Privileges
--
@@ -17,7 +15,7 @@ function core.register_privilege(name, param)
def.give_to_admin = def.give_to_singleplayer
end
if def.description == nil then
def.description = S("(no description)")
def.description = "(no description)"
end
end
local def
@@ -30,76 +28,71 @@ function core.register_privilege(name, param)
core.registered_privileges[name] = def
end
core.register_privilege("interact", S("Can interact with things and modify the world"))
core.register_privilege("shout", S("Can speak in chat"))
local basic_privs =
core.string_to_privs((core.settings:get("basic_privs") or "shout,interact"))
local basic_privs_desc = S("Can modify basic privileges (@1)",
core.privs_to_string(basic_privs, ', '))
core.register_privilege("basic_privs", basic_privs_desc)
core.register_privilege("privs", S("Can modify privileges"))
core.register_privilege("interact", "Can interact with things and modify the world")
core.register_privilege("shout", "Can speak in chat")
core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
core.register_privilege("privs", "Can modify privileges")
core.register_privilege("teleport", {
description = S("Can teleport self"),
description = "Can teleport self",
give_to_singleplayer = false,
})
core.register_privilege("bring", {
description = S("Can teleport other players"),
description = "Can teleport other players",
give_to_singleplayer = false,
})
core.register_privilege("settime", {
description = S("Can set the time of day using /time"),
description = "Can set the time of day using /time",
give_to_singleplayer = false,
})
core.register_privilege("server", {
description = S("Can do server maintenance stuff"),
description = "Can do server maintenance stuff",
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("protection_bypass", {
description = S("Can bypass node protection in the world"),
description = "Can bypass node protection in the world",
give_to_singleplayer = false,
})
core.register_privilege("ban", {
description = S("Can ban and unban players"),
description = "Can ban and unban players",
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("kick", {
description = S("Can kick players"),
description = "Can kick players",
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("give", {
description = S("Can use /give and /giveme"),
description = "Can use /give and /giveme",
give_to_singleplayer = false,
})
core.register_privilege("password", {
description = S("Can use /setpassword and /clearpassword"),
description = "Can use /setpassword and /clearpassword",
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("fly", {
description = S("Can use fly mode"),
description = "Can use fly mode",
give_to_singleplayer = false,
})
core.register_privilege("fast", {
description = S("Can use fast mode"),
description = "Can use fast mode",
give_to_singleplayer = false,
})
core.register_privilege("noclip", {
description = S("Can fly through solid nodes using noclip mode"),
description = "Can fly through solid nodes using noclip mode",
give_to_singleplayer = false,
})
core.register_privilege("rollback", {
description = S("Can use the rollback functionality"),
description = "Can use the rollback functionality",
give_to_singleplayer = false,
})
core.register_privilege("debug", {
description = S("Can enable wireframe"),
description = "Allows enabling various debug options that may affect gameplay",
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_can_bypass_userlimit(function(name, ip)

View File

@@ -1,6 +1,4 @@
-- Minetest: builtin/register.lua
local S = core.get_translator("__builtin")
-- Minetest: builtin/misc_register.lua
--
-- Make raw registration functions inaccessible to anyone except this file
@@ -79,22 +77,8 @@ local function check_modname_prefix(name)
end
end
local function check_node_list(list, field)
local t = type(list)
if t == "table" then
for _, entry in pairs(list) do
assert(type(entry) == "string",
"Field '" .. field .. "' contains non-string entry")
end
elseif t ~= "string" and t ~= "nil" then
error("Field '" .. field .. "' has invalid type " .. t)
end
end
function core.register_abm(spec)
-- Add to core.registered_abms
check_node_list(spec.nodenames, "nodenames")
check_node_list(spec.neighbors, "neighbors")
assert(type(spec.action) == "function", "Required field 'action' of type function")
core.registered_abms[#core.registered_abms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
@@ -103,7 +87,6 @@ end
function core.register_lbm(spec)
-- Add to core.registered_lbms
check_modname_prefix(spec.name)
check_node_list(spec.nodenames, "nodenames")
assert(type(spec.action) == "function", "Required field 'action' of type function")
core.registered_lbms[#core.registered_lbms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
@@ -318,16 +301,14 @@ end
function core.on_craft(itemstack, player, old_craft_list, craft_inv)
for _, func in ipairs(core.registered_on_crafts) do
-- cast to ItemStack since func() could return a string
itemstack = ItemStack(func(itemstack, player, old_craft_list, craft_inv) or 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
-- cast to ItemStack since func() could return a string
itemstack = ItemStack(func(itemstack, player, old_craft_list, craft_inv) or itemstack)
itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
end
return itemstack
end
@@ -345,7 +326,7 @@ end
core.register_item(":unknown", {
type = "none",
description = S("Unknown Item"),
description = "Unknown Item",
inventory_image = "unknown_item.png",
on_place = core.item_place,
on_secondary_use = core.item_secondary_use,
@@ -355,7 +336,7 @@ core.register_item(":unknown", {
})
core.register_node(":air", {
description = S("Air"),
description = "Air",
inventory_image = "air.png",
wield_image = "air.png",
drawtype = "airlike",
@@ -372,7 +353,7 @@ core.register_node(":air", {
})
core.register_node(":ignore", {
description = S("Ignore"),
description = "Ignore",
inventory_image = "ignore.png",
wield_image = "ignore.png",
drawtype = "airlike",
@@ -385,12 +366,11 @@ core.register_node(":ignore", {
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1},
node_placement_prediction = "",
on_place = function(itemstack, placer, pointed_thing)
core.chat_send_player(
placer:get_player_name(),
core.colorize("#FF0000",
S("You can't place 'ignore' nodes!")))
"You can't place 'ignore' nodes!"))
return ""
end,
})
@@ -420,14 +400,8 @@ function core.override_item(name, redefinition)
register_item_raw(item)
end
do
local default = {mod = "??", name = "??"}
core.callback_origins = setmetatable({}, {
__index = function()
return default
end
})
end
core.callback_origins = {}
function core.run_callbacks(callbacks, mode, ...)
assert(type(callbacks) == "table")
@@ -442,7 +416,9 @@ function core.run_callbacks(callbacks, mode, ...)
local ret = nil
for i = 1, cb_len do
local origin = core.callback_origins[callbacks[i]]
core.set_last_run_mod(origin.mod)
if origin then
core.set_last_run_mod(origin.mod)
end
local cb_ret = callbacks[i](...)
if mode == 0 and i == 1 then
@@ -622,7 +598,6 @@ core.registered_on_crafts, core.register_on_craft = make_registration()
core.registered_craft_predicts, core.register_craft_predict = make_registration()
core.registered_on_protection_violation, core.register_on_protection_violation = make_registration()
core.registered_on_item_eats, core.register_on_item_eat = make_registration()
core.registered_on_item_pickups, core.register_on_item_pickup = make_registration()
core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
core.registered_on_priv_grant, core.register_on_priv_grant = make_registration()
core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration()
@@ -632,18 +607,6 @@ core.registered_on_modchannel_message, core.register_on_modchannel_message = mak
core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration()
core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration()
core.registered_on_mapblocks_changed, core.register_on_mapblocks_changed = make_registration()
core.register_on_mods_loaded(function()
core.after(0, function()
setmetatable(core.registered_on_mapblocks_changed, {
__newindex = function()
error("on_mapblocks_changed callbacks must be registered at load time")
end,
})
end)
end)
--
-- Compatibility for on_mapgen_init()

View File

@@ -1,39 +1,39 @@
-- cache setting
local enable_damage = core.settings:get_bool("enable_damage")
local bar_definitions = {
hp = {
hud_elem_type = "statbar",
position = {x = 0.5, y = 1},
text = "heart.png",
text2 = "heart_gone.png",
number = core.PLAYER_MAX_HP_DEFAULT,
item = core.PLAYER_MAX_HP_DEFAULT,
direction = 0,
size = {x = 24, y = 24},
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
},
breath = {
hud_elem_type = "statbar",
position = {x = 0.5, y = 1},
text = "bubble.png",
text2 = "bubble_gone.png",
number = core.PLAYER_MAX_BREATH_DEFAULT * 2,
item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
direction = 0,
size = {x = 24, y = 24},
offset = {x = 25, y= -(48 + 24 + 16)},
},
local health_bar_definition = {
hud_elem_type = "statbar",
position = {x = 0.5, y = 1},
text = "heart.png",
text2 = "heart_gone.png",
number = core.PLAYER_MAX_HP_DEFAULT,
item = core.PLAYER_MAX_HP_DEFAULT,
direction = 0,
size = {x = 24, y = 24},
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
}
local breath_bar_definition = {
hud_elem_type = "statbar",
position = {x = 0.5, y = 1},
text = "bubble.png",
text2 = "bubble_gone.png",
number = core.PLAYER_MAX_BREATH_DEFAULT,
item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
direction = 0,
size = {x = 24, y = 24},
offset = {x = 25, y= -(48 + 24 + 16)},
}
local hud_ids = {}
local function scaleToHudMax(player, field)
-- Scale "hp" or "breath" to the hud maximum dimensions
local function scaleToDefault(player, field)
-- Scale "hp" or "breath" to the default dimensions
local current = player["get_" .. field](player)
local nominal = bar_definitions[field].item
local max_display = math.max(player:get_properties()[field .. "_max"], current)
return math.ceil(current / max_display * nominal)
local nominal = core["PLAYER_MAX_" .. field:upper() .. "_DEFAULT"]
local max_display = math.max(nominal,
math.max(player:get_properties()[field .. "_max"], current))
return current / max_display * nominal
end
local function update_builtin_statbars(player)
@@ -55,9 +55,9 @@ local function update_builtin_statbars(player)
local immortal = player:get_armor_groups().immortal == 1
if flags.healthbar and enable_damage and not immortal then
local number = scaleToHudMax(player, "hp")
local number = scaleToDefault(player, "hp")
if hud.id_healthbar == nil then
local hud_def = table.copy(bar_definitions.hp)
local hud_def = table.copy(health_bar_definition)
hud_def.number = number
hud.id_healthbar = player:hud_add(hud_def)
else
@@ -73,9 +73,9 @@ local function update_builtin_statbars(player)
local breath = player:get_breath()
local breath_max = player:get_properties().breath_max
if show_breathbar and breath <= breath_max then
local number = scaleToHudMax(player, "breath")
local number = 2 * scaleToDefault(player, "breath")
if not hud.id_breathbar and breath < breath_max then
local hud_def = table.copy(bar_definitions.breath)
local hud_def = table.copy(breath_bar_definition)
hud_def.number = number
hud.id_breathbar = player:hud_add(hud_def)
elseif hud.id_breathbar then
@@ -138,16 +138,14 @@ 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
health_bar_definition = definition
for name, ids in pairs(hud_ids) do
local player = core.get_player_by_name(name)
@@ -161,8 +159,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
breath_bar_definition = definition
for name, ids in pairs(hud_ids) do
local player = core.get_player_by_name(name)

View File

@@ -1,17 +1,11 @@
local math_floor = math.floor
local vector_new = vector.new
VoxelArea = {
MinEdge = vector_new(1, 1, 1),
MaxEdge = vector_new(0, 0, 0),
MinEdge = {x=1, y=1, z=1},
MaxEdge = {x=0, y=0, z=0},
ystride = 0,
zstride = 0,
}
local class_metatable = {}
setmetatable(VoxelArea, class_metatable)
local function new(self, o)
function VoxelArea:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
@@ -23,19 +17,13 @@ local function new(self, o)
return o
end
function class_metatable:__call(MinEdge, MaxEdge)
return new(self, {MinEdge = MinEdge, MaxEdge = MaxEdge})
end
VoxelArea.new = new
function VoxelArea:getExtent()
local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
return vector_new(
MaxEdge.x - MinEdge.x + 1,
MaxEdge.y - MinEdge.y + 1,
MaxEdge.z - MinEdge.z + 1
)
return {
x = MaxEdge.x - MinEdge.x + 1,
y = MaxEdge.y - MinEdge.y + 1,
z = MaxEdge.z - MinEdge.z + 1,
}
end
function VoxelArea:getVolume()
@@ -48,7 +36,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)
@@ -56,23 +44,24 @@ 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
local z = math_floor(i / self.zstride) + MinEdge.z
p.z = math.floor(i / self.zstride) + MinEdge.z
i = i % self.zstride
local y = math_floor(i / self.ystride) + MinEdge.y
p.y = math.floor(i / self.ystride) + MinEdge.y
i = i % self.ystride
local x = math_floor(i) + MinEdge.x
p.x = math.floor(i) + MinEdge.x
return vector_new(x, y, z)
return p
end
function VoxelArea:contains(x, y, z)

View File

@@ -6,25 +6,19 @@
--
-- Initialize some very basic things
function core.error_handler(err, level)
return debug.traceback(tostring(err), level)
end
do
local function concat_args(...)
function core.debug(...) core.log(table.concat({...}, "\t")) end
if core.print then
local core_print = core.print
-- Override native print and use
-- terminal if that's turned on
function print(...)
local n, t = select("#", ...), {...}
for i = 1, n do
t[i] = tostring(t[i])
end
return table.concat(t, "\t")
end
function core.debug(...) core.log(concat_args(...)) end
if core.print then
local core_print = core.print
-- Override native print and use
-- terminal if that's turned on
function print(...) core_print(concat_args(...)) end
core.print = nil -- don't pollute our namespace
core_print(table.concat(t, "\t"))
end
core.print = nil -- don't pollute our namespace
end
math.randomseed(os.time())
minetest = core
@@ -36,7 +30,6 @@ local clientpath = scriptdir .. "client" .. DIR_DELIM
local commonpath = scriptdir .. "common" .. DIR_DELIM
local asyncpath = scriptdir .. "async" .. DIR_DELIM
dofile(commonpath .. "vector.lua")
dofile(commonpath .. "strict.lua")
dofile(commonpath .. "serialize.lua")
dofile(commonpath .. "misc_helpers.lua")
@@ -62,10 +55,8 @@ 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 .. "mainmenu.lua")
elseif INIT == "async_game" then
dofile(asyncpath .. "game.lua")
elseif INIT == "async" then
dofile(asyncpath .. "init.lua")
elseif INIT == "client" then
dofile(clientpath .. "init.lua")
else

View File

@@ -1,246 +0,0 @@
# textdomain: __builtin
Empty command.=Leerer Befehl.
Invalid command: @1=Ungültiger Befehl: @1
Invalid command usage.=Ungültige Befehlsverwendung.
(@1 s)= (@1 s)
Command execution took @1 s=Befehlsausführung brauchte @1 s
You don't have permission to run this command (missing privileges: @1).=Sie haben keine Erlaubnis, diesen Befehl auszuführen (fehlende Privilegien: @1).
Unable to get position of player @1.=Konnte Position vom Spieler @1 nicht ermitteln.
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Ungültiges Gebietsformat. Erwartet: (x1,y1,z1) (x2,y2,z2)
<action>=<Aktion>
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Chataktion zeigen (z.B. wird „/me isst Pizza“ zu „<Spielername> isst Pizza“)
Show the name of the server owner=Den Namen des Servereigentümers zeigen
The administrator of this server is @1.=Der Administrator dieses Servers ist @1.
There's no administrator named in the config file.=In der Konfigurationsdatei wurde kein Administrator angegeben.
@1 does not have any privileges.=@1 hat keine Privilegien.
Privileges of @1: @2=Privilegien von @1: @2
[<name>]=[<Name>]
Show privileges of yourself or another player=Ihre eigenen Privilegien oder die eines anderen Spielers anzeigen
Player @1 does not exist.=Spieler @1 existiert nicht.
<privilege>=<Privileg>
Return list of all online players with privilege=Liste aller Spieler mit einem Privileg ausgeben
Invalid parameters (see /help haspriv).=Ungültige Parameter (siehe „/help haspriv“).
Unknown privilege!=Unbekanntes Privileg!
No online player has the "@1" privilege.=Kein online spielender Spieler hat das „@1“-Privileg.
Players online with the "@1" privilege: @2=Derzeit online spielende Spieler mit dem „@1“-Privileg: @2
Your privileges are insufficient.=Ihre Privilegien sind unzureichend.
Your privileges are insufficient. '@1' only allows you to grant: @2=Ihre Privilegien sind unzureichend. Mit „@1“ können Sie nur folgendes gewähren: @2
Unknown privilege: @1=Unbekanntes Privileg: @1
@1 granted you privileges: @2=@1 gewährte Ihnen Privilegien: @2
<name> (<privilege> [, <privilege2> [<...>]] | all)=<Name> (<Privileg> [, <Privileg2> [<...>]] | all)
Give privileges to player=Privileg an Spieler vergeben
Invalid parameters (see /help grant).=Ungültige Parameter (siehe „/help grant“).
<privilege> [, <privilege2> [<...>]] | all=<Privileg> [, <Privileg2> [<...>]] | all
Grant privileges to yourself=Privilegien an Ihnen selbst vergeben
Invalid parameters (see /help grantme).=Ungültige Parameter (siehe „/help grantme“).
Your privileges are insufficient. '@1' only allows you to revoke: @2=Ihre Privilegien sind unzureichend. Mit „@1“ können Sie nur folgendes entziehen: @2
Note: Cannot revoke in singleplayer: @1=Anmerkung: Im Einzelspielermodus kann man folgendes nicht entziehen: @1
Note: Cannot revoke from admin: @1=Anmerkung: Vom Admin kann man folgendes nicht entziehen: @1
No privileges were revoked.=Es wurden keine Privilegien entzogen.
@1 revoked privileges from you: @2=@1 entfernte Privilegien von Ihnen: @2
Remove privileges from player=Privilegien von Spieler entfernen
Invalid parameters (see /help revoke).=Ungültige Parameter (siehe „/help revoke“).
Revoke privileges from yourself=Privilegien von Ihnen selbst entfernen
Invalid parameters (see /help revokeme).=Ungültige Parameter (siehe „/help revokeme“).
<name> <password>=<Name> <Passwort>
Set player's password (sent unencrypted, thus insecure)=Spielerpasswort setzen (unverschlüsselt gesendet, also unsicher)
Name field required.=Namensfeld benötigt.
Your password was cleared by @1.=Ihr Passwort wurde von @1 geleert.
Password of player "@1" cleared.=Passwort von Spieler „@1“ geleert.
Your password was set by @1.=Ihr Passwort wurde von @1 gesetzt.
Password of player "@1" set.=Passwort von Spieler „@1“ gesetzt.
<name>=<Name>
Set empty password for a player=Leeres Passwort für einen Spieler setzen
Reload authentication data=Authentifizierungsdaten erneut laden
Done.=Fertig.
Failed.=Fehlgeschlagen.
Remove a player's data=Daten eines Spielers löschen
Player "@1" removed.=Spieler „@1“ gelöscht.
No such player "@1" to remove.=Es gibt keinen Spieler „@1“, der gelöscht werden könnte.
Player "@1" is connected, cannot remove.=Spieler „@1“ ist verbunden, er kann nicht gelöscht werden.
Unhandled remove_player return code @1.=Nicht berücksichtigter remove_player-Rückgabewert @1.
Cannot teleport out of map bounds!=Eine Teleportation außerhalb der Kartengrenzen ist nicht möglich!
Cannot get player with name @1.=Spieler mit Namen @1 kann nicht gefunden werden.
Cannot teleport, @1 is attached to an object!=Teleportation nicht möglich, @1 ist an einem Objekt befestigt!
Teleporting @1 to @2.=Teleportation von @1 nach @2
One does not teleport to oneself.=Man teleportiert sich doch nicht zu sich selbst.
Cannot get teleportee with name @1.=Der zu teleportierende Spieler mit Namen @1 kann nicht gefunden werden.
Cannot get target player with name @1.=Zielspieler mit Namen @1 kann nicht gefunden werden.
Teleporting @1 to @2 at @3.=Teleportation von @1 zu @2 bei @3
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <zu_Name> | <Name> <X>,<Y>,<Z> | <Name> <zu_Name>
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>
Invalid parameters (see /help set).=Ungültige Parameter (siehe „/help set“).
Finished emerging @1 blocks in @2ms.=Fertig mit Erzeugung von @1 Blöcken in @2 ms.
emergeblocks update: @1/@2 blocks emerged (@3%)=emergeblocks-Update: @1/@2 Kartenblöcke geladen (@3%)
(here [<radius>]) | (<pos1> <pos2>)=(here [<Radius>]) | (<Pos1> <Pos2>)
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Lade (oder, wenn nicht existent, generiere) Kartenblöcke im Gebiet zwischen Pos1 und Pos2 (<Pos1> und <Pos2> müssen in Klammern stehen)
Started emerge of area ranging from @1 to @2.=Start des Ladevorgangs des Gebiets zwischen @1 und @2.
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Kartenblöcke innerhalb des Gebiets zwischen Pos1 und Pos2 löschen (<Pos1> und <Pos2> müssen in Klammern stehen)
Successfully cleared area ranging from @1 to @2.=Gebiet zwischen @1 und @2 erfolgreich geleert.
Failed to clear one or more blocks in area.=Fehlgeschlagen: Ein oder mehrere Kartenblöcke im Gebiet konnten nicht geleert werden.
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Setzt das Licht im Gebiet zwischen Pos1 und Pos2 zurück (<Pos1> und <Pos2> müssen in Klammern stehen)
Successfully reset light in the area ranging from @1 to @2.=Das Licht im Gebiet zwischen @1 und @2 wurde erfolgreich zurückgesetzt.
Failed to load one or more blocks in area.=Fehlgeschlagen: Ein oder mehrere Kartenblöcke im Gebiet konnten nicht geladen werden.
List mods installed on the server=Installierte Mods auf dem Server auflisten
No mods installed.=Es sind keine Mods installiert.
Cannot give an empty item.=Ein leerer Gegenstand kann nicht gegeben werden.
Cannot give an unknown item.=Ein unbekannter Gegenstand kann nicht gegeben werden.
Giving 'ignore' is not allowed.=„ignore“ darf nicht gegeben werden.
@1 is not a known player.=@1 ist kein bekannter Spieler.
@1 partially added to inventory.=@1 teilweise ins Inventar eingefügt.
@1 could not be added to inventory.=@1 konnte nicht ins Inventar eingefügt werden.
@1 added to inventory.=@1 zum Inventar hinzugefügt.
@1 partially added to inventory of @2.=@1 teilweise ins Inventar von @2 eingefügt.
@1 could not be added to inventory of @2.=@1 konnte nicht ins Inventar von @2 eingefügt werden.
@1 added to inventory of @2.=@1 ins Inventar von @2 eingefügt.
<name> <ItemString> [<count> [<wear>]]=<Name> <ItemString> [<Anzahl> [<Abnutzung>]]
Give item to player=Gegenstand an Spieler geben
Name and ItemString required.=Name und ItemString benötigt.
<ItemString> [<count> [<wear>]]=<ItemString> [<Anzahl> [<Abnutzung>]]
Give item to yourself=Gegenstand Ihnen selbst geben
ItemString required.=ItemString benötigt.
<EntityName> [<X>,<Y>,<Z>]=<EntityName> [<X>,<Y>,<Z>]
Spawn entity at given (or your) position=Entity an angegebener (oder Ihrer eigenen) Position spawnen
EntityName required.=EntityName benötigt.
Unable to spawn entity, player is nil.=Entity konnte nicht gespawnt werden, Spieler ist nil.
Cannot spawn an unknown entity.=Ein unbekanntes Entity kann nicht gespawnt werden.
Invalid parameters (@1).=Ungültige Parameter (@1).
@1 spawned.=@1 gespawnt.
@1 failed to spawn.=@1 konnte nicht gespawnt werden.
Destroy item in hand=Gegenstand in der Hand zerstören
Unable to pulverize, no player.=Konnte nicht pulverisieren, kein Spieler.
Unable to pulverize, no item in hand.=Konnte nicht pulverisieren, kein Gegenstand in der Hand.
An item was pulverized.=Ein Gegenstand wurde pulverisiert.
[<range>] [<seconds>] [<limit>]=[<Reichweite>] [<Sekunden>] [<Limit>]
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Überprüfen, wer als letztes einen Node oder einen Node in der Nähe innerhalb der in <Sekunden> angegebenen Zeitspanne angefasst hat. Standard: Reichweite @= 0, Sekunden @= 86400 @= 24h, Limit @= 5. <Sekunden> auf „inf“ setzen, um Zeitlimit zu deaktivieren.
Rollback functions are disabled.=Rollback-Funktionen sind deaktiviert.
That limit is too high!=Dieses Limit ist zu hoch!
Checking @1 ...=Überprüfe @1 ...
Nobody has touched the specified location in @1 seconds.=Niemand hat die angegebene Position seit @1 Sekunden angefasst.
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 vor @5 Sekunden.
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Hauen Sie einen Node (Reichweite@=@1, Sekunden@=@2, Limit@=@3).
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<Name> [<Sekunden>]) | (:<Akteur> [<Sekunden>])
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Aktionen eines Spielers zurückrollen. Standard für <Sekunden> ist 60. <Sekunden> auf „inf“ setzen, um Zeitlimit zu deaktivieren
Invalid parameters. See /help rollback and /help rollback_check.=Ungültige Parameter. Siehe /help rollback und /help rollback_check.
Reverting actions of player '@1' since @2 seconds.=Die Aktionen des Spielers „@1“ seit @2 Sekunden werden rückgängig gemacht.
Reverting actions of @1 since @2 seconds.=Die Aktionen von @1 seit @2 Sekunden werden rückgängig gemacht.
(log is too long to show)=(Protokoll ist zu lang für die Anzeige)
Reverting actions succeeded.=Die Aktionen wurden erfolgreich rückgängig gemacht.
Reverting actions FAILED.=FEHLGESCHLAGEN: Die Aktionen konnten nicht rückgängig gemacht werden.
Show server status=Serverstatus anzeigen
This command was disabled by a mod or game.=Dieser Befehl wurde von einer Mod oder einem Spiel deaktiviert.
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
Show or set time of day=Tageszeit anzeigen oder setzen
Current time is @1:@2.=Es ist jetzt @1:@2 Uhr.
You don't have permission to run this command (missing privilege: @1).=Sie haben nicht die Erlaubnis, diesen Befehl auszuführen (fehlendes Privileg: @1).
Invalid time (must be between 0 and 24000).=Ungültige Zeit (muss zwischen 0 und 24000 liegen).
Time of day changed.=Tageszeit geändert.
Invalid hour (must be between 0 and 23 inclusive).=Ungültige Stunde (muss zwischen 0 und 23 inklusive liegen).
Invalid minute (must be between 0 and 59 inclusive).=Ungültige Minute (muss zwischen 0 und 59 inklusive liegen).
Show day count since world creation=Anzahl Tage seit der Erschaffung der Welt anzeigen
Current day is @1.=Aktueller Tag ist @1.
[<delay_in_seconds> | -1] [-r] [<message>]=[<Verzögerung_in_Sekunden> | -1] [-r] [<Nachricht>]
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Server herunterfahren (-1 bricht einen verzögerten Abschaltvorgang ab, -r erlaubt Spielern, sich wiederzuverbinden)
Server shutting down (operator request).=Server wird heruntergefahren (Betreiberanfrage).
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.
<name> | <IP_address>=<Name> | <IP_Adresse>
Remove IP ban belonging to a player/IP=Einen IP-Bann auf einen Spieler zurücknehmen
Failed to unban player/IP.=Konnte Bann auf Spieler/IP nicht zurücknehmen.
Unbanned @1.=Bann auf @1 zurückgenommen.
<name> [<reason>]=<Name> [<Grund>]
Kick a player=Spieler hinauswerfen
Failed to kick player @1.=Spieler @1 konnte nicht hinausgeworfen werden.
Kicked @1.=@1 hinausgeworfen.
[full | quick]=[full | quick]
Clear all objects in world=Alle Objekte in der Welt löschen
Invalid usage, see /help clearobjects.=Ungültige Verwendung, siehe /help clearobjects.
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Lösche alle Objekte. Dies kann eine lange Zeit dauern. Eine Netzwerkzeitüberschreitung könnte für Sie auftreten. (von @1)
Cleared all objects.=Alle Objekte gelöscht.
<name> <message>=<Name> <Nachricht>
Send a direct message to a player=Eine Direktnachricht an einen Spieler senden
Invalid usage, see /help msg.=Ungültige Verwendung, siehe /help msg.
The player @1 is not online.=Der Spieler @1 ist nicht online.
DM from @1: @2=DN von @1: @2
Message sent.=Nachricht gesendet.
Get the last login time of a player or yourself=Den letzten Loginzeitpunkt eines Spielers oder Ihren eigenen anfragen
@1's last login time was @2.=Letzter Loginzeitpunkt von @1 war @2.
@1's last login time is unknown.=Letzter Loginzeitpunkt von @1 ist unbekannt.
Clear the inventory of yourself or another player=Das Inventar von Ihnen oder einem anderen Spieler leeren
You don't have permission to clear another player's inventory (missing privilege: @1).=Sie haben nicht die Erlaubnis, das Inventar eines anderen Spielers zu leeren (fehlendes Privileg: @1).
@1 cleared your inventory.=@1 hat Ihr Inventar geleert.
Cleared @1's inventory.=Inventar von @1 geleert.
Player must be online to clear inventory!=Spieler muss online sein, um das Inventar leeren zu können!
Players can't be killed, damage has been disabled.=Spieler können nicht getötet werden, Schaden ist deaktiviert.
Player @1 is not online.=Spieler @1 ist nicht online.
You are already dead.=Sie sind schon tot.
@1 is already dead.=@1 ist bereits tot.
@1 has been killed.=@1 wurde getötet.
Kill player or yourself=Einen Spieler oder Sie selbst töten
Invalid parameters (see /help @1).=Ungültige Parameter (siehe „/help @1“).
Too many arguments, try using just /help <command>=Zu viele Argumente. Probieren Sie es mit „/help <Befehl>“
Available commands: @1=Verfügbare Befehle: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=„/help <Befehl>“ benutzen, um mehr Informationen zu erhalten, oder „/help all“, um alles aufzulisten.
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.
Double-click to copy the entry to the chat history.=Doppelklicken, um den Eintrag in die Chathistorie einzufügen.
Command: @1 @2=Befehl: @1 @2
Available commands: (see also: /help <cmd>)=Verfügbare Befehle: (siehe auch: /help <Befehl>)
Close=Schließen
Privilege=Privileg
Description=Beschreibung
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.
Statistics were reset.=Statistiken wurden zurückgesetzt.
Usage: @1=Verwendung: @1
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Format kann entweder „txt“, „csv“, „lua“, „json“ oder „json_pretty“ sein (die Struktur kann sich in Zukunft ändern).
@1 joined the game.=@1 ist dem Spiel beigetreten.
@1 left the game.=@1 hat das Spiel verlassen.
@1 left the game (timed out).=@1 hat das Spiel verlassen (Netzwerkzeitüberschreitung).
(no description)=(keine Beschreibung)
Can interact with things and modify the world=Kann mit Dingen interagieren und die Welt verändern
Can speak in chat=Kann im Chat sprechen
Can modify basic privileges (@1)=Kann grundlegende Privilegien anpassen (@1)
Can modify privileges=Kann Privilegien anpassen
Can teleport self=Kann sich selbst teleportieren
Can teleport other players=Kann andere Spieler teleportieren
Can set the time of day using /time=Kann die Tageszeit mit /time setzen
Can do server maintenance stuff=Kann Serverwartungsdinge machen
Can bypass node protection in the world=Kann den Schutz auf Blöcken in der Welt umgehen
Can ban and unban players=Kann Spieler verbannen und entbannen
Can kick players=Kann Spieler hinauswerfen
Can use /give and /giveme=Kann /give und /giveme benutzen
Can use /setpassword and /clearpassword=Kann /setpassword und /clearpassword benutzen
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 enable wireframe=Kann Drahtmodell aktivieren
Unknown Item=Unbekannter Gegenstand
Air=Luft
Ignore=Ignorieren
You can't place 'ignore' nodes!=Sie können keine „ignore“-Blöcke platzieren!
Values below show absolute/relative times spend per server step by the instrumented function.=Die unten angegebenen Werte zeigen absolute/relative Zeitspannen, die je Server-Step von der instrumentierten Funktion in Anspruch genommen wurden.
A total of @1 sample(s) were taken.=Es wurden insgesamt @1 Datenpunkt(e) aufgezeichnet.
The output is limited to '@1'.=Die Ausgabe ist beschränkt auf „@1“.
Saving of profile failed: @1=Speichern des Profils fehlgeschlagen: @1
Profile saved to @1=Profil abgespeichert nach @1

View File

@@ -1,246 +0,0 @@
# textdomain: __builtin
Empty command.=Perintah kosong.
Invalid command: @1=Perintah tidak sah: @1
Invalid command usage.=Penggunaan perintah tidak sah.
(@1 s)= (@1 detik)
Command execution took @1 s=Pelaksanaan perintah memerlukan @1 detik
You don't have permission to run this command (missing privileges: @1).=Anda tidak memiliki izin untuk menjalankan perintah ini (hak tidak ada: @1).
Unable to get position of player @1.=Tidak dapat mengambil letak pemain @1.
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Format daerah tidak sah. Diharapkan: (x1,y1,z1) (x2,y2,z2)
<action>=<tindakan>
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Tampilkan tindakan obrolan (misal, '/me memesan piza' menampilkan '<nama pemain> memesan piza')
Show the name of the server owner=Tampilkan nama pemilik server
The administrator of this server is @1.=Pengurus server ini adalah @1.
There's no administrator named in the config file.=Tidak ada nama pengurus dalam berkas konfigurasi.
@1 does not have any privileges.=@1 tidak punya hak apa pun.
Privileges of @1: @2=Hak @1: @2
[<name>]=[<nama>]
Show privileges of yourself or another player=Tampilkan hak diri Anda atau pemain lain
Player @1 does not exist.=Pemain @1 tidak ada.
<privilege>=<hak istimewa>
Return list of all online players with privilege=Tampilkan daftar semua pemain daring dengan hak yang diberikan
Invalid parameters (see /help haspriv).=Parameter tidak sah (lihat /help haspriv).
Unknown privilege!=Hak tidak diketahui!
No online player has the "@1" privilege.=Tidak ada pemain daring dengan hak "@1".
Players online with the "@1" privilege: @2=Pemain daring dengan hak "@1": @2
Your privileges are insufficient.=Hak Anda tidak cukup.
Your privileges are insufficient. '@1' only allows you to grant: @2=Hak Anda tidak cukup. '@1' hanya membolehkan Anda untuk memberi hak: @2
Unknown privilege: @1=Hak tidak diketahui: @1
@1 granted you privileges: @2=@1 memberi Anda hak: @2
<name> (<privilege> [, <privilege2> [<...>]] | all)=<nama> (<hak> [, <hak2> [<...>]] | all)
Give privileges to player=Beri hak kepada pemain
Invalid parameters (see /help grant).=Parameter tidak sah (lihat /help grant).
<privilege> [, <privilege2> [<...>]] | all=<hak> [, <hak2> [<...>]] | all
Grant privileges to yourself=Beri hak-hak kepada diri Anda
Invalid parameters (see /help grantme).=Parameter tidak sah (lihat /help grantme).
Your privileges are insufficient. '@1' only allows you to revoke: @2=Hak Anda tidak cukup. '@1' hanya membolehkan Anda untuk mencabut hak: @2
Note: Cannot revoke in singleplayer: @1=Catatan: Tidak dapat mencabut hak dalam pemain tunggal: @1
Note: Cannot revoke from admin: @1=Catatan: Tidak dapat mencabut hak dari pengurus: @1
No privileges were revoked.=Tidak ada hak yang dicabut.
@1 revoked privileges from you: @2=@1 mencabut hak Anda: @2
Remove privileges from player=Hapus hak dari pemain
Invalid parameters (see /help revoke).=Parameter tidak sah (lihat /help revoke).
Revoke privileges from yourself=Hapus hak dari diri Anda
Invalid parameters (see /help revokeme).=Parameter tidak sah (lihat /help revokeme).
<name> <password>=<nama> <kata sandi>
Set player's password (sent unencrypted, thus insecure)=Atur kata sandi pemain (dikirim tanpa enkripsi sehingga tidak aman)
Name field required.=Isian nama diperlukan.
Your password was cleared by @1.=Kata sandi Anda dikosongkan oleh @1.
Password of player "@1" cleared.=Kata sandi pemain "@1" dikosongkan.
Your password was set by @1.=Kata sandi Anda diatur oleh @1.
Password of player "@1" set.=Kata sandi pemain "@1" diatur.
<name>=<nama>
Set empty password for a player=Atur kata sandi kosong untuk pemain
Reload authentication data=Muat ulang data autentikasi
Done.=Selesai.
Failed.=Gagal.
Remove a player's data=Hapus data pemain
Player "@1" removed.=Pemain "@1" dihapus.
No such player "@1" to remove.=Tidak ada pemain "@1" yang dapat dihapus.
Player "@1" is connected, cannot remove.=Pemain "@1" sedang daring dan tidak dapat dihapus.
Unhandled remove_player return code @1.=Masalah pada remove_player dengan kode @1.
Cannot teleport out of map bounds!=Tidak dapat teleportasi ke luar batas peta!
Cannot get player with name @1.=Tidak dapat mengambil pemain dengan nama @1.
Cannot teleport, @1 is attached to an object!=Tidak dapat meneleportasikan karena @1 terikat kepada sebuah objek!
Teleporting @1 to @2.=Meneleportasikan @1 ke @2.
One does not teleport to oneself.=Tidak dapat teleportasi ke diri sendiri.
Cannot get teleportee with name @1.=Tidak dapat mengambil pemain yang akan diteleportasi dengan nama @1.
Cannot get target player with name @1.=Tidak dapat mengambil pemain tujuan dengan nama @1.
Teleporting @1 to @2 at @3.=Meneleportasikan @1 ke arah @2 di @3.
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <nama_tujuan> | <nama> <X>,<Y>,<Z> | <nama> <nama_tujuan>
Teleport to position or player=Teleportasi ke letak tertentu atau pemain
You don't have permission to teleport other players (missing privilege: @1).=Anda tidak punya izin untuk meneleportasikan pemain lain (hak tidak ada: @1)
([-n] <name> <value>) | <name>=([-n] <nama> <nilai>) | <nama>
Set or read server configuration setting=Atur atau baca setelan konfigurasi server
Failed. Cannot modify secure settings. Edit the settings file manually.=Gagal. Tidak dapat mengubah setelan aman. Sunting berkas setelan secara manual.
Failed. Use '/set -n <name> <value>' to create a new setting.=Gagal. Gunakan '/set -n <nama> <nilai>' untuk membuat setelan baru.
@1 @= @2=@1 @= @2
<not set>=<tidak diatur>
Invalid parameters (see /help set).=Parameter tidak sah (lihat /help set).
Finished emerging @1 blocks in @2ms.=Selesai memunculkan @1 blok dalam @2 milidetik.
emergeblocks update: @1/@2 blocks emerged (@3%)=kabar emergeblocks: @1/@2 blok muncul (@3%)
(here [<radius>]) | (<pos1> <pos2>)=(here [<jari-jari>]) | (<letak1> <letak2>)
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Muat (atau buat jika belum ada) blok peta yang ada dalam daerah dari letak1 ke letak2 (<letak1> dan <letak2> harus dalam tanda kurung)
Started emerge of area ranging from @1 to @2.=Mulai memunculkan daerah dari @1 ke @2.
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Hapus blok peta yang ada dalam daerah dari letak1 ke letak2 (<letak1> dan <letak2> harus dalam tanda kurung)
Successfully cleared area ranging from @1 to @2.=Berhasil menghapus daerah dari @1 ke @2.
Failed to clear one or more blocks in area.=Gagal menghapus satu atau beberapa blok dalam daerah.
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Atur ulang pencahayaan dalam daerah dari letak1 ke letak2 (<letak1> dan <letak2> harus dalam tanda kurung)
Successfully reset light in the area ranging from @1 to @2.=Berhasil mengatur ulang pencahayaan dalam daerah dari @1 ke @2.
Failed to load one or more blocks in area.=Gagal memuat satu atau beberapa blok dalam daerah.
List mods installed on the server=Daftar mod yang dipasang di server
No mods installed.=Tiada mod yang dipasang.
Cannot give an empty item.=Tidak dapat memberikan barang kosong.
Cannot give an unknown item.=Tidak dapat memberikan barang yang tidak diketahui.
Giving 'ignore' is not allowed.=Memberikan 'ignore' tidak dibolehkan.
@1 is not a known player.=@1 bukan pemain yang diketahui.
@1 partially added to inventory.=@1 ditambahkan sebagian ke inventaris.
@1 could not be added to inventory.=@1 tidak dapat ditambahkan ke inventaris.
@1 added to inventory.=@1 ditambahkan ke inventaris.
@1 partially added to inventory of @2.=@1 ditambahkan sebagian ke inventaris @2.
@1 could not be added to inventory of @2.=@1 tidak dapat ditambahkan ke inventaris @2.
@1 added to inventory of @2.=@1 ditambahkan ke inventaris @2.
<name> <ItemString> [<count> [<wear>]]=<nama> <ItemString> [<jumlah> [<penggunaan>]]
Give item to player=Beri barang kepada pemain
Name and ItemString required.=Nama dan ItemString diperlukan.
<ItemString> [<count> [<wear>]]=<ItemString> [<jumlah> [<penggunaan>]]
Give item to yourself=Beri barang kepada diri Anda
ItemString required.=ItemString diperlukan.
<EntityName> [<X>,<Y>,<Z>]=<EntityName> [<X>,<Y>,<Z>]
Spawn entity at given (or your) position=Bangkitkan entitas pada letak tertentu (atau letak Anda)
EntityName required.=EntityName diperlukan.
Unable to spawn entity, player is nil.=Tidak dapat membangkitkan entitas karena pemain bernilai nil.
Cannot spawn an unknown entity.=Tidak dapat membangkitkan entitas yang tidak diketahui.
Invalid parameters (@1).=Parameter tidak sah (@1).
@1 spawned.=@1 dibangkitkan.
@1 failed to spawn.=@1 gagal dibangkitkan.
Destroy item in hand=Hapus barang di tangan.
Unable to pulverize, no player.=Gagal menghapus karena pemain tidak ada.
Unable to pulverize, no item in hand.=Gagal menghapus karena barang tidak ada di tangan.
An item was pulverized.=Sebuah barang dihapus.
[<range>] [<seconds>] [<limit>]=[<jangkauan>] [<detik>] [<batas>]
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Periksa yang terakhir menyentuh sebuah nodus atau nodus di sekitarnya dalam waktu tertentu yang diatur dengan <detik>. Bawaan: jangkauan @= 0, detik @= 86400 @= 24 jam, batas @= 5. Atur <detik> ke inf untuk tanpa batas waktu
Rollback functions are disabled.=Fungsi cadangan (rollback) dimatikan.
That limit is too high!=Batasnya terlalu tinggi!
Checking @1 ...=Memeriksa @1 ...
Nobody has touched the specified location in @1 seconds.=Tidak ada yang menyentuh lokasi yang diberikan dalam @1 detik.
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 detik yang lalu.
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Pukul sebuah nodus (jangkauan@=@1, detik@=@2, batas@=@3).
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nama> [<detik>]) | (:<pemeran> [<detik>])
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Batalkan tindakan pemain. Bawaan untuk <detik> adalah 60. Atur <detik> ke inf untuk tanpa batas waktu
Invalid parameters. See /help rollback and /help rollback_check.=Parameter tidak sah. Lihat /help rollback dan /help rollback_check.
Reverting actions of player '@1' since @2 seconds.=Membatalkan tindakan pemain '@1' sejak @2 detik.
Reverting actions of @1 since @2 seconds.=Membatalkan tindakan @1 sejak @2 detik.
(log is too long to show)=(log terlalu panjang untuk ditampilkan)
Reverting actions succeeded.=Pembatalan tindakan berhasil.
Reverting actions FAILED.=Pembatalan tindakan GAGAL.
Show server status=Tampilkan status server
This command was disabled by a mod or game.=Perintah ini dimatikan oleh mod atau permainan.
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
Show or set time of day=Tampilkan atau atur waktu harian
Current time is @1:@2.=Waktu saat ini @1.@2.
You don't have permission to run this command (missing privilege: @1).=Anda tidak memiliki izin untuk menjalankan perintah ini (hak tidak ada: @1).
Invalid time (must be between 0 and 24000).=Waktu tidak sah (harus antara 0 dan 24000).
Time of day changed.=Waktu harian diubah.
Invalid hour (must be between 0 and 23 inclusive).=Jam tidak sah (harus antara 0 dan 23 inklusif).
Invalid minute (must be between 0 and 59 inclusive).=Menit tidak sah (harus antara 0 dan 59 inklusif).
Show day count since world creation=Tampilkan hitungan hari sejak pembuatan dunia
Current day is @1.=Sekarang hari @1.
[<delay_in_seconds> | -1] [-r] [<message>]=[<jeda_dalam_detik> | -1] [-r] [<pesan>]
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Matikan server (-1 membatalkan mati terjadwal, -r membolehkan pemain untuk menyambung ulang)
Server shutting down (operator request).=Server dimatikan (permintaan operator).
Ban the IP of a player or show the ban list=Blokir IP pemain atau tampilkan daftar blokir
The ban list is empty.=Daftar blokir kosong.
Ban list: @1=Daftar blokir: @1
You cannot ban players in singleplayer!=Anda tidak dapat memblokir pemain dalam pemain tunggal!
Player is not online.=Pemain tidak daring.
Failed to ban player.=Gagal memblokir pemain.
Banned @1.=@1 diblokir.
<name> | <IP_address>=<nama> | <alamat_IP>
Remove IP ban belonging to a player/IP=Hapus blokir IP yang dimiliki pemain
Failed to unban player/IP.=Gagal menghapus blokir pemain/IP.
Unbanned @1.=@1 dihapus blokir.
<name> [<reason>]=<nama> [<alasan>]
Kick a player=Tendang keluar pemain
Failed to kick player @1.=Gagal menendang keluar pemain @1.
Kicked @1.=@1 ditendang keluar.
[full | quick]=[full | quick]
Clear all objects in world=Hapus semua objek dalam dunia
Invalid usage, see /help clearobjects.=Penggunaan tidak sah. Lihat /help clearobjects.
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Menghapus semua objek. Ini mungkin perlu waktu lama. Anda mungkin mengalami kehabisan waktu. (oleh @1)
Cleared all objects.=Semua objek dihapus.
<name> <message>=<nama> <pesan>
Send a direct message to a player=Kirim pesan langsung kepada pemain
Invalid usage, see /help msg.=Penggunaan tidak sah. Lihat /help msg.
The player @1 is not online.=Pemain @1 tidak daring.
DM from @1: @2=Pesan langsung dari @1: @2
Message sent.=Pesan terkirim.
Get the last login time of a player or yourself=Ambil waktu masuk terakhir pemain atau diri Anda
@1's last login time was @2.=Waktu masuk terakhir @1 adalah @2.
@1's last login time is unknown.=Waktu masuk terakhir @1 tidak diketahui.
Clear the inventory of yourself or another player=Bersihkan inventaris diri Anda atau pemain lain
You don't have permission to clear another player's inventory (missing privilege: @1).=Anda tidak punya izin untuk membersihkan inventaris pemain lain (hak tidak ada: @1).
@1 cleared your inventory.=@1 membersihkan inventaris Anda.
Cleared @1's inventory.=Inventaris @1 dibersihkan.
Player must be online to clear inventory!=Pemain harus daring untuk dapat dibersihkan inventarisnya.
Players can't be killed, damage has been disabled.=Pemain tidak dapat dibunuh karena kerusakan dimatikan.
Player @1 is not online.=Pemain @1 tidak daring.
You are already dead.=Anda telah mati.
@1 is already dead.=@1 telah mati.
@1 has been killed.=@1 telah dibunuh.
Kill player or yourself=Bunuh pemain atau diri Anda
Invalid parameters (see /help @1).=Parameter tidak sah (lihat /help @1).
Too many arguments, try using just /help <command>=Terlalu banyak argumen. Coba hanya gunakan /help <perintah>
Available commands: @1=Perintah yang tersedia: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Gunakan '/help <perintah>' untuk informasi lebih lanjut atau '/help all' untuk melihat daftar semuanya.
Available commands:=Perintah yang tersedia:
Command not available: @1=Perintah tidak tersedia: @1
[all | privs | <cmd>] [-t]=[all | privs | <perintah>] [-t]
Get help for commands or list privileges (-t: output in chat)=Ambil bantuan untuk perintah atau daftar hak (-t: keluaran di obrolan)
Available privileges:=Hak yang ada:
Command=Perintah
Parameters=Parameter
For more information, click on any entry in the list.=Untuk informasi lebih lanjut, klik pada entri apa pun dalam daftar.
Double-click to copy the entry to the chat history.=Klik ganda untuk menyalin entri ke riwayat obrolan.
Command: @1 @2=Perintah: @1 @2
Available commands: (see also: /help <cmd>)=Perintah yang tersedia: (lihat juga: /help <perintah>)
Close=Tutup
Privilege=Hak
Description=Deskripsi
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset
Handle the profiler and profiling data=Menangani profiler dan data profiling
Statistics written to action log.=Statistik ditulis ke log action.
Statistics were reset.=Statistik diatur ulang.
Usage: @1=Penggunaan: @1
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Format berupa salah satu dari txt, csv, lua, json, json_pretty (struktur mungkin berubah).
@1 joined the game.=@1 bergabung dalam permainan.
@1 left the game.=@1 keluar permainan.
@1 left the game (timed out).=@1 keluar permainan (kehabisan waktu).
(no description)=(tanpa deskripsi)
Can interact with things and modify the world=Dapat berinteraksi dengan barang dan mengubah dunia
Can speak in chat=Dapat berbicara di obrolan
Can modify basic privileges (@1)=Dapat mengubah hak-hak dasar (@1)
Can modify privileges=Dapat mengubah hak-hak
Can teleport self=Dapat teleportasi diri
Can teleport other players=Dapat meneleportasikan pemain lain
Can set the time of day using /time=Dapat mengatur waktu harian dengan /time
Can do server maintenance stuff=Dapat melakukan tindakan perawatan server
Can bypass node protection in the world=Dapat menembus pengamanan nodus dalam dunia
Can ban and unban players=Dapat memblokir atau menghapus blokir pemain
Can kick players=Dapat menendang keluar pemain
Can use /give and /giveme=Dapat menggunakan /give dan /giveme
Can use /setpassword and /clearpassword=Dapat menggunakan /setpassword dan /clearpassword
Can use fly mode=Dapat menggunakan mode terbang
Can use fast mode=Dapat menggunakan mode cepat
Can fly through solid nodes using noclip mode=Dapat terbang menembus nodus padat dengan mode tembus blok
Can use the rollback functionality=Dapat menggunakan fungsi cadangan (rollback)
Can enable wireframe=Dapat melihat dalam mode rangka kawat
Unknown Item=Barang Tak Diketahui
Air=Udara
Ignore=Ignore
You can't place 'ignore' nodes!=Anda tidak dapat menaruh nodus 'ignore'!
Values below show absolute/relative times spend per server step by the instrumented function.=Nilai berikut menampilkan waktu mutlak/relatif yang dihabiskan tiap langkah server oleh fungsi instrumen.
A total of @1 sample(s) were taken.=Total @1 sampel yang diambil.
The output is limited to '@1'.=Keluaran dibatasi ke '@1'.
Saving of profile failed: @1=Penyimpanan profil gagal: @1
Profile saved to @1=Profil disimpan ke @1

View File

@@ -1,260 +0,0 @@
# textdomain: __builtin
Empty command.=Comando vuoto.
Invalid command: @1=Comando non valido: @1
Invalid command usage.=Utilizzo del comando non valido.
(@1 s)=
Command execution took @1 s=
You don't have permission to run this command (missing privileges: @1).=Non hai il permesso di eseguire questo comando (privilegi mancanti: @1).
Unable to get position of player @1.=Impossibile ottenere la posizione del giocatore @1.
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Formato dell'area non corretto. Richiesto: (x1,y1,z1) (x2,y2,z2)
<action>=<azione>
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Mostra un'azione in chat (es. `/me ordina una pizza` mostra `<nome giocatore> ordina una pizza`)
Show the name of the server owner=Mostra il nome del proprietario del server
The administrator of this server is @1.=L'amministratore di questo server è @1.
There's no administrator named in the config file.=Non c'è nessun amministratore nel file di configurazione.
@1 does not have any privileges.=
Privileges of @1: @2=Privilegi di @1: @2
[<name>]=[<nome>]
Show privileges of yourself or another player=Mostra i privilegi propri o di un altro giocatore
Player @1 does not exist.=Il giocatore @1 non esiste.
<privilege>=<privilegio>
Return list of all online players with privilege=Ritorna una lista di tutti i giocatori connessi col tale privilegio
Invalid parameters (see /help haspriv).=Parametri non validi (vedi /help haspriv).
Unknown privilege!=Privilegio sconosciuto!
No online player has the "@1" privilege.=
Players online with the "@1" privilege: @2=Giocatori connessi con il privilegio "@1": @2
Your privileges are insufficient.=I tuoi privilegi sono insufficienti.
Your privileges are insufficient. '@1' only allows you to grant: @2=
Unknown privilege: @1=Privilegio sconosciuto: @1
@1 granted you privileges: @2=@1 ti ha assegnato i seguenti privilegi: @2
<name> (<privilege> [, <privilege2> [<...>]] | all)=
Give privileges to player=Dà privilegi al giocatore
Invalid parameters (see /help grant).=Parametri non validi (vedi /help grant).
<privilege> [, <privilege2> [<...>]] | all=
Grant privileges to yourself=Assegna dei privilegi a te stessǝ
Invalid parameters (see /help grantme).=Parametri non validi (vedi /help grantme).
Your privileges are insufficient. '@1' only allows you to revoke: @2=
Note: Cannot revoke in singleplayer: @1=
Note: Cannot revoke from admin: @1=
No privileges were revoked.=
@1 revoked privileges from you: @2=@1 ti ha revocato i seguenti privilegi: @2
Remove privileges from player=Rimuove privilegi dal giocatore
Invalid parameters (see /help revoke).=Parametri non validi (vedi /help revoke).
Revoke privileges from yourself=Revoca privilegi a te stessǝ
Invalid parameters (see /help revokeme).=Parametri non validi (vedi /help revokeme).
<name> <password>=<nome> <password>
Set player's password (sent unencrypted, thus insecure)=
Name field required.=Campo "nome" richiesto.
Your password was cleared by @1.=La tua password è stata resettata da @1.
Password of player "@1" cleared.=Password del giocatore "@1" resettata.
Your password was set by @1.=La tua password è stata impostata da @1.
Password of player "@1" set.=Password del giocatore "@1" impostata.
<name>=<nome>
Set empty password for a player=Imposta una password vuota a un giocatore
Reload authentication data=Ricarica i dati d'autenticazione
Done.=Fatto.
Failed.=Errore.
Remove a player's data=Rimuove i dati di un giocatore
Player "@1" removed.=Giocatore "@1" rimosso.
No such player "@1" to remove.=Non è presente nessun giocatore "@1" da rimuovere.
Player "@1" is connected, cannot remove.=Il giocatore "@1" è connesso, non può essere rimosso.
Unhandled remove_player return code @1.=Codice ritornato da remove_player non gestito (@1).
Cannot teleport out of map bounds!=Non ci si può teletrasportare fuori dai limiti della mappa!
Cannot get player with name @1.=Impossibile trovare il giocatore chiamato @1.
Cannot teleport, @1 is attached to an object!=Impossibile teletrasportare, @1 è attaccato a un oggetto!
Teleporting @1 to @2.=Teletrasportando @1 da @2.
One does not teleport to oneself.=Non ci si può teletrasportare su se stessi.
Cannot get teleportee with name @1.=Impossibile trovare il giocatore chiamato @1 per il teletrasporto
Cannot get target player with name @1.=Impossibile trovare il giocatore chiamato @1 per il teletrasporto
Teleporting @1 to @2 at @3.=Teletrasportando @1 da @2 a @3
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <da_nome> | <nome> <X>,<Y>,<Z> | <nome> <da_nome>
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>
Invalid parameters (see /help set).=Parametri non validi (vedi /help set).
Finished emerging @1 blocks in @2ms.=Finito di emergere @1 blocchi in @2ms
emergeblocks update: @1/@2 blocks emerged (@3%)=aggiornamento emergeblocks: @1/@2 blocchi emersi (@3%)
(here [<radius>]) | (<pos1> <pos2>)=(here [<raggio>]) | (<pos1> <pos2>)
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Carica (o, se non esiste, genera) blocchi mappa contenuti nell'area tra pos1 e pos2 (<pos1> e <pos2> vanno tra parentesi)
Started emerge of area ranging from @1 to @2.=Iniziata emersione dell'area tra @1 e @2.
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Cancella i blocchi mappa contenuti nell'area tra pos1 e pos2 (<pos1> e <pos2> vanno tra parentesi)
Successfully cleared area ranging from @1 to @2.=Area tra @1 e @2 ripulita con successo.
Failed to clear one or more blocks in area.=Errore nel ripulire uno o più blocchi mappa nell'area
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Reimposta l'illuminazione nell'area tra pos1 e po2 (<pos1> e <pos2> vanno tra parentesi)
Successfully reset light in the area ranging from @1 to @2.=Luce nell'area tra @1 e @2 reimpostata con successo.
Failed to load one or more blocks in area.=Errore nel caricare uno o più blocchi mappa nell'area.
List mods installed on the server=Elenca le mod installate nel server
No mods installed.=
Cannot give an empty item.=Impossibile dare un oggetto vuoto.
Cannot give an unknown item.=Impossibile dare un oggetto sconosciuto.
Giving 'ignore' is not allowed.=Non è permesso dare 'ignore'.
@1 is not a known player.=@1 non è un giocatore conosciuto.
@1 partially added to inventory.=@1 parzialmente aggiunto all'inventario.
@1 could not be added to inventory.=@1 non può essere aggiunto all'inventario.
@1 added to inventory.=@1 aggiunto all'inventario.
@1 partially added to inventory of @2.=@1 parzialmente aggiunto all'inventario di @2.
@1 could not be added to inventory of @2.=Non è stato possibile aggiungere @1 all'inventario di @2.
@1 added to inventory of @2.=@1 aggiunto all'inventario di @2.
<name> <ItemString> [<count> [<wear>]]=<nome> <NomeOggetto> [<quantità> [<usura>]]
Give item to player=Dà oggetti ai giocatori
Name and ItemString required.=Richiesti nome e NomeOggetto.
<ItemString> [<count> [<wear>]]=<NomeOggetto> [<quantità> [<usura>]]
Give item to yourself=Dà oggetti a te stessǝ
ItemString required.=Richiesto NomeOggetto.
<EntityName> [<X>,<Y>,<Z>]=<NomeEntità> [<X>,<Y>,<Z>]
Spawn entity at given (or your) position=Genera un'entità alla data coordinata (o la tua)
EntityName required.=Richiesto NomeEntità
Unable to spawn entity, player is nil.=Impossibile generare l'entità, il giocatore è nil.
Cannot spawn an unknown entity.=Impossibile generare un'entità sconosciuta.
Invalid parameters (@1).=Parametri non validi (@1).
@1 spawned.=Generata entità @1.
@1 failed to spawn.=Errore nel generare @1
Destroy item in hand=Distrugge l'oggetto in mano
Unable to pulverize, no player.=Impossibile polverizzare, nessun giocatore.
Unable to pulverize, no item in hand.=Impossibile polverizzare, nessun oggetto in mano.
An item was pulverized.=Un oggetto è stato polverizzato.
[<range>] [<seconds>] [<limit>]=[<raggio>] [<secondi>] [<limite>]
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Controlla chi è l'ultimo giocatore che ha toccato un nodo o un nodo nelle sue vicinanze, negli ultimi secondi indicati. Di base: raggio @= 0, secondi @= 86400 @= 24h, limite @= 5.
Rollback functions are disabled.=Le funzioni di rollback sono disabilitate.
That limit is too high!=Il limite è troppo alto!
Checking @1 ...=Controllando @1 ...
Nobody has touched the specified location in @1 seconds.=Nessuno ha toccato il punto specificato negli ultimi @1 secondi.
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 secondi fa.
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Colpisce un nodo (raggio@=@1, secondi@=@2, limite@=@3)
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nome> [<secondi>]) | (:<attore> [<secondi>])
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Riavvolge le azioni di un giocatore. Di base, <secondi> è 60. Imposta <secondi> a inf per nessun limite di tempo
Invalid parameters. See /help rollback and /help rollback_check.=Parametri non validi. Vedi /help rollback e /help rollback_check.
Reverting actions of player '@1' since @2 seconds.=Riavvolge le azioni del giocatore '@1' avvenute negli ultimi @2 secondi.
Reverting actions of @1 since @2 seconds.=Riavvolge le azioni di @1 avvenute negli ultimi @2 secondi.
(log is too long to show)=(il log è troppo lungo per essere mostrato)
Reverting actions succeeded.=Riavvolgimento azioni avvenuto con successo.
Reverting actions FAILED.=Errore nel riavvolgere le azioni.
Show server status=Mostra lo stato del server
This command was disabled by a mod or game.=Questo comando è stato disabilitato da una mod o dal gioco.
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
Show or set time of day=Mostra o imposta l'orario della giornata
Current time is @1:@2.=Orario corrente: @1:@2.
You don't have permission to run this command (missing privilege: @1).=Non hai il permesso di eseguire questo comando (privilegio mancante: @1)
Invalid time (must be between 0 and 24000).=
Time of day changed.=Orario della giornata cambiato.
Invalid hour (must be between 0 and 23 inclusive).=Ora non valida (deve essere tra 0 e 23 inclusi)
Invalid minute (must be between 0 and 59 inclusive).=Minuto non valido (deve essere tra 0 e 59 inclusi)
Show day count since world creation=Mostra il conteggio dei giorni da quando il mondo è stato creato
Current day is @1.=Giorno attuale: @1.
[<delay_in_seconds> | -1] [-r] [<message>]=
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=
Server shutting down (operator request).=Arresto del server in corso (per richiesta dell'operatore)
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ǝ.
<name> | <IP_address>=<nome> | <indirizzo_IP>
Remove IP ban belonging to a player/IP=Perdona l'IP appartenente a un giocatore/IP
Failed to unban player/IP.=Errore nel perdonare il giocatore/IP
Unbanned @1.=@1 perdonatǝ
<name> [<reason>]=<nome> [<ragione>]
Kick a player=Caccia un giocatore
Failed to kick player @1.=Errore nel cacciare il giocatore @1.
Kicked @1.=@1 cacciatǝ.
[full | quick]=[full | quick]
Clear all objects in world=Elimina tutti gli oggetti/entità nel mondo
Invalid usage, see /help clearobjects.=Uso incorretto, vedi /help clearobjects.
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Eliminando tutti gli oggetti/entità. Questo potrebbe richiedere molto tempo e farti eventualmente crashare. (di @1)
Cleared all objects.=Tutti gli oggetti sono stati eliminati.
<name> <message>=<nome> <messaggio>
Send a direct message to a player=Invia un messaggio privato al giocatore
Invalid usage, see /help msg.=Uso incorretto, vedi /help msg
The player @1 is not online.=Il giocatore @1 non è connesso.
DM from @1: @2=Messaggio privato da @1: @2
Message sent.=Messaggio inviato.
Get the last login time of a player or yourself=Ritorna l'ultimo accesso di un giocatore o di te stessǝ
@1's last login time was @2.=L'ultimo accesso di @1 è avvenuto il @2
@1's last login time is unknown.=L'ultimo accesso di @1 non è conosciuto
Clear the inventory of yourself or another player=Svuota l'inventario tuo o di un altro giocatore
You don't have permission to clear another player's inventory (missing privilege: @1).=Non hai il permesso di svuotare l'inventario di un altro giocatore (privilegio mancante: @1).
@1 cleared your inventory.=@1 ha svuotato il tuo inventario.
Cleared @1's inventory.=L'inventario di @1 è stato svuotato.
Player must be online to clear inventory!=Il giocatore deve essere connesso per svuotarne l'inventario!
Players can't be killed, damage has been disabled.=I giocatori non possono essere uccisi, il danno è disabilitato.
Player @1 is not online.=Il giocatore @1 non è connesso.
You are already dead.=Sei già mortǝ.
@1 is already dead.=@1 è già mortǝ.
@1 has been killed.=@1 è stato uccisǝ.
Kill player or yourself=Uccide un giocatore o te stessǝ
Invalid parameters (see /help @1).=
Too many arguments, try using just /help <command>=
Available commands: @1=Comandi disponibili: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Usa '/help <comando>' per ottenere più informazioni, o '/help all' per elencare tutti i comandi.
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.
Double-click to copy the entry to the chat history.=Doppio click per copiare la voce nella cronologia della chat.
Command: @1 @2=Comando: @1 @2
Available commands: (see also: /help <cmd>)=Comandi disponibili: (vedi anche /help <comando>)
Close=Chiudi
Privilege=Privilegio
Description=Descrizione
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.
Statistics were reset.=Le statistiche sono state resettate.
Usage: @1=Utilizzo: @1
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=I formati supportati sono txt, csv, lua, json e json_pretty (le strutture potrebbero essere soggetti a cambiamenti).
@1 joined the game.=
@1 left the game.=
@1 left the game (timed out).=
(no description)=(nessuna descrizione)
Can interact with things and modify the world=Si può interagire con le cose e modificare il mondo
Can speak in chat=Si può parlare in chat
Can modify basic privileges (@1)=
Can modify privileges=Si possono modificare i privilegi
Can teleport self=Si può teletrasportare se stessз
Can teleport other players=Si possono teletrasportare gli altri giocatori
Can set the time of day using /time=Si può impostate l'orario della giornata tramite /time
Can do server maintenance stuff=Si possono eseguire operazioni di manutenzione del server
Can bypass node protection in the world=Si può aggirare la protezione dei nodi nel mondo
Can ban and unban players=Si possono bandire e perdonare i giocatori
Can kick players=Si possono cacciare i giocatori
Can use /give and /giveme=Si possono usare /give e /give me
Can use /setpassword and /clearpassword=Si possono usare /setpassword e /clearpassword
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 enable wireframe=
Unknown Item=Oggetto sconosciuto
Air=Aria
Ignore=Ignora
You can't place 'ignore' nodes!=Non puoi piazzare nodi 'ignore'!
Values below show absolute/relative times spend per server step by the instrumented function.=
A total of @1 sample(s) were taken.=
The output is limited to '@1'.=
Saving of profile failed: @1=
Profile saved to @1=
##### not used anymore #####
Set player's password=Imposta la password del giocatore
Invalid time.=Orario non valido.
[all | privs | <cmd>]=[all | privs | <comando>]
Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi
Allows enabling various debug options that may affect gameplay=Permette di abilitare varie opzioni di debug che potrebbero influenzare l'esperienza di gioco
[<delay_in_seconds> | -1] [reconnect] [<message>]=[<ritardo_in_secondi> | -1] [reconnect] [<messaggio>]
Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato)
<name> (<privilege> | all)=<nome> (<privilegio> | all)
<privilege> | all=<privilegio> | all
Can modify 'shout' and 'interact' privileges=Si possono modificare i privilegi 'shout' e 'interact'

View File

@@ -1,246 +0,0 @@
# textdomain: __builtin
Empty command.=Perintah kosong.
Invalid command: @1=Perintah tidak sah: @1
Invalid command usage.=Penggunaan perintah tidak sah.
(@1 s)= (@1 s)
Command execution took @1 s=Pelaksanaan perintah mengambil masa @1 s
You don't have permission to run this command (missing privileges: @1).=Anda tiada kebenaran untuk jalankan perintah ini (tiada keistimewaan: @1).
Unable to get position of player @1.=Tidak mampu mendapatkan kedudukan pemain @1.
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Format kawasan tidak betul. Format disangka: (x1,y1,z1) (x2,y2,z2)
<action>=<perbuatan>
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Tunjuk perbuatan sembang (cth., '/me memesan piza' memaparkan '<nama pemain> memesan piza')
Show the name of the server owner=Tunjuk nama pemilik pelayan
The administrator of this server is @1.=Pentadbir bagi pelayan ini ialah @1.
There's no administrator named in the config file.=Tiada pentadbir dinamakan dalam fail konfigurasi.
@1 does not have any privileges.=@1 tidak mempunyai sebarang keistimewaan.
Privileges of @1: @2=Keistimewaan milik @1: @2
[<name>]=[<nama>]
Show privileges of yourself or another player=Tunjuk keistimewaan diri anda atau pemain lain
Player @1 does not exist.=Pemain @1 tidak wujud.
<privilege>=<keistimewaan>
Return list of all online players with privilege=Kembalikan senarai semua pemain dalam talian yang mempunyai keistimewaan
Invalid parameters (see /help haspriv).=Parameter tidak sah (sila lihat /help haspriv).
Unknown privilege!=Keistimewaan tidak diketahui!
No online player has the "@1" privilege.=Tiada pemain dalam talian yang mempunyai keistimewaan "@1".
Players online with the "@1" privilege: @2=Pemain dalam talian yang mempunyai keistimewaan "@1": @2
Your privileges are insufficient.=Keistimewaan anda tidak mencukupi.
Your privileges are insufficient. '@1' only allows you to grant: @2=Keistimewaan anda tidak mencukupi. '@1' hanya membolehkan anda untuk memberikan: @2
Unknown privilege: @1=Keistimewaan tidak diketahui: @1
@1 granted you privileges: @2=@1 memberikan anda keistimewaan: @2
<name> (<privilege> [, <privilege2> [<...>]] | all)=<nama> (<keistimewaan> [, <keistimewaan2> [<...>]] | all)
Give privileges to player=Berikan keistimewaan kepada pemain
Invalid parameters (see /help grant).=Parameter tidak sah (sila lihat /help grant).
<privilege> [, <privilege2> [<...>]] | all=<keistimewaan> [, <keistimewaan2> [<...>]] | all
Grant privileges to yourself=Berikan keistimewaan kepada diri sendiri
Invalid parameters (see /help grantme).=Parameter tidak sah (sila lihat /help grantme).
Your privileges are insufficient. '@1' only allows you to revoke: @2=Keistimewaan anda tidak mencukupi. '@1' hanya membolehkan anda untuk menarik balik: @2
Note: Cannot revoke in singleplayer: @1=Nota: Tidak boleh tarik balik keistimewaan dalam mod pemain perseorangan: @1
Note: Cannot revoke from admin: @1=Nota: Tidak boleh tarik balik keistimewaan daripada pentadbir: @1
No privileges were revoked.=Tiada keistimewaan yang ditarik balik.
@1 revoked privileges from you: @2=@1 menarik balik keistimewaan berikut daripada anda: @2
Remove privileges from player=Tarik balik keistimewaan daripada pemain
Invalid parameters (see /help revoke).=Parameter tidak sah (sila lihat /help revoke).
Revoke privileges from yourself=Tarik balik keistimewaan daripada diri sendiri
Invalid parameters (see /help revokeme).=Parameter tidak sah (sila lihat /help revokeme).
<name> <password>=<nama> <kata laluan>
Set player's password (sent unencrypted, thus insecure)=Menetapkan kata laluan pemain (dihantar secara tidak tersulit, oleh itu ia tidak selamat)
Name field required.=Medan nama diperlukan.
Your password was cleared by @1.=Kata laluan anda telah dipadamkan oleh @1.
Password of player "@1" cleared.=Kata laluan bagi pemain "@1" telah dipadamkan.
Your password was set by @1.=Kata laluan anda telah ditetapkan oleh @1.
Password of player "@1" set.=Kata laluan bagi pemain "@1" telah ditetapkan.
<name>=<nama>
Set empty password for a player=Tetapkan kata laluan kosong bagi seorang pemain
Reload authentication data=Muat semula data pengesahan
Done.=Selesai.
Failed.=Gagal.
Remove a player's data=Buang data seorang pemain
Player "@1" removed.=Pemain "@1" telah dibuang.
No such player "@1" to remove.=Tiada pemain "@1" tersebut untuk dibuang.
Player "@1" is connected, cannot remove.=Pemain "@1" masih tersambung, tidak boleh dibuang.
Unhandled remove_player return code @1.=Kod kembali remove_player tidak dikendalikan @1.
Cannot teleport out of map bounds!=Tidak boleh merentas jarak keluar dari batas peta!
Cannot get player with name @1.=Tidak boleh mendapatkan pemain dengan nama @1.
Cannot teleport, @1 is attached to an object!=Tidak boleh rentas jarak, @1 tersambung ke suatu objek!
Teleporting @1 to @2.=Merentas jarak @1 ke @2.
One does not teleport to oneself.=Seseorang itu tidak boleh rentas jarak ke diri sendiri.
Cannot get teleportee with name @1.=Tidak boleh mendapatkan perentas jarak dengan nama @1.
Cannot get target player with name @1.=Tidak boleh mendapatkan pemain sasaran dengan nama @1.
Teleporting @1 to @2 at @3.=Merentas jarak @1 ke @2 di @3.
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <nama_sasaran> | <nama> <X>,<Y>,<Z> | <nama> <nama_sasaran>
Teleport to position or player=Rentas jarak ke kedudukan atau pemain
You don't have permission to teleport other players (missing privilege: @1).=Anda tiada kebenaran untuk merentas jarak pemain yang lain (tiada keistimewaan: @1).
([-n] <name> <value>) | <name>=([-n] <nama> <nilai>) | <nama>
Set or read server configuration setting=Tetapkan atau baca tetapan konfigurasi pelayan
Failed. Cannot modify secure settings. Edit the settings file manually.=Gagal. Tidak boleh mengubah tetapan terselamat. Sila edit fail tetapan secara manual.
Failed. Use '/set -n <name> <value>' to create a new setting.=Gagal. Sila gunakan '/set -n <nama> <nilai>' untuk mencipta tetapan baharu.
@1 @= @2=@1 @= @2
<not set>=<tidak ditetapkan>
Invalid parameters (see /help set).=Parameter tidak sah (sila lihat /help set).
Finished emerging @1 blocks in @2ms.=Selesai menimbulkan @1 blok dalam @2ms.
emergeblocks update: @1/@2 blocks emerged (@3%)=kemas kini blok timbul: @1/@2 blok telah timbul (@3%)
(here [<radius>]) | (<pos1> <pos2>)=(here [<jejari>]) | (<kedudukan1> <kedudukan2>)
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Muatkan (atau, jika belum wujud, janakan) blok peta yang terkandung dalam kawasan antara kedudukan1 dan kedudukan2 (<kedudukan1> dan <kedudukan2> mestilah berada dalam kurungan)
Started emerge of area ranging from @1 to @2.=Mula menimbulkan kawasan di antara @1 dan @2.
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Padamkan blok peta yang terkandung dalam kawasan antara kedudukan1 dan kedudukan2 (<kedudukan1> dan <kedudukan2> mestilah berada dalam kurungan)
Successfully cleared area ranging from @1 to @2.=Berjaya memadam kawasan di antara @1 dan @2.
Failed to clear one or more blocks in area.=Gagal untuk memadam satu atau lebih blok dalam kawasan.
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Tetapkan semula pencahayaan dalam kawasan antara kedudukan1 dan kedudukan2 (<kedudukan1> dan <kedudukan2> mestilah berada dalam kurungan)
Successfully reset light in the area ranging from @1 to @2.=Berjaya menetapkan semula pencahayaan dalam kawasan di antara @1 dan @2.
Failed to load one or more blocks in area.=Gagal untuk memuatkan satu atau lebih blok dalam kawasan.
List mods installed on the server=Senaraikan mods yang terpasang di pelayan
No mods installed.=Tiada mods terpasang.
Cannot give an empty item.=Tidak boleh berikan item kosong.
Cannot give an unknown item.=Tidak boleh berikan item tidak diketahui.
Giving 'ignore' is not allowed.=Tidak dibenarkan untuk memberikan nod 'abai'.
@1 is not a known player.=@1 bukanlah pemain yang diketahui.
@1 partially added to inventory.=@1 telah ditambah ke inventori secara separa.
@1 could not be added to inventory.=@1 tidak boleh ditambah ke inventori.
@1 added to inventory.=@1 telah ditambah ke inventori.
@1 partially added to inventory of @2.=@1 telah ditambah secara separa ke inventori @2.
@1 could not be added to inventory of @2.=@1 tidak boleh ditambah ke inventori @2.
@1 added to inventory of @2.=@1 telah ditambah ke inventori @2.
<name> <ItemString> [<count> [<wear>]]=<nama> <RentetanItem> [<jumlah> [<haus>]]
Give item to player=Berikan item kepada pemain
Name and ItemString required.=Nama dan RentetanItem diperlukan.
<ItemString> [<count> [<wear>]]=<RentetanItem> [<jumlah> [<haus>]]
Give item to yourself=Berikan item kepada diri sendiri
ItemString required.=RentetanItem diperlukan.
<EntityName> [<X>,<Y>,<Z>]=<NamaEntiti> [<X>,<Y>,<Z>]
Spawn entity at given (or your) position=Jelmakan entiti di kedudukan yang diberi (atau di kedudukan anda)
EntityName required.=NamaEntiti diperlukan.
Unable to spawn entity, player is nil.=Tidak mampu menjelmakan entiti, pemain itu kosong.
Cannot spawn an unknown entity.=Tidak boleh menjelmakan entiti yang tidak diketahui.
Invalid parameters (@1).=Parameter tidak sah (@1).
@1 spawned.=@1 telah dijelmakan.
@1 failed to spawn.=@1 gagal untuk dijelmakan.
Destroy item in hand=Musnahkan item dalam tangan
Unable to pulverize, no player.=Gagal untuk dimusnahkan, tiada pemain.
Unable to pulverize, no item in hand.=Gagal untuk dimusnahkan, tiada item dalam tangan.
An item was pulverized.=Suatu item telah dimusnahkan.
[<range>] [<seconds>] [<limit>]=[<jarak>] [<saat>] [<had>]
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Periksa siapa orang terakhir yang menyentuh suatu nod atau suatu nod berhampiran dalam masa yang ditentukan dalam unit <saat>. Lalai: jarak @= 0, saat @= 86400 @= 24j, had @= 5. Tetapkan <saat> ke inf untuk tiada had masa
Rollback functions are disabled.=Fungsi gulung balik dilumpuhkan.
That limit is too high!=Had tersebut terlalu tinggi!
Checking @1 ...=Memeriksa @1 ...
Nobody has touched the specified location in @1 seconds.=Tiada siapa menyentuh tempat yang ditentukan dalam masa @1 saat yang lalu.
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 saat yang lalu.
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Ketuk suatu nod (jarak@=@1, saat@=@2, had@=@3)
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nama> [<saat>]) | (:<pelaku> [<saat>])
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Membalikkan perlakuan seorang pemain. Nilai lalai untuk <saat> ialah 60. Tetapkan <saat> ke inf untuk tiada had masa
Invalid parameters. See /help rollback and /help rollback_check.=Parameter tidak sah. Sila lihat /help rollback dan /help rollback_check.
Reverting actions of player '@1' since @2 seconds.=Membalikkan perlakuan pemain '@1' sejak @2 saat lalu.
Reverting actions of @1 since @2 seconds.=Membalikkan perlakuan @1 sejak @2 saat lalu.
(log is too long to show)=(log terlalu panjang untuk ditunjukkan)
Reverting actions succeeded.=Pembalikan perlakuan telah berjaya.
Reverting actions FAILED.=Pembalikan perlakuan telah GAGAL.
Show server status=Tunjukkan status pelayan
This command was disabled by a mod or game.=Perintah ini dilumpuhkan oleh mods atau permainan.
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
Show or set time of day=Tunjukkan atau tetapkan waktu dalam hari
Current time is @1:@2.=Waktu semasa ialah @1:@2.
You don't have permission to run this command (missing privilege: @1).=Anda tiada kebenaran untuk menjalankan perintah ini (tiada keistimewaan: @1).
Invalid time (must be between 0 and 24000).=Waktu tidak sah (mestilah di antara 0 dan 24000)
Time of day changed.=Waktu dalam hari telah ditukar.
Invalid hour (must be between 0 and 23 inclusive).=Jam tidak sah (mestilah di antara 0 dan 23).
Invalid minute (must be between 0 and 59 inclusive).=Minit tidak sah (mestilah di antara 0 dan 59).
Show day count since world creation=Tunjuk jumlah hari telah berlalu sejak penciptaan dunia
Current day is @1.=Hari semasa ialah hari ke-@1.
[<delay_in_seconds> | -1] [-r] [<message>]=[<lengah_dalam_saat> | -1] [-r] [<mesej>]
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Tutup pelayan (-1 membatalkan penutupan terlengah, -r membolehkan pemain untuk sambung semula)
Server shutting down (operator request).=Pelayan sedang ditutup (permintaan pengendali).
Ban the IP of a player or show the ban list=Haramkan IP seorang pemain atau tunjukkan senarai pengharaman
The ban list is empty.=Senarai pengharaman kosong.
Ban list: @1=Senarai pengharaman: @1
You cannot ban players in singleplayer!=Anda tidak boleh mengharamkan pemain dalam mod pemain perseorangan!
Player is not online.=Pemain tersebut tidak berada dalam talian.
Failed to ban player.=Gagal untuk mengharamkan pemain.
Banned @1.=Berjaya mengharamkan @1.
<name> | <IP_address>=<nama> | <alamat_IP>
Remove IP ban belonging to a player/IP=Buang pengharaman IP kepunyaan seorang pemain/IP tertentu
Failed to unban player/IP.=Gagal untuk membuang pengharaman pemain/IP.
Unbanned @1.=Berjaya membuang pengharaman @1.
<name> [<reason>]=<nama> [<sebab>]
Kick a player=Tendang seorang pemain
Failed to kick player @1.=Gagal untuk menendang pemain @1.
Kicked @1.=Berjaya menendang @1.
[full | quick]=[full | quick]
Clear all objects in world=Padamkan semua objek dalam dunia
Invalid usage, see /help clearobjects.=Kegunaan tidak sah, sila lihat /help clearobjects
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Memadamkan semua objek. Ini mungkin ambil masa yang lama. (oleh @1)
Cleared all objects.=Semua objek telah berjaya dipadam.
<name> <message>=<nama> <mesej>
Send a direct message to a player=Menghantar mesej terus kepada seorang pemain
Invalid usage, see /help msg.=Kegunaan tidak sah, sila lihat /help msg.
The player @1 is not online.=Pemain @1 tidak berada dalam talian.
DM from @1: @2=DM daripada @1: @2
Message sent.=Mesej telah dihantar.
Get the last login time of a player or yourself=Dapatkan waktu log masuk terakhir bagi seorang pemain atau diri sendiri
@1's last login time was @2.=Waktu log masuk terakhir @1 ialah pada @2.
@1's last login time is unknown.=Waktu log masuk terakhir @1 tidak diketahui.
Clear the inventory of yourself or another player=Padamkan inventori diri sendiri atau pemain lain
You don't have permission to clear another player's inventory (missing privilege: @1).=Anda tiada kebenaran untuk memadam inventori pemain lain (tiada keistimewaan: @1).
@1 cleared your inventory.=@1 telah memadam inventori anda.
Cleared @1's inventory.=Berjaya memadam inventori @1.
Player must be online to clear inventory!=Pemain mesti berada dalam talian untuk memadam inventorinya!
Players can't be killed, damage has been disabled.=Pemain tidak boleh dibunuh, keupayaan cedera telah dilumpuhkan.
Player @1 is not online.=Pemain @1 tidak berada dalam talian.
You are already dead.=Anda sudah pun mati.
@1 is already dead.=@1 sudah pun mati.
@1 has been killed.=@1 telah berjaya dibunuh.
Kill player or yourself=Bunuh pemain atau diri sendiri
Invalid parameters (see /help @1).=Parameter tidak sah (sila lihat /help @1).
Too many arguments, try using just /help <command>=Terlalu banyak argumen, cuba guna /help <perintah> sahaja
Available commands: @1=Perintah yang tersedia: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Gunakan '/help <perintah>' untuk maklumat lanjut, atau '/help all' untuk senaraikan kesemuanya.
Available commands:=Perintah yang tersedia:
Command not available: @1=Perintah tidak tersedia: @1
[all | privs | <cmd>] [-t]=[all | privs | <perintah>] [-t]
Get help for commands or list privileges (-t: output in chat)=Dapatkan bantuan untuk perintah atau senaraikan keistimewaan (-t: output dalam sembang)
Available privileges:=Keistimewaan yang tersedia:
Command=Perintah
Parameters=Parameter
For more information, click on any entry in the list.=Untuk maklumat lanjut, klik pada mana-mana entri dalam senarai.
Double-click to copy the entry to the chat history.=Klik dua kali untuk salin entri ke sejarah sembang.
Command: @1 @2=Perintah: @1 @2
Available commands: (see also: /help <cmd>)=Perintah yang tersedua: (sila lihat juga: /help <perintah>)
Close=Tutup
Privilege=Keistimewaan
Description=Keterangan
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<tapisan>] | dump [<tapisan>] | save [<format> [<tapisan>]] | reset
Handle the profiler and profiling data=Uruskan pembukah dan data pembukahan
Statistics written to action log.=Statistik telah ditulis ke log perlakuan.
Statistics were reset.=Statistik telah ditetapkan semula.
Usage: @1=Kegunaan: @1
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Format boleh jadi salah satu daripada txt, csv, lua, json, json_pretty (struktur boleh berubah kemudian).
@1 joined the game.=@1 telah menyertai permainan.
@1 left the game.=@1 telah meninggalkan permainan.
@1 left the game (timed out).=@1 telah meninggalkan permainan (tamat tempoh masa).
(no description)=(tiada keterangan)
Can interact with things and modify the world=Boleh berinteraksi dengan barangan dan mengubah dunia
Can speak in chat=Boleh bercakap dalam sembang
Can modify basic privileges (@1)=Boleh mengubah keistimewaan asas (@1)
Can modify privileges=Boleh mengubah keistimewaan
Can teleport self=Boleh merentas jarak diri sendiri
Can teleport other players=Boleh merentas jarak pemain lain
Can set the time of day using /time=Boleh menetapkan waktu dalam hari menggunakan /time
Can do server maintenance stuff=Boleh melakukan perkara berkaitan penyenggaraan pelayan
Can bypass node protection in the world=Boleh memintas perlindungan nod dalam dunia
Can ban and unban players=Boleh mengharamkan dan membuang pengharaman pemain
Can kick players=Boleh menendang pemain
Can use /give and /giveme=Boleh menggunakan /give dan /giveme
Can use /setpassword and /clearpassword=Boleh menggunakan /setpassword dan /clearpassword
Can use fly mode=Boleh menggunakan mod terbang
Can use fast mode=Boleh menggunakan mod pergerakan pantas
Can fly through solid nodes using noclip mode=Boleh terbang menerusi nod pepejal menggunakan mod tembus blok
Can use the rollback functionality=Boleh menggunakan keberfungsian gulung balik
Can enable wireframe=Boleh membolehkan rangka dawai
Unknown Item=Item Tidak Diketahui
Air=Udara
Ignore=Abai
You can't place 'ignore' nodes!=Anda tidak boleh meletakkan nod 'abai'!
Values below show absolute/relative times spend per server step by the instrumented function.=Nilai di bawah menunjukkan masa mutlak/relatif yang digunakan oleh fungsi yang dipasangkan pada setiap langkah pelayan
A total of @1 sample(s) were taken.=Sebanyak @1 sampel telah diambil secara keseluruhan.
The output is limited to '@1'.=Output dihadkan kepada '@1'.
Saving of profile failed: @1=Penyimpanan profil telah gagal: @1
Profile saved to @1=Profil telah disimpan ke @1

View File

@@ -1,246 +0,0 @@
# textdomain: __builtin
Empty command.=
Invalid command: @1=
Invalid command usage.=
(@1 s)=
Command execution took @1 s=
You don't have permission to run this command (missing privileges: @1).=
Unable to get position of player @1.=
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=
<action>=
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=
Show the name of the server owner=
The administrator of this server is @1.=
There's no administrator named in the config file.=
@1 does not have any privileges.=
Privileges of @1: @2=
[<name>]=
Show privileges of yourself or another player=
Player @1 does not exist.=
<privilege>=
Return list of all online players with privilege=
Invalid parameters (see /help haspriv).=
Unknown privilege!=
No online player has the "@1" privilege.=
Players online with the "@1" privilege: @2=
Your privileges are insufficient.=
Your privileges are insufficient. '@1' only allows you to grant: @2=
Unknown privilege: @1=
@1 granted you privileges: @2=
<name> (<privilege> [, <privilege2> [<...>]] | all)=
Give privileges to player=
Invalid parameters (see /help grant).=
<privilege> [, <privilege2> [<...>]] | all=
Grant privileges to yourself=
Invalid parameters (see /help grantme).=
Your privileges are insufficient. '@1' only allows you to revoke: @2=
Note: Cannot revoke in singleplayer: @1=
Note: Cannot revoke from admin: @1=
No privileges were revoked.=
@1 revoked privileges from you: @2=
Remove privileges from player=
Invalid parameters (see /help revoke).=
Revoke privileges from yourself=
Invalid parameters (see /help revokeme).=
<name> <password>=
Set player's password (sent unencrypted, thus insecure)=
Name field required.=
Your password was cleared by @1.=
Password of player "@1" cleared.=
Your password was set by @1.=
Password of player "@1" set.=
<name>=
Set empty password for a player=
Reload authentication data=
Done.=
Failed.=
Remove a player's data=
Player "@1" removed.=
No such player "@1" to remove.=
Player "@1" is connected, cannot remove.=
Unhandled remove_player return code @1.=
Cannot teleport out of map bounds!=
Cannot get player with name @1.=
Cannot teleport, @1 is attached to an object!=
Teleporting @1 to @2.=
One does not teleport to oneself.=
Cannot get teleportee with name @1.=
Cannot get target player with name @1.=
Teleporting @1 to @2 at @3.=
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=
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>=
Invalid parameters (see /help set).=
Finished emerging @1 blocks in @2ms.=
emergeblocks update: @1/@2 blocks emerged (@3%)=
(here [<radius>]) | (<pos1> <pos2>)=
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=
Started emerge of area ranging from @1 to @2.=
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=
Successfully cleared area ranging from @1 to @2.=
Failed to clear one or more blocks in area.=
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=
Successfully reset light in the area ranging from @1 to @2.=
Failed to load one or more blocks in area.=
List mods installed on the server=
No mods installed.=
Cannot give an empty item.=
Cannot give an unknown item.=
Giving 'ignore' is not allowed.=
@1 is not a known player.=
@1 partially added to inventory.=
@1 could not be added to inventory.=
@1 added to inventory.=
@1 partially added to inventory of @2.=
@1 could not be added to inventory of @2.=
@1 added to inventory of @2.=
<name> <ItemString> [<count> [<wear>]]=
Give item to player=
Name and ItemString required.=
<ItemString> [<count> [<wear>]]=
Give item to yourself=
ItemString required.=
<EntityName> [<X>,<Y>,<Z>]=
Spawn entity at given (or your) position=
EntityName required.=
Unable to spawn entity, player is nil.=
Cannot spawn an unknown entity.=
Invalid parameters (@1).=
@1 spawned.=
@1 failed to spawn.=
Destroy item in hand=
Unable to pulverize, no player.=
Unable to pulverize, no item in hand.=
An item was pulverized.=
[<range>] [<seconds>] [<limit>]=
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=
Rollback functions are disabled.=
That limit is too high!=
Checking @1 ...=
Nobody has touched the specified location in @1 seconds.=
@1 @2 @3 -> @4 @5 seconds ago.=
Punch a node (range@=@1, seconds@=@2, limit@=@3).=
(<name> [<seconds>]) | (:<actor> [<seconds>])=
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=
Invalid parameters. See /help rollback and /help rollback_check.=
Reverting actions of player '@1' since @2 seconds.=
Reverting actions of @1 since @2 seconds.=
(log is too long to show)=
Reverting actions succeeded.=
Reverting actions FAILED.=
Show server status=
This command was disabled by a mod or game.=
[<0..23>:<0..59> | <0..24000>]=
Show or set time of day=
Current time is @1:@2.=
You don't have permission to run this command (missing privilege: @1).=
Invalid time (must be between 0 and 24000).=
Time of day changed.=
Invalid hour (must be between 0 and 23 inclusive).=
Invalid minute (must be between 0 and 59 inclusive).=
Show day count since world creation=
Current day is @1.=
[<delay_in_seconds> | -1] [-r] [<message>]=
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=
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.=
<name> | <IP_address>=
Remove IP ban belonging to a player/IP=
Failed to unban player/IP.=
Unbanned @1.=
<name> [<reason>]=
Kick a player=
Failed to kick player @1.=
Kicked @1.=
[full | quick]=
Clear all objects in world=
Invalid usage, see /help clearobjects.=
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=
Cleared all objects.=
<name> <message>=
Send a direct message to a player=
Invalid usage, see /help msg.=
The player @1 is not online.=
DM from @1: @2=
Message sent.=
Get the last login time of a player or yourself=
@1's last login time was @2.=
@1's last login time is unknown.=
Clear the inventory of yourself or another player=
You don't have permission to clear another player's inventory (missing privilege: @1).=
@1 cleared your inventory.=
Cleared @1's inventory.=
Player must be online to clear inventory!=
Players can't be killed, damage has been disabled.=
Player @1 is not online.=
You are already dead.=
@1 is already dead.=
@1 has been killed.=
Kill player or yourself=
Invalid parameters (see /help @1).=
Too many arguments, try using just /help <command>=
Available commands: @1=
Use '/help <cmd>' to get more information, or '/help all' to list everything.=
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.=
Double-click to copy the entry to the chat history.=
Command: @1 @2=
Available commands: (see also: /help <cmd>)=
Close=
Privilege=
Description=
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=
Handle the profiler and profiling data=
Statistics written to action log.=
Statistics were reset.=
Usage: @1=
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=
@1 joined the game.=
@1 left the game.=
@1 left the game (timed out).=
(no description)=
Can interact with things and modify the world=
Can speak in chat=
Can modify basic privileges (@1)=
Can modify privileges=
Can teleport self=
Can teleport other players=
Can set the time of day using /time=
Can do server maintenance stuff=
Can bypass node protection in the world=
Can ban and unban players=
Can kick players=
Can use /give and /giveme=
Can use /setpassword and /clearpassword=
Can use fly mode=
Can use fast mode=
Can fly through solid nodes using noclip mode=
Can use the rollback functionality=
Can enable wireframe=
Unknown Item=
Air=
Ignore=
You can't place 'ignore' nodes!=
Values below show absolute/relative times spend per server step by the instrumented function.=
A total of @1 sample(s) were taken.=
The output is limited to '@1'.=
Saving of profile failed: @1=
Profile saved to @1=

View File

@@ -14,11 +14,14 @@
--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.
--------------------------------------------------------------------------------
-- Global menu data
--------------------------------------------------------------------------------
menudata = {}
--------------------------------------------------------------------------------
-- Local cached values
--------------------------------------------------------------------------------
local min_supp_proto, max_supp_proto
function common_update_cached_supp_proto()
@@ -26,12 +29,14 @@ function common_update_cached_supp_proto()
max_supp_proto = core.get_max_supp_proto()
end
common_update_cached_supp_proto()
--------------------------------------------------------------------------------
-- Menu helper functions
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function render_client_count(n)
if n > 999 then return '99+'
elseif n >= 0 then return tostring(n)
if n > 99 then return '99+'
elseif n >= 0 then return tostring(n)
else return '?' end
end
@@ -45,28 +50,21 @@ local function configure_selected_world_params(idx)
end
end
-- retrieved from https://wondernetwork.com/pings with (hopefully) representative cities
-- Amsterdam, Auckland, Brasilia, Denver, Lagos, Singapore
local latency_matrix = {
["AF"] = { ["AS"]=258, ["EU"]=100, ["NA"]=218, ["OC"]=432, ["SA"]=308 },
["AS"] = { ["EU"]=168, ["NA"]=215, ["OC"]=125, ["SA"]=366 },
["EU"] = { ["NA"]=120, ["OC"]=298, ["SA"]=221 },
["NA"] = { ["OC"]=202, ["SA"]=168 },
["OC"] = { ["SA"]=411 },
["SA"] = {}
}
function estimate_continent_latency(own, spec)
local there = spec.geo_continent
if not own or not there then
return nil
end
if own == there then
return 0
end
return latency_matrix[there][own] or latency_matrix[own][there]
--------------------------------------------------------------------------------
function image_column(tooltip, flagname)
return "image,tooltip=" .. core.formspec_escape(tooltip) .. "," ..
"0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," ..
"1=" .. core.formspec_escape(defaulttexturedir ..
(flagname and "server_flags_" .. flagname .. ".png" or "blank.png")) .. "," ..
"2=" .. core.formspec_escape(defaulttexturedir .. "server_ping_4.png") .. "," ..
"3=" .. core.formspec_escape(defaulttexturedir .. "server_ping_3.png") .. "," ..
"4=" .. core.formspec_escape(defaulttexturedir .. "server_ping_2.png") .. "," ..
"5=" .. core.formspec_escape(defaulttexturedir .. "server_ping_1.png")
end
function render_serverlist_row(spec)
--------------------------------------------------------------------------------
function render_serverlist_row(spec, is_favorite)
local text = ""
if spec.name then
text = text .. core.formspec_escape(spec.name:trim())
@@ -77,29 +75,31 @@ function render_serverlist_row(spec)
end
end
local grey_out = not spec.is_compatible
local grey_out = not is_server_protocol_compat(spec.proto_min, spec.proto_max)
local details = {}
if spec.lag or spec.ping then
local lag = (spec.lag or 0) * 1000 + (spec.ping or 0) * 250
if lag <= 125 then
table.insert(details, "1")
elseif lag <= 175 then
table.insert(details, "2")
elseif lag <= 250 then
table.insert(details, "3")
else
table.insert(details, "4")
end
local details
if is_favorite then
details = "1,"
else
table.insert(details, "0")
details = "0,"
end
table.insert(details, ",")
if spec.ping then
local ping = spec.ping * 1000
if ping <= 50 then
details = details .. "2,"
elseif ping <= 100 then
details = details .. "3,"
elseif ping <= 250 then
details = details .. "4,"
else
details = details .. "5,"
end
else
details = details .. "0,"
end
local color = (grey_out and "#aaaaaa") or ((spec.is_favorite and "#ddddaa") or "#ffffff")
if spec.clients and (spec.clients_max or 0) > 0 then
if spec.clients and spec.clients_max then
local clients_percent = 100 * spec.clients / spec.clients_max
-- Choose a color depending on how many clients are connected
@@ -110,53 +110,68 @@ function render_serverlist_row(spec)
elseif clients_percent <= 60 then clients_color = '#a1e587' -- 0-60%: green
elseif clients_percent <= 90 then clients_color = '#ffdc97' -- 60-90%: yellow
elseif clients_percent == 100 then clients_color = '#dd5b5b' -- full server: red (darker)
else clients_color = '#ffba97' -- 90-100%: orange
else clients_color = '#ffba97' -- 90-100%: orange
end
table.insert(details, clients_color)
table.insert(details, render_client_count(spec.clients) .. " / " ..
render_client_count(spec.clients_max))
details = details .. clients_color .. ',' ..
render_client_count(spec.clients) .. ',/,' ..
render_client_count(spec.clients_max) .. ','
elseif grey_out then
details = details .. '#aaaaaa,?,/,?,'
else
table.insert(details, color)
table.insert(details, "?")
details = details .. ',?,/,?,'
end
if spec.creative then
table.insert(details, "1") -- creative icon
details = details .. "1,"
else
table.insert(details, "0")
details = details .. "0,"
end
if spec.damage then
details = details .. "1,"
else
details = details .. "0,"
end
if spec.pvp then
table.insert(details, "2") -- pvp icon
elseif spec.damage then
table.insert(details, "1") -- heart icon
details = details .. "1,"
else
table.insert(details, "0")
details = details .. "0,"
end
table.insert(details, color)
table.insert(details, text)
return details .. (grey_out and '#aaaaaa,' or ',') .. text
end
return table.concat(details, ",")
end
---------------------------------------------------------------------------------
os.tmpname = function()
error('do not use') -- instead use core.get_temp_path()
end
--------------------------------------------------------------------------------
os.tempfolder = function()
local temp = core.get_temp_path()
return temp .. DIR_DELIM .. "MT_" .. math.random(0, 10000)
end
--------------------------------------------------------------------------------
os.tmpname = function()
local path = os.tempfolder()
io.open(path, "w"):close()
return path
end
--------------------------------------------------------------------------------
function menu_render_worldlist()
local retval = {}
local retval = ""
local current_worldlist = menudata.worldlist:get_list()
for i, v in ipairs(current_worldlist) do
retval[#retval+1] = core.formspec_escape(v.name)
if retval ~= "" then retval = retval .. "," end
retval = retval .. core.formspec_escape(v.name) ..
" \\[" .. core.formspec_escape(v.gameid) .. "\\]"
end
return table.concat(retval, ",")
return retval
end
--------------------------------------------------------------------------------
function menu_handle_key_up_down(fields, textlist, settingname)
local oldidx, newidx = core.get_textlist_index(textlist), 1
if fields.key_up or fields.key_down then
@@ -173,6 +188,7 @@ function menu_handle_key_up_down(fields, textlist, settingname)
return false
end
--------------------------------------------------------------------------------
function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transparency)
local textlines = core.wrap_text(text, textlen, true)
local retval = "textlist[" .. xpos .. "," .. ypos .. ";" .. width ..
@@ -190,6 +206,7 @@ function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transp
return retval
end
--------------------------------------------------------------------------------
function is_server_protocol_compat(server_proto_min, server_proto_max)
if (not server_proto_min) or (not server_proto_max) then
-- There is no info. Assume the best and act as if we would be compatible.
@@ -197,7 +214,7 @@ function is_server_protocol_compat(server_proto_min, server_proto_max)
end
return min_supp_proto <= server_proto_max and max_supp_proto >= server_proto_min
end
--------------------------------------------------------------------------------
function is_server_protocol_compat_or_error(server_proto_min, server_proto_max)
if not is_server_protocol_compat(server_proto_min, server_proto_max) then
local server_prot_ver_info, client_prot_ver_info
@@ -225,7 +242,7 @@ function is_server_protocol_compat_or_error(server_proto_min, server_proto_max)
return true
end
--------------------------------------------------------------------------------
function menu_worldmt(selected, setting, value)
local world = menudata.worldlist:get_list()[selected]
if world then
@@ -257,11 +274,3 @@ 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

View File

@@ -57,60 +57,8 @@ local function init_data(data)
hide_game = data.hide_gamemods,
hide_modpackcontents = data.hide_modpackcontents
})
-- Sorting is already done by pgkmgr.get_mods
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
data.list:add_sort_mechanism("alphabetic", sort_mod_list)
data.list:set_sortmode("alphabetic")
end
local function get_formspec(data)
@@ -118,10 +66,7 @@ local function get_formspec(data)
init_data(data)
end
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 mod = data.list:get_list()[data.selected_mod] or {name = ""}
local retval =
"size[11.5,7.5,true]" ..
@@ -142,29 +87,6 @@ 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, ",")
@@ -216,6 +138,7 @@ 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;" ..
@@ -240,16 +163,10 @@ 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;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]" ..
"tablecolumns[color;tree;text]" ..
"table[5.5,0.75;5.75,6;world_config_modlist;" ..
pkgmgr.render_packagelist(data.list, use_technical_names, with_error) .. ";" .. data.selected_mod .."]"
pkgmgr.render_packagelist(data.list) .. ";" .. data.selected_mod .."]"
end
local function handle_buttons(this, fields)
@@ -288,19 +205,14 @@ 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
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
worldfile:set("load_mod_" .. mod.name,
mod.enabled and "true" or "false")
elseif mod.enabled then
gamedata.errormessage = fgettext_ne("Failed to enable mo" ..
"d \"$1\" as it contains disallowed characters. " ..
@@ -344,26 +256,12 @@ 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 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
and not list[i].is_modpack then
list[i].enabled = true
was_enabled[list[i].name] = true
end
end
enabled_all = true
return true
end

View File

@@ -25,7 +25,7 @@ end
-- Unordered preserves the original order of the ContentDB API,
-- before the package list is ordered based on installed state.
local store = { packages = {}, packages_full = {}, packages_full_unordered = {}, aliases = {} }
local store = { packages = {}, packages_full = {}, packages_full_unordered = {} }
local http = core.get_http_api()
@@ -57,79 +57,36 @@ local filter_types_type = {
"txp",
}
local REASON_NEW = "new"
local REASON_UPDATE = "update"
local REASON_DEPENDENCY = "dependency"
-- encodes for use as URL parameter or path component
local function urlencode(str)
return str:gsub("[^%a%d()._~-]", function(char)
return string.format("%%%02X", string.byte(char))
end)
end
assert(urlencode("sample text?") == "sample%20text%3F")
local function get_download_url(package, reason)
local base_url = core.settings:get("contentdb_url")
local ret = base_url .. ("/packages/%s/releases/%d/download/"):format(
package.url_part, package.release)
if reason then
ret = ret .. "?reason=" .. reason
end
return ret
end
local function download_and_extract(param)
local package = param.package
local filename = core.get_temp_path(true)
if filename == "" or not core.download_file(param.url, filename) then
core.log("error", "Downloading " .. dump(param.url) .. " failed")
local function download_package(param)
if core.download_file(param.package.url, param.filename) then
return {
msg = fgettext("Failed to download \"$1\"", package.title)
filename = param.filename,
successful = true,
}
end
local tempfolder = core.get_temp_path()
if tempfolder ~= "" then
tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000)
if not core.extract_zip(filename, tempfolder) then
tempfolder = nil
end
else
tempfolder = nil
end
os.remove(filename)
if not tempfolder then
core.log("error", "downloading " .. dump(param.package.url) .. " failed")
return {
msg = fgettext("Failed to extract \"$1\" (unsupported file type or broken archive)", package.title),
successful = false,
}
end
return {
path = tempfolder
}
end
local function start_install(package, reason)
local function start_install(package)
local params = {
package = package,
url = get_download_url(package, reason),
filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip",
}
number_downloading = number_downloading + 1
local function callback(result)
if result.msg then
gamedata.errormessage = result.msg
else
local path, msg = pkgmgr.install_dir(package.type, result.path, package.name, package.path)
core.delete_dir(result.path)
if result.successful then
local path, msg = pkgmgr.install(package.type,
result.filename, package.name,
package.path)
if not path then
gamedata.errormessage = fgettext("Error installing \"$1\": $2", package.title, msg)
gamedata.errormessage = msg
else
core.log("action", "Installed package to " .. path)
@@ -151,9 +108,11 @@ local function start_install(package, reason)
if conf_path then
local conf = Settings(conf_path)
conf:set("title", package.title)
if not name_is_title then
conf:set("name", package.name)
if name_is_title then
conf:set("name", package.title)
else
conf:set("title", package.title)
conf:set("name", package.name)
end
if not conf:get("description") then
conf:set("description", package.short_description)
@@ -163,6 +122,9 @@ local function start_install(package, reason)
conf:write()
end
end
os.remove(result.filename)
else
gamedata.errormessage = fgettext("Failed to download $1", package.name)
end
package.downloading = false
@@ -173,7 +135,7 @@ local function start_install(package, reason)
if next then
table.remove(download_queue, 1)
start_install(next.package, next.reason)
start_install(next)
end
ui.update()
@@ -182,19 +144,19 @@ local function start_install(package, reason)
package.queued = false
package.downloading = true
if not core.handle_async(download_and_extract, params, callback) then
if not core.handle_async(download_package, params, callback) then
core.log("error", "ERROR: async event failed")
gamedata.errormessage = fgettext("Failed to download $1", package.name)
return
end
end
local function queue_download(package, reason)
local function queue_download(package)
local max_concurrent_downloads = tonumber(core.settings:get("contentdb_max_concurrent_downloads"))
if number_downloading < math.max(max_concurrent_downloads, 1) then
start_install(package, reason)
if number_downloading < max_concurrent_downloads then
start_install(package)
else
table.insert(download_queue, { package = package, reason = reason })
table.insert(download_queue, package)
package.queued = true
end
end
@@ -207,7 +169,7 @@ local function get_raw_dependencies(package)
local url_fmt = "/api/packages/%s/dependencies/?only_hard=1&protocol_version=%s&engine_version=%s"
local version = core.get_version()
local base_url = core.settings:get("contentdb_url")
local url = base_url .. url_fmt:format(package.url_part, core.get_max_supp_proto(), urlencode(version.string))
local url = base_url .. url_fmt:format(package.id, core.get_max_supp_proto(), version.string)
local response = http.fetch_sync({ url = url })
if not response.succeeded then
@@ -278,7 +240,7 @@ local function resolve_dependencies_2(raw_deps, installed_mods, out)
end
end
-- Otherwise, find the first mod that fulfills it
-- Otherwise, find the first mod that fulfils it
if fallback then
return {
is_optional = dep.is_optional,
@@ -346,21 +308,22 @@ end
local install_dialog = {}
function install_dialog.get_formspec()
local selected_game, selected_game_idx = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
if not selected_game_idx then
selected_game_idx = 1
selected_game = pkgmgr.games[1]
end
local game_list = {}
for i, game in ipairs(pkgmgr.games) do
game_list[i] = core.formspec_escape(game.title)
end
local package = install_dialog.package
local raw_deps = install_dialog.raw_deps
local will_install_deps = install_dialog.will_install_deps
local selected_game_idx = 1
local selected_gameid = core.settings:get("menu_last_game")
local games = table.copy(pkgmgr.games)
for i=1, #games do
if selected_gameid and games[i].id == selected_gameid then
selected_game_idx = i
end
games[i] = core.formspec_escape(games[i].name)
end
local selected_game = pkgmgr.games[selected_game_idx]
local deps_to_install = 0
local deps_not_found = 0
@@ -407,7 +370,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;selected_game;", table.concat(game_list, ","), ";", selected_game_idx, "]",
"dropdown[2,0;4.25,0.5;gameid;", table.concat(games, ","), ";", selected_game_idx, "]",
"label[0,0.8;", fgettext("Dependencies:"), "]",
@@ -444,12 +407,12 @@ function install_dialog.handle_submit(this, fields)
end
if fields.install_all then
queue_download(install_dialog.package, REASON_NEW)
queue_download(install_dialog.package)
if install_dialog.will_install_deps then
for _, dep in pairs(install_dialog.dependencies) do
if not dep.is_optional and not dep.installed and dep.package then
queue_download(dep.package, REASON_DEPENDENCY)
queue_download(dep.package)
end
end
end
@@ -458,9 +421,9 @@ function install_dialog.handle_submit(this, fields)
return true
end
if fields.selected_game then
if fields.gameid then
for _, game in pairs(pkgmgr.games) do
if game.title == fields.selected_game then
if game.name == fields.gameid then
core.settings:set("menu_last_game", game.id)
break
end
@@ -487,10 +450,12 @@ local confirm_overwrite = {}
function confirm_overwrite.get_formspec()
local package = confirm_overwrite.package
return confirmation_formspec(
fgettext("\"$1\" already exists. Would you like to overwrite it?", package.name),
'install', fgettext("Overwrite"),
'cancel', fgettext("Cancel"))
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") .. "]"
end
function confirm_overwrite.handle_submit(this, fields)
@@ -579,43 +544,33 @@ function store.load()
local base_url = core.settings:get("contentdb_url")
local url = base_url ..
"/api/packages/?type=mod&type=game&type=txp&protocol_version=" ..
core.get_max_supp_proto() .. "&engine_version=" .. urlencode(version.string)
core.get_max_supp_proto() .. "&engine_version=" .. version.string
for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do
item = item:trim()
if item ~= "" then
url = url .. "&hide=" .. urlencode(item)
url = url .. "&hide=" .. item
end
end
local response = http.fetch_sync({ url = url })
local timeout = tonumber(core.settings:get("curl_file_download_timeout"))
local response = http.fetch_sync({ url = url, timeout = timeout })
if not response.succeeded then
return
end
store.packages_full = core.parse_json(response.data) or {}
store.aliases = {}
for _, package in pairs(store.packages_full) do
package.url = base_url .. "/packages/" ..
package.author .. "/" .. package.name ..
"/releases/" .. package.release .. "/download/"
local name_len = #package.name
-- This must match what store.update_paths() does!
package.id = package.author:lower() .. "/"
if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
package.id = package.id .. package.name:sub(1, name_len - 5)
package.id = package.author:lower() .. "/" .. package.name:sub(1, name_len - 5)
else
package.id = package.id .. package.name
end
package.url_part = urlencode(package.author) .. "/" .. urlencode(package.name)
if package.aliases then
for _, alias in ipairs(package.aliases) do
-- We currently don't support name changing
local suffix = "/" .. package.name
if alias:sub(-#suffix) == suffix then
store.aliases[alias:lower()] = package.id
end
end
package.id = package.author:lower() .. "/" .. package.name
end
end
@@ -629,8 +584,7 @@ function store.update_paths()
pkgmgr.refresh_globals()
for _, mod in pairs(pkgmgr.global_mods:get_list()) do
if mod.author and mod.release > 0 then
local id = mod.author:lower() .. "/" .. mod.name
mod_hash[store.aliases[id] or id] = mod
mod_hash[mod.author:lower() .. "/" .. mod.name] = mod
end
end
@@ -638,16 +592,14 @@ function store.update_paths()
pkgmgr.update_gamelist()
for _, game in pairs(pkgmgr.games) do
if game.author ~= "" and game.release > 0 then
local id = game.author:lower() .. "/" .. game.id
game_hash[store.aliases[id] or id] = game
game_hash[game.author:lower() .. "/" .. game.id] = game
end
end
local txp_hash = {}
for _, txp in pairs(pkgmgr.get_texture_packs()) do
if txp.author and txp.release > 0 then
local id = txp.author:lower() .. "/" .. txp.name
txp_hash[store.aliases[id] or id] = txp
txp_hash[txp.author:lower() .. "/" .. txp.name] = txp
end
end
@@ -857,7 +809,8 @@ function store.get_formspec(dlgdata)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif package.queued then
formspec[#formspec + 1] = left_base
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_queued.png;queued]"
elseif not package.path then
local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
@@ -962,7 +915,7 @@ function store.handle_submit(this, fields)
local package = store.packages_full[i]
if package.path and package.installed_release < package.release and
not (package.downloading or package.queued) then
queue_download(package, REASON_UPDATE)
queue_download(package)
end
end
return true
@@ -995,7 +948,7 @@ function store.handle_submit(this, fields)
this:hide()
dlg:show()
else
queue_download(package, package.path and REASON_UPDATE or REASON_NEW)
queue_download(package)
end
end
@@ -1020,9 +973,9 @@ function store.handle_submit(this, fields)
end
if fields["view_" .. i] then
local url = ("%s/packages/%s?protocol_version=%d"):format(
core.settings:get("contentdb_url"), package.url_part,
core.get_max_supp_proto())
local url = ("%s/packages/%s/%s?protocol_version=%d"):format(
core.settings:get("contentdb_url"),
package.author, package.name, core.get_max_supp_proto())
core.open_url(url)
return true
end

View File

@@ -15,6 +15,8 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local worldname = ""
local function table_to_flags(ftable)
-- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"
local str = {}
@@ -29,8 +31,9 @@ local function strflag(flags, flag)
return (flags[flag] == true) and "true" or "false"
end
local cb_caverns = { "caverns", fgettext("Caverns"),
local cb_caverns = { "caverns", fgettext("Caverns"), "caverns",
fgettext("Very large caverns deep in the underground") }
local tt_sea_rivers = fgettext("Sea level rivers")
local flag_checkboxes = {
v5 = {
@@ -38,38 +41,39 @@ local flag_checkboxes = {
},
v7 = {
cb_caverns,
{ "ridges", fgettext("Rivers"), fgettext("Sea level rivers") },
{ "mountains", fgettext("Mountains") },
{ "floatlands", fgettext("Floatlands (experimental)"),
{ "ridges", fgettext("Rivers"), "ridges", tt_sea_rivers },
{ "mountains", fgettext("Mountains"), "mountains" },
{ "floatlands", fgettext("Floatlands (experimental)"), "floatlands",
fgettext("Floating landmasses in the sky") },
},
carpathian = {
cb_caverns,
{ "rivers", fgettext("Rivers"), fgettext("Sea level rivers") },
{ "rivers", fgettext("Rivers"), "rivers", tt_sea_rivers },
},
valleys = {
{ "altitude_chill", fgettext("Altitude chill"),
{ "altitude-chill", fgettext("Altitude chill"), "altitude_chill",
fgettext("Reduces heat with altitude") },
{ "altitude_dry", fgettext("Altitude dry"),
{ "altitude-dry", fgettext("Altitude dry"), "altitude_dry",
fgettext("Reduces humidity with altitude") },
{ "humid_rivers", fgettext("Humid rivers"),
{ "humid-rivers", fgettext("Humid rivers"), "humid_rivers",
fgettext("Increases humidity around rivers") },
{ "vary_river_depth", fgettext("Vary river depth"),
{ "vary-river-depth", fgettext("Vary river depth"), "vary_river_depth",
fgettext("Low humidity and high heat causes shallow or dry rivers") },
},
flat = {
cb_caverns,
{ "hills", fgettext("Hills") },
{ "lakes", fgettext("Lakes") },
{ "hills", fgettext("Hills"), "hills" },
{ "lakes", fgettext("Lakes"), "lakes" },
},
fractal = {
{ "terrain", fgettext("Additional terrain"),
{ "terrain", fgettext("Additional terrain"), "terrain",
fgettext("Generate non-fractal terrain: Oceans and underground") },
},
v6 = {
{ "trees", fgettext("Trees and jungle grass") },
{ "flat", fgettext("Flat terrain") },
{ "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") },
{ "trees", fgettext("Trees and jungle grass"), "trees" },
{ "flat", fgettext("Flat terrain"), "flat" },
{ "mudflow", fgettext("Mud flow"), "mudflow",
fgettext("Terrain surface erosion") },
-- Biome settings are in mgv6_biomes below
},
}
@@ -91,31 +95,48 @@ local mgv6_biomes = {
local function create_world_formspec(dialogdata)
-- Point the player to ContentDB when no games are found
-- Error out when no games found
if #pkgmgr.games == 0 then
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") .. "]"
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") .. "]"
end
local current_mg = dialogdata.mg
local mapgens = core.get_mapgen_names()
local flags = dialogdata.flags
local current_seed = core.settings:get("fixed_map_seed") or ""
local current_mg = core.settings:get("mg_name")
local gameid = core.settings:get("menu_last_game")
local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
if game == nil then
-- should never happen but just pick the first game
game = pkgmgr.games[1]
core.settings:set("menu_last_game", game.id)
local flags = {
main = core.settings:get_flags("mg_flags"),
v5 = core.settings:get_flags("mgv5_spflags"),
v6 = core.settings:get_flags("mgv6_spflags"),
v7 = core.settings:get_flags("mgv7_spflags"),
fractal = core.settings:get_flags("mgfractal_spflags"),
carpathian = core.settings:get_flags("mgcarpathian_spflags"),
valleys = core.settings:get_flags("mgvalleys_spflags"),
flat = core.settings:get_flags("mgflat_spflags"),
}
local gameidx = 0
if gameid ~= nil then
local _
_, gameidx = pkgmgr.find_by_gameid(gameid)
if gameidx == nil then
gameidx = 0
end
end
local game_by_gameidx = core.get_game(gameidx)
local disallowed_mapgen_settings = {}
if game ~= nil then
local gameconfig = Settings(game.path.."/game.conf")
if game_by_gameidx ~= nil then
local gamepath = game_by_gameidx.path
local gameconfig = Settings(gamepath.."/game.conf")
local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split()
for key, value in pairs(allowed_mapgens) do
@@ -135,7 +156,7 @@ local function create_world_formspec(dialogdata)
end
end
if #disallowed_mapgens > 0 then
if disallowed_mapgens then
for i = #mapgens, 1, -1 do
if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then
table.remove(mapgens, i)
@@ -151,29 +172,23 @@ local function create_world_formspec(dialogdata)
local mglist = ""
local selindex
do -- build the list of mapgens
local i = 1
local first_mg
for k, v in pairs(mapgens) do
if not first_mg then
first_mg = v
end
if current_mg == v then
selindex = i
end
i = i + 1
mglist = mglist .. core.formspec_escape(v) .. ","
local i = 1
local first_mg
for k,v in pairs(mapgens) do
if not first_mg then
first_mg = v
end
if not selindex then
selindex = 1
current_mg = first_mg
if current_mg == v then
selindex = i
end
mglist = mglist:sub(1, -2)
i = i + 1
mglist = mglist .. v .. ","
end
-- The logic of the flag element IDs is as follows:
-- "flag_main_foo-bar-baz" controls dialogdata.flags["main"]["foo_bar_baz"]
-- see the buttonhandler for the implementation of this
if not selindex then
selindex = 1
current_mg = first_mg
end
mglist = mglist:sub(1, -2)
local mg_main_flags = function(mapgen, y)
if mapgen == "singlenode" then
@@ -183,11 +198,11 @@ local function create_world_formspec(dialogdata)
return "", y
end
local form = "checkbox[0," .. y .. ";flag_main_caves;" ..
local form = "checkbox[0," .. y .. ";flag_mg_caves;" ..
fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]"
y = y + 0.5
form = form .. "checkbox[0,"..y..";flag_main_dungeons;" ..
form = form .. "checkbox[0,"..y..";flag_mg_dungeons;" ..
fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]"
y = y + 0.5
@@ -198,7 +213,7 @@ local function create_world_formspec(dialogdata)
else
d_tt = fgettext("Structures appearing on the terrain, typically trees and plants")
end
form = form .. "checkbox[0,"..y..";flag_main_decorations;" ..
form = form .. "checkbox[0,"..y..";flag_mg_decorations;" ..
d_name .. ";" ..
strflag(flags.main, "decorations").."]" ..
"tooltip[flag_mg_decorations;" ..
@@ -206,7 +221,7 @@ local function create_world_formspec(dialogdata)
"]"
y = y + 0.5
form = form .. "tooltip[flag_main_caves;" ..
form = form .. "tooltip[flag_mg_caves;" ..
fgettext("Network of tunnels and caves")
.. "]"
return form, y
@@ -220,13 +235,13 @@ local function create_world_formspec(dialogdata)
return "", y
end
local form = ""
for _, tab in pairs(flag_checkboxes[mapgen]) do
local id = "flag_"..mapgen.."_"..tab[1]:gsub("_", "-")
for _,tab in pairs(flag_checkboxes[mapgen]) do
local id = "flag_mg"..mapgen.."_"..tab[1]
form = form .. ("checkbox[0,%f;%s;%s;%s]"):
format(y, id, tab[2], strflag(flags[mapgen], tab[1]))
format(y, id, tab[2], strflag(flags[mapgen], tab[3]))
if tab[3] then
form = form .. "tooltip["..id..";"..tab[3].."]"
if tab[4] then
form = form .. "tooltip["..id..";"..tab[4].."]"
end
y = y + 0.5
end
@@ -262,14 +277,16 @@ local function create_world_formspec(dialogdata)
-- biomeblend
y = y + 0.55
form = form .. "checkbox[0,"..y..";flag_v6_biomeblend;" ..
form = form .. "checkbox[0,"..y..";flag_mgv6_biomeblend;" ..
fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" ..
"tooltip[flag_v6_biomeblend;" ..
"tooltip[flag_mgv6_biomeblend;" ..
fgettext("Smooth transition between biomes") .. "]"
return form, y
end
current_seed = core.formspec_escape(current_seed)
local y_start = 0.0
local y = y_start
local str_flags, str_spflags
@@ -288,6 +305,17 @@ 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]" ..
@@ -295,33 +323,21 @@ local function create_world_formspec(dialogdata)
"container[0,0]"..
"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]"
";" .. core.formspec_escape(worldname) .. "]" ..
if not disallowed_mapgen_settings["seed"] then
"field[0.3,1.7;6,0.5;te_seed;" ..
fgettext("Seed") ..
";".. current_seed .. "]" ..
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 .. "]"
"dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
-- Warning if only devtest is installed
if #pkgmgr.games == 1 and pkgmgr.games[1].id == "devtest" then
retval = retval ..
"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
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_end[]" ..
"container_end[]" ..
-- Right side
@@ -340,33 +356,13 @@ 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
if fields["key_enter"] then
-- HACK: This timestamp prevents double-triggering when pressing Enter on an input box
-- and releasing it on a button[] or textlist[] due to instant formspec updates.
this.parent.dlg_create_world_closed_at = core.get_us_time()
end
local worldname = fields["te_world_name"]
local game, _ = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
local gameindex = core.get_textlist_index("games")
local message
if game == nil then
message = fgettext("No game selected")
end
if message == nil then
if gameindex ~= nil then
-- For unnamed worlds use the generated name 'world<number>',
-- where the number increments: it is set to 1 larger than the largest
-- generated name number found.
@@ -381,46 +377,36 @@ local function create_world_buttonhandler(this, fields)
worldname = "world" .. worldnum_max + 1
end
if menudata.worldlist:uid_exists_raw(worldname) then
core.settings:set("fixed_map_seed", fields["te_seed"])
local message
if not menudata.worldlist:uid_exists_raw(worldname) then
core.settings:set("mg_name",fields["dd_mapgen"])
message = core.create_world(worldname,gameindex)
else
message = fgettext("A world named \"$1\" already exists", worldname)
end
if message ~= nil then
gamedata.errormessage = message
else
core.settings:set("menu_last_game",pkgmgr.games[gameindex].id)
if this.data.update_worldlist_filter then
menudata.worldlist:set_filtercriteria(pkgmgr.games[gameindex].id)
mm_texture.update("singleplayer", pkgmgr.games[gameindex].id)
end
menudata.worldlist:refresh()
core.settings:set("mainmenu_last_selected_world",
menudata.worldlist:raw_index_by_uid(worldname))
end
else
gamedata.errormessage = fgettext("No game selected")
end
if message == nil then
this.data.seed = fields["te_seed"] or ""
this.data.mg = fields["dd_mapgen"]
-- actual names as used by engine
local settings = {
fixed_map_seed = this.data.seed,
mg_name = this.data.mg,
mg_flags = table_to_flags(this.data.flags.main),
mgv5_spflags = table_to_flags(this.data.flags.v5),
mgv6_spflags = table_to_flags(this.data.flags.v6),
mgv7_spflags = table_to_flags(this.data.flags.v7),
mgfractal_spflags = table_to_flags(this.data.flags.fractal),
mgcarpathian_spflags = table_to_flags(this.data.flags.carpathian),
mgvalleys_spflags = table_to_flags(this.data.flags.valleys),
mgflat_spflags = table_to_flags(this.data.flags.flat),
}
message = core.create_world(worldname, game.id, settings)
end
if message == nil then
core.settings:set("menu_last_game", game.id)
menudata.worldlist:set_filtercriteria(game.id)
menudata.worldlist:refresh()
core.settings:set("mainmenu_last_selected_world",
menudata.worldlist:raw_index_by_uid(worldname))
end
gamedata.errormessage = message
this:delete()
return true
end
this.data.worldname = fields["te_world_name"]
this.data.seed = fields["te_seed"] or ""
worldname = fields.te_world_name
if fields["games"] then
local gameindex = core.get_textlist_index("games")
@@ -431,11 +417,22 @@ local function create_world_buttonhandler(this, fields)
for k,v in pairs(fields) do
local split = string.split(k, "_", nil, 3)
if split and split[1] == "flag" then
local setting
if split[2] == "mg" then
setting = "mg_flags"
else
setting = split[2].."_spflags"
end
-- We replaced the underscore of flag names with a dash.
local flag = string.gsub(split[3], "-", "_")
local ftable = this.data.flags[split[2]]
assert(ftable)
ftable[flag] = v == "true"
local ftable = core.settings:get_flags(setting)
if v == "true" then
ftable[flag] = true
else
ftable[flag] = false
end
local flags = table_to_flags(ftable)
core.settings:set(setting, flags)
return true
end
end
@@ -449,16 +446,18 @@ local function create_world_buttonhandler(this, fields)
local entry = core.formspec_escape(fields["mgv6_biomes"])
for b=1, #mgv6_biomes do
if entry == mgv6_biomes[b][1] then
local ftable = this.data.flags.v6
local ftable = core.settings:get_flags("mgv6_spflags")
ftable.jungles = mgv6_biomes[b][2].jungles
ftable.snowbiomes = mgv6_biomes[b][2].snowbiomes
local flags = table_to_flags(ftable)
core.settings:set("mgv6_spflags", flags)
return true
end
end
end
if fields["dd_mapgen"] then
this.data.mg = fields["dd_mapgen"]
core.settings:set("mg_name", fields["dd_mapgen"])
return true
end
@@ -466,27 +465,13 @@ local function create_world_buttonhandler(this, fields)
end
function create_create_world_dlg()
function create_create_world_dlg(update_worldlistfilter)
worldname = ""
local retval = dialog_create("sp_create_world",
create_world_formspec,
create_world_buttonhandler,
nil)
retval.data = {
worldname = "",
-- settings the world is created with:
seed = core.settings:get("fixed_map_seed") or "",
mg = core.settings:get("mg_name"),
flags = {
main = core.settings:get_flags("mg_flags"),
v5 = core.settings:get_flags("mgv5_spflags"),
v6 = core.settings:get_flags("mgv6_spflags"),
v7 = core.settings:get_flags("mgv7_spflags"),
fractal = core.settings:get_flags("mgfractal_spflags"),
carpathian = core.settings:get_flags("mgcarpathian_spflags"),
valleys = core.settings:get_flags("mgvalleys_spflags"),
flat = core.settings:get_flags("mgflat_spflags"),
}
}
retval.update_worldlist_filter = update_worldlistfilter
return retval
end

View File

@@ -18,10 +18,15 @@
--------------------------------------------------------------------------------
local function delete_content_formspec(dialogdata)
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"))
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
end
--------------------------------------------------------------------------------

View File

@@ -17,10 +17,14 @@
local function delete_world_formspec(dialogdata)
return confirmation_formspec(
fgettext("Delete World \"$1\"?", dialogdata.delete_name),
'world_delete_confirm', fgettext("Delete"),
'world_delete_cancel', fgettext("Cancel"))
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
end
local function delete_world_buttonhandler(this, fields)

View File

@@ -1,123 +0,0 @@
--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

View File

@@ -31,10 +31,6 @@ end
-- returns error message, or nil
local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
-- strip carriage returns (CR, /r)
line = line:gsub("\r", "")
-- comment
local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
if comment then
@@ -344,14 +340,16 @@ local function parse_config_file(read_all, parse_mods)
if parse_mods then
-- Parse games
local games_category_initialized = false
for _, game in ipairs(pkgmgr.games) do
local index = 1
local game = pkgmgr.get_game(index)
while game do
local path = game.path .. DIR_DELIM .. FILENAME
local file = io.open(path, "r")
if file then
if not games_category_initialized then
fgettext_ne("Content: Games") -- not used, but needed for xgettext
fgettext_ne("Games") -- not used, but needed for xgettext
table.insert(settings, {
name = "Content: Games",
name = "Games",
level = 0,
type = "category",
})
@@ -368,57 +366,29 @@ local function parse_config_file(read_all, parse_mods)
file:close()
end
index = index + 1
game = pkgmgr.get_game(index)
end
-- Parse mods
local mods_category_initialized = false
local mods = {}
pkgmgr.get_mods(core.get_modpath(), "mods", mods)
get_mods(core.get_modpath(), 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("Content: Mods") -- not used, but needed for xgettext
fgettext_ne("Mods") -- not used, but needed for xgettext
table.insert(settings, {
name = "Content: Mods",
name = "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 = {}
pkgmgr.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,
@@ -523,40 +493,44 @@ end
local function get_current_np_group(setting)
local value = core.settings:get_np_group(setting.name)
local t = {}
if value == nil then
return setting.values
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)
end
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
}
return t
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
return setting.default
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
end
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 "")
return t
end
local checkboxes = {} -- handle checkboxes events
@@ -646,7 +620,7 @@ local function create_change_setting_formspec(dialogdata)
-- Third row
add_field(0.3, "te_octaves", fgettext("Octaves"), t[7])
add_field(3.6, "te_persist", fgettext("Persistence"), t[8])
add_field(3.6, "te_persist", fgettext("Persistance"), t[8])
add_field(6.9, "te_lacun", fgettext("Lacunarity"), t[9])
height = height + 1.1
@@ -689,7 +663,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.+-eE]+") do -- All numeric characters
for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
table.insert(v3f, line)
end
@@ -982,7 +956,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("show_technical_names") and entry.readable_name then
if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
name = fgettext_ne(entry.readable_name)
else
name = entry.name
@@ -1023,7 +997,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("show_technical_names")) .. "]"
.. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
return formspec
end
@@ -1106,7 +1080,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
end
if fields["cb_tech_settings"] then
core.settings:set("show_technical_names", fields["cb_tech_settings"])
core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
core.settings:write()
core.update_formspec(this:get_formspec())
return true

View File

@@ -1,171 +0,0 @@
--[[
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;", title, "]",
"textarea[0.4,1.6;12,3.4;;;", 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

View File

@@ -18,7 +18,7 @@ local minetest_example_header = [[
# to the program, eg. "minetest.exe --config ../minetest.conf.example".
# Further documentation:
# https://wiki.minetest.net/
# http://wiki.minetest.net/
]]
@@ -31,7 +31,7 @@ local group_format_template = [[
# octaves = %s,
# persistence = %s,
# lacunarity = %s,
# flags =%s
# flags = %s
# }
]]
@@ -55,17 +55,9 @@ local function create_minetest_conf_example()
end
if entry.comment ~= "" then
for _, comment_line in ipairs(entry.comment:split("\n", true)) do
if comment_line == "" then
insert(result, "#\n")
else
insert(result, "# " .. comment_line .. "\n")
end
insert(result, "# " .. comment_line .. "\n")
end
end
if entry.type == "key" then
local line = "See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h"
insert(result, "# " .. line .. "\n")
end
insert(result, "# type: " .. entry.type)
if entry.min then
insert(result, " min: " .. entry.min)
@@ -81,14 +73,10 @@ 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],
flags))
entry.values[10]))
else
local append
if entry.default ~= "" then
@@ -103,7 +91,7 @@ end
local translation_file_header = [[
// This file is automatically generated
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files
// It conatins 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() {]]
@@ -113,9 +101,6 @@ local function create_translation_file()
for _, entry in ipairs(settings) do
if entry.type == "category" then
insert(result, sprintf("\tgettext(%q);", entry.name))
elseif entry.type == "key" then --luacheck: ignore
-- Neither names nor descriptions of keys are used since we have a
-- dedicated menu for them.
else
if entry.readable_name then
insert(result, sprintf("\tgettext(%q);", entry.readable_name))
@@ -141,3 +126,4 @@ 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()

View File

@@ -17,11 +17,9 @@
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()
@@ -37,7 +35,7 @@ dofile(menupath .. DIR_DELIM .. "async_event.lua")
dofile(menupath .. DIR_DELIM .. "common.lua")
dofile(menupath .. DIR_DELIM .. "pkgmgr.lua")
dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua")
dofile(menupath .. DIR_DELIM .. "game_theme.lua")
dofile(menupath .. DIR_DELIM .. "textures.lua")
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
@@ -45,15 +43,13 @@ 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 = {}
tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua")
tabs.content = dofile(menupath .. DIR_DELIM .. "tab_content.lua")
tabs.about = dofile(menupath .. DIR_DELIM .. "tab_about.lua")
tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua")
tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua")
tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
@@ -86,19 +82,15 @@ local function init_globals()
menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic)
menudata.worldlist:set_sortmode("alphabetic")
local gameid = core.settings:get("menu_last_game")
local game = gameid and pkgmgr.find_by_gameid(gameid)
if not game then
gameid = core.settings:get("default_game") or "minetest"
game = pkgmgr.find_by_gameid(gameid)
core.settings:set("menu_last_game", gameid)
if not core.settings:get("menu_last_game") then
local default_game = core.settings:get("default_game") or "minetest"
core.settings:set("menu_last_game", default_game)
end
mm_game_theme.init()
mm_texture.init()
-- 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)
@@ -106,7 +98,7 @@ local function init_globals()
tv_main:add(tabs.content)
tv_main:add(tabs.settings)
tv_main:add(tabs.about)
tv_main:add(tabs.credits)
tv_main:set_global_event_handler(main_event_handler)
tv_main:set_fixed_size(false)
@@ -118,14 +110,19 @@ local function init_globals()
-- In case the folder of the last selected game has been deleted,
-- display "Minetest" as a header
if tv_main.current_tab == "local" and not game then
mm_game_theme.reset()
if tv_main.current_tab == "local" then
local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
if game == nil then
mm_texture.reset()
end
end
ui.set_default("maintab")
check_new_version()
tv_main:show()
ui.update()
core.sound_play("main_menu", true)
end
init_globals()

View File

@@ -78,63 +78,54 @@ 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 = enabled,
enabled = path == current_texture_path,
}
end
end
end
--modmanager implementation
pkgmgr = {}
--- Scans a directory recursively for mods and adds them to `listing`
-- @param path Absolute directory path to scan recursively
-- @param virtual_path Prettified unique path (e.g. "mods", "mods/mt_modpack")
-- @param listing Input. Flat array to insert located mods and modpacks
-- @param modpack Currently processing modpack or nil/"" if none (recursion)
function pkgmgr.get_mods(path, virtual_path, listing, modpack)
function get_mods(path,retval,modpack)
local mods = core.get_dir_list(path, true)
for _, name in ipairs(mods) do
if name:sub(1, 1) ~= "." then
local mod_path = path .. DIR_DELIM .. name
local mod_virtual_path = virtual_path .. "/" .. name
local prefix = path .. DIR_DELIM .. name
local toadd = {
dir_name = name,
parent_dir = path,
}
listing[#listing + 1] = toadd
retval[#retval + 1] = toadd
-- Get config file
local mod_conf
local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf")
local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf")
if modpack_conf then
toadd.is_modpack = true
modpack_conf:close()
mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table()
mod_conf = Settings(prefix .. 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(mod_path .. DIR_DELIM .. "mod.conf"):to_table()
mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table()
if mod_conf.name then
name = mod_conf.name
toadd.is_name_explicit = true
@@ -143,16 +134,14 @@ function pkgmgr.get_mods(path, virtual_path, listing, 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 = mod_path
toadd.virtual_path = mod_virtual_path
toadd.path = prefix
toadd.type = "mod"
-- Check modpack.txt
-- Note: modpack.conf is already checked above
local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
if modpackfile then
modpackfile:close()
toadd.is_modpack = true
@@ -164,19 +153,15 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack)
elseif toadd.is_modpack then
toadd.type = "modpack"
toadd.is_modpack = true
pkgmgr.get_mods(mod_path, mod_virtual_path, listing, name)
get_mods(prefix, retval, name)
end
end
end
if not modpack then
-- Sort all when the recursion is done
table.sort(listing, function(a, b)
return a.virtual_path:lower() < b.virtual_path:lower()
end)
end
end
--modmanager implementation
pkgmgr = {}
function pkgmgr.get_texture_packs()
local txtpath = core.get_texturepath()
local txtpath_system = core.get_texturepath_share()
@@ -189,13 +174,28 @@ function pkgmgr.get_texture_packs()
end
table.sort(retval, function(a, b)
return a.title:lower() < b.title:lower()
return a.name > b.name
end)
return retval
end
--------------------------------------------------------------------------------
function pkgmgr.extract(modfile)
if modfile.type == "zip" then
local tempfolder = os.tempfolder()
if tempfolder ~= nil and
tempfolder ~= "" then
core.create_dir(tempfolder)
if core.extract_zip(modfile.name,tempfolder) then
return tempfolder
end
end
end
return nil
end
function pkgmgr.get_folder_type(path)
local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
if testfile ~= nil then
@@ -255,12 +255,101 @@ function pkgmgr.get_base_folder(temppath)
end
--------------------------------------------------------------------------------
function pkgmgr.is_valid_modname(modpath)
return modpath:match("[^a-z0-9_]") == nil
function pkgmgr.isValidModname(modpath)
if modpath:find("-") ~= nil then
return false
end
return true
end
--------------------------------------------------------------------------------
function pkgmgr.render_packagelist(render_list, use_technical_names, with_error)
function pkgmgr.parse_register_line(line)
local pos1 = line:find("\"")
local pos2 = nil
if pos1 ~= nil then
pos2 = line:find("\"",pos1+1)
end
if pos1 ~= nil and pos2 ~= nil then
local item = line:sub(pos1+1,pos2-1)
if item ~= nil and
item ~= "" then
local pos3 = item:find(":")
if pos3 ~= nil then
local retval = item:sub(1,pos3-1)
if retval ~= nil and
retval ~= "" then
return retval
end
end
end
end
return nil
end
--------------------------------------------------------------------------------
function pkgmgr.parse_dofile_line(modpath,line)
local pos1 = line:find("\"")
local pos2 = nil
if pos1 ~= nil then
pos2 = line:find("\"",pos1+1)
end
if pos1 ~= nil and pos2 ~= nil then
local filename = line:sub(pos1+1,pos2-1)
if filename ~= nil and
filename ~= "" and
filename:find(".lua") then
return pkgmgr.identify_modname(modpath,filename)
end
end
return nil
end
--------------------------------------------------------------------------------
function pkgmgr.identify_modname(modpath,filename)
local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
if testfile ~= nil then
local line = testfile:read()
while line~= nil do
local modname = nil
if line:find("minetest.register_tool") then
modname = pkgmgr.parse_register_line(line)
end
if line:find("minetest.register_craftitem") then
modname = pkgmgr.parse_register_line(line)
end
if line:find("minetest.register_node") then
modname = pkgmgr.parse_register_line(line)
end
if line:find("dofile") then
modname = pkgmgr.parse_dofile_line(modpath,line)
end
if modname ~= nil then
testfile:close()
return modname
end
line = testfile:read()
end
testfile:close()
end
return nil
end
--------------------------------------------------------------------------------
function pkgmgr.render_packagelist(render_list)
if not render_list then
if not pkgmgr.global_mods then
pkgmgr.refresh_globals()
@@ -272,75 +361,31 @@ function pkgmgr.render_packagelist(render_list, use_technical_names, with_error)
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 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
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
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
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
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
end
return table.concat(retval, ",")
@@ -367,14 +412,6 @@ 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
@@ -383,16 +420,13 @@ 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
@@ -417,7 +451,7 @@ function pkgmgr.enable_mod(this, toset)
local enabled_mods = {}
toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
if next(enabled_mods) == nil then
if not toset then
-- Mod(s) were disabled, so no dependencies need to be enabled
table.sort(toggled_mods)
core.log("info", "Following mods were disabled: " ..
@@ -427,16 +461,11 @@ function pkgmgr.enable_mod(this, toset)
-- Enable mods' depends after activation
-- Make a list of mod ids indexed by their names. Among mods with the
-- same name, enabled mods take precedence, after which game mods take
-- precedence, being last in the mod list.
-- Make a list of mod ids indexed by their names
local mod_ids = {}
for id, mod2 in pairs(list) do
if mod2.type == "mod" and not mod2.is_modpack then
local prev_id = mod_ids[mod2.name]
if not prev_id or not list[prev_id].enabled then
mod_ids[mod2.name] = id
end
mod_ids[mod2.name] = id
end
end
@@ -453,7 +482,6 @@ 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]
@@ -465,15 +493,15 @@ function pkgmgr.enable_mod(this, toset)
if not mod_to_enable then
core.log("warning", "Mod dependency \"" .. name ..
"\" not found!")
elseif not mod_to_enable.is_game_content then
if not mod_to_enable.enabled then
else
if mod_to_enable.enabled == false then
mod_to_enable.enabled = true
toggled_mods[#toggled_mods+1] = mod_to_enable.name
end
-- Push the dependencies of the dependency onto the stack
local depends = pkgmgr.get_dependencies(mod_to_enable.path)
for i = 1, #depends do
if not enabled_mods[depends[i]] then
if not enabled_mods[name] then
sp = sp+1
to_enable[sp] = depends[i]
end
@@ -520,68 +548,102 @@ function pkgmgr.get_worldconfig(worldpath)
end
--------------------------------------------------------------------------------
function pkgmgr.install_dir(expected_type, path, basename, targetpath)
assert(type(expected_type) == "string")
assert(type(path) == "string")
assert(basename == nil or type(basename) == "string")
assert(targetpath == nil or type(targetpath) == "string")
function pkgmgr.install_dir(type, path, basename, targetpath)
local basefolder = pkgmgr.get_base_folder(path)
if expected_type == "txp" then
assert(basename)
-- There's no good way to detect a texture pack, so let's just assume
-- it's correct for now.
-- There's no good way to detect a texture pack, so let's just assume
-- it's correct for now.
if type == "txp" then
if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
return nil, fgettext("Unable to install a $1 as a texture pack", basefolder.type)
end
local from = basefolder and basefolder.path or path
if not targetpath then
if targetpath then
core.delete_dir(targetpath)
core.create_dir(targetpath)
else
targetpath = core.get_texturepath() .. DIR_DELIM .. basename
end
core.delete_dir(targetpath)
if not core.copy_dir(from, targetpath, false) then
if not core.copy_dir(from, targetpath) then
return nil,
fgettext("Failed to install $1 to $2", basename, targetpath)
end
return targetpath, nil
elseif not basefolder then
return nil, fgettext("Unable to find a valid mod, modpack, or game")
return nil, fgettext("Unable to find a valid mod or modpack")
end
-- Check type
if basefolder.type ~= expected_type and (basefolder.type ~= "modpack" or expected_type ~= "mod") then
return nil, fgettext("Unable to install a $1 as a $2", basefolder.type, expected_type)
end
-- Set targetpath if not predetermined
if not targetpath then
local content_path
if basefolder.type == "modpack" or basefolder.type == "mod" then
if not basename then
basename = get_last_folder(cleanup_path(basefolder.path))
end
content_path = core.get_modpath()
elseif basefolder.type == "game" then
content_path = core.get_gamepath()
else
error("Unknown content type")
--
-- Get destination
--
if basefolder.type == "modpack" then
if type ~= "mod" then
return nil, fgettext("Unable to install a modpack as a $1", type)
end
if basename and (basefolder.type ~= "mod" or pkgmgr.is_valid_modname(basename)) then
targetpath = content_path .. DIR_DELIM .. basename
-- Get destination name for modpack
if targetpath then
core.delete_dir(targetpath)
core.create_dir(targetpath)
else
return nil,
fgettext("Install: Unable to find suitable folder name for $1", path)
local clean_path = nil
if basename ~= nil then
clean_path = basename
end
if not clean_path then
clean_path = get_last_folder(cleanup_path(basefolder.path))
end
if clean_path then
targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
else
return nil,
fgettext("Install Mod: Unable to find suitable folder name for modpack $1",
path)
end
end
elseif basefolder.type == "mod" then
if type ~= "mod" then
return nil, fgettext("Unable to install a mod as a $1", type)
end
if targetpath then
core.delete_dir(targetpath)
core.create_dir(targetpath)
else
local targetfolder = basename
if targetfolder == nil then
targetfolder = pkgmgr.identify_modname(basefolder.path, "init.lua")
end
-- If heuristic failed try to use current foldername
if targetfolder == nil then
targetfolder = get_last_folder(basefolder.path)
end
if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
else
return nil, fgettext("Install Mod: Unable to find real mod name for: $1", path)
end
end
elseif basefolder.type == "game" then
if type ~= "game" then
return nil, fgettext("Unable to install a game as a $1", type)
end
if targetpath then
core.delete_dir(targetpath)
core.create_dir(targetpath)
else
targetpath = core.get_gamepath() .. DIR_DELIM .. basename
end
end
-- Copy it
core.delete_dir(targetpath)
if not core.copy_dir(basefolder.path, targetpath, false) then
if not core.copy_dir(basefolder.path, targetpath) then
return nil,
fgettext("Failed to install $1 to $2", basename, targetpath)
end
@@ -595,6 +657,23 @@ function pkgmgr.install_dir(expected_type, path, basename, targetpath)
return targetpath, nil
end
--------------------------------------------------------------------------------
function pkgmgr.install(type, modfilename, basename, dest)
local archive_info = pkgmgr.identify_filetype(modfilename)
local path = pkgmgr.extract(archive_info)
if path == nil then
return nil,
fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
fgettext("Install: Unsupported file type \"$1\" or broken archive",
archive_info.type)
end
local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
core.delete_dir(path)
return targetpath, msg
end
--------------------------------------------------------------------------------
function pkgmgr.preparemodlist(data)
local retval = {}
@@ -603,15 +682,16 @@ function pkgmgr.preparemodlist(data)
local game_mods = {}
--read global mods
local modpaths = core.get_modpaths()
for key, modpath in pairs(modpaths) do
pkgmgr.get_mods(modpath, key, global_mods)
local modpath = core.get_modpath()
if modpath ~= nil and
modpath ~= "" then
get_mods(modpath,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
@@ -624,7 +704,7 @@ function pkgmgr.preparemodlist(data)
retval[#retval + 1] = {
type = "game",
is_game_content = true,
name = fgettext("$1 mods", gamespec.title),
name = fgettext("$1 mods", gamespec.name),
path = gamespec.path
}
end
@@ -645,37 +725,22 @@ 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 mod_found = false
local fallback_found = false
local fallback_mod = nil
for i=1, #retval do
local element = nil
for i=1,#retval,1 do
if retval[i].name == key and
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
not retval[i].is_modpack then
element = retval[i]
break
end
end
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
if element ~= nil then
element.enabled = value ~= "false" and value ~= "nil" and value
else
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
end
end
end
@@ -712,6 +777,35 @@ function pkgmgr.comparemod(elem1,elem2)
return true
end
--------------------------------------------------------------------------------
function pkgmgr.mod_exists(basename)
if pkgmgr.global_mods == nil then
pkgmgr.refresh_globals()
end
if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then
return true
end
return false
end
--------------------------------------------------------------------------------
function pkgmgr.get_global_mod(idx)
if pkgmgr.global_mods == nil then
return nil
end
if idx == nil or idx < 1 or
idx > pkgmgr.global_mods:size() then
return nil
end
return pkgmgr.global_mods:get_list()[idx]
end
--------------------------------------------------------------------------------
function pkgmgr.refresh_globals()
local function is_equal(element,uid) --uid match
@@ -725,11 +819,50 @@ function pkgmgr.refresh_globals()
pkgmgr.global_mods:set_sortmode("alphabetic")
end
--------------------------------------------------------------------------------
function pkgmgr.identify_filetype(name)
if name:sub(-3):lower() == "zip" then
return {
name = name,
type = "zip"
}
end
if name:sub(-6):lower() == "tar.gz" or
name:sub(-3):lower() == "tgz"then
return {
name = name,
type = "tgz"
}
end
if name:sub(-6):lower() == "tar.bz2" then
return {
name = name,
type = "tbz"
}
end
if name:sub(-2):lower() == "7z" then
return {
name = name,
type = "7z"
}
end
return {
name = name,
type = "ukn"
}
end
--------------------------------------------------------------------------------
function pkgmgr.find_by_gameid(gameid)
for i, game in ipairs(pkgmgr.games) do
if game.id == gameid then
return game, i
for i=1,#pkgmgr.games,1 do
if pkgmgr.games[i].id == gameid then
return pkgmgr.games[i], i
end
end
return nil, nil
@@ -740,16 +873,49 @@ function pkgmgr.get_game_mods(gamespec, retval)
if gamespec ~= nil and
gamespec.gamemods_path ~= nil and
gamespec.gamemods_path ~= "" then
pkgmgr.get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval)
get_mods(gamespec.gamemods_path, retval)
end
end
--------------------------------------------------------------------------------
function pkgmgr.get_game_modlist(gamespec)
local retval = ""
local game_mods = {}
pkgmgr.get_game_mods(gamespec, game_mods)
for i=1,#game_mods,1 do
if retval ~= "" then
retval = retval..","
end
retval = retval .. game_mods[i].name
end
return retval
end
--------------------------------------------------------------------------------
function pkgmgr.get_game(index)
if index > 0 and index <= #pkgmgr.games then
return pkgmgr.games[index]
end
return nil
end
--------------------------------------------------------------------------------
function pkgmgr.update_gamelist()
pkgmgr.games = core.get_games()
table.sort(pkgmgr.games, function(a, b)
return a.title:lower() < b.title:lower()
end)
end
--------------------------------------------------------------------------------
function pkgmgr.gamelist()
local retval = ""
if #pkgmgr.games > 0 then
retval = retval .. core.formspec_escape(pkgmgr.games[1].name)
for i=2,#pkgmgr.games,1 do
retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].name)
end
end
return retval
end
--------------------------------------------------------------------------------

View File

@@ -15,101 +15,28 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
serverlistmgr = {
-- continent code we detected for ourselves
my_continent = core.get_once("continent"),
-- list of locally favorites servers
favorites = nil,
-- list of servers fetched from public list
servers = nil,
}
serverlistmgr = {}
--------------------------------------------------------------------------------
-- Efficient data structure for normalizing arbitrary scores attached to objects
-- e.g. {{"a", 3.14}, {"b", 3.14}, {"c", 20}, {"d", 0}}
-- -> {["d"] = 0, ["a"] = 0.5, ["b"] = 0.5, ["c"] = 1}
local Normalizer = {}
function Normalizer:new()
local t = {
map = {}
}
setmetatable(t, self)
self.__index = self
return t
end
function Normalizer:push(obj, score)
if not self.map[score] then
self.map[score] = {}
end
local t = self.map[score]
t[#t + 1] = obj
end
function Normalizer:calc()
local list = {}
for k, _ in pairs(self.map) do
list[#list + 1] = k
end
table.sort(list)
local ret = {}
for i, k in ipairs(list) do
local score = #list == 1 and 1 or ( (i - 1) / (#list - 1) )
for _, obj in ipairs(self.map[k]) do
ret[obj] = score
end
end
return ret
end
--------------------------------------------------------------------------------
-- how much the pre-sorted server list contributes to the final ranking
local WEIGHT_SORT = 2
-- how much the estimated latency contributes to the final ranking
local WEIGHT_LATENCY = 1
local function order_server_list(list)
-- calculate the scores
local s1 = Normalizer:new()
local s2 = Normalizer:new()
for i, fav in ipairs(list) do
-- first: the original position
s1:push(fav, #list - i)
-- second: estimated latency
local ping = (fav.ping or 0) * 1000
if ping < 400 then
-- If ping is under 400ms replace it with our own estimate,
-- we assume the server has latency issues anyway otherwise
ping = estimate_continent_latency(serverlistmgr.my_continent, fav) or 0
end
s2:push(fav, -ping)
end
s1 = s1:calc()
s2 = s2:calc()
-- make a shallow copy and pre-calculate ordering
local res, order = {}, {}
local res = {}
--orders the favorite list after support
for i = 1, #list do
local fav = list[i]
res[i] = fav
local n = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
order[fav] = n
if is_server_protocol_compat(fav.proto_min, fav.proto_max) then
res[#res + 1] = fav
end
end
for i = 1, #list do
local fav = list[i]
if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then
res[#res + 1] = fav
end
end
-- now sort the list
table.sort(res, function(fav1, fav2)
return order[fav1] > order[fav2]
end)
return res
end
local public_downloading = false
local geoip_downloading = false
--------------------------------------------------------------------------------
function serverlistmgr.sync()
@@ -129,39 +56,6 @@ function serverlistmgr.sync()
return
end
-- only fetched once per MT instance
if not serverlistmgr.my_continent and not geoip_downloading then
geoip_downloading = true
core.handle_async(
function(param)
local http = core.get_http_api()
local url = core.settings:get("serverlist_url") .. "/geoip"
local response = http.fetch_sync({ url = url })
if not response.succeeded then
return
end
local retval = core.parse_json(response.data)
return retval and type(retval.continent) == "string" and retval.continent
end,
nil,
function(result)
geoip_downloading = false
if not result then
return
end
serverlistmgr.my_continent = result
core.set_once("continent", result)
-- reorder list if we already have it
if serverlistmgr.servers then
serverlistmgr.servers = order_server_list(serverlistmgr.servers)
core.event_handler("Refresh")
end
end
)
end
if public_downloading then
return
end
@@ -185,7 +79,7 @@ function serverlistmgr.sync()
end,
nil,
function(result)
public_downloading = false
public_downloading = nil
local favs = order_server_list(result)
if favs[1] then
serverlistmgr.servers = favs

View File

@@ -1,205 +0,0 @@
--Minetest
--Copyright (C) 2013 sapier
--
--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.
-- https://github.com/orgs/minetest/teams/engine/members
local core_developers = {
"Perttu Ahola (celeron55) <celeron55@gmail.com> [Project founder]",
"sfan5 <sfan5@live.de>",
"ShadowNinja <shadowninja@minetest.net>",
"Nathanaëlle Courant (Nore/Ekdohibs) <nore@mesecons.net>",
"Loic Blot (nerzhul/nrz) <loic.blot@unix-experience.fr>",
"Andrew Ward (rubenwardy) <rw@rubenwardy.com>",
"Krock/SmallJoker <mk939@ymail.com>",
"Lars Hofhansl <larsh@apache.org>",
"v-rob <robinsonvincent89@gmail.com>",
"Hugues Ross <hugues.ross@gmail.com>",
"Dmitry Kostenko (x2048) <codeforsmile@gmail.com>",
"Desour",
}
-- currently only https://github.com/orgs/minetest/teams/triagers/members
local core_team = {
"Zughy [Issue triager]",
"wsor [Issue triager]",
"Hugo Locurcio (Calinou) [Issue triager]",
}
-- For updating active/previous contributors, see the script in ./util/gather_git_credits.py
local active_contributors = {
"Wuzzy [Features, translations, devtest]",
"Lars Müller [Bugfixes and entity features]",
"paradust7 [Bugfixes]",
"ROllerozxa [Bugfixes, Android]",
"srifqi [Android, translations]",
"Lexi Hale [Particlespawner animation]",
"savilli [Bugfixes]",
"fluxionary [Bugfixes]",
"Gregor Parzefall [Bugfixes]",
"Abdou-31 [Documentation]",
"pecksin [Bouncy physics]",
"Daroc Alden [Fixes]",
}
local previous_core_developers = {
"BlockMen",
"Maciej Kasatkin (RealBadAngel) [RIP]",
"Lisa Milne (darkrose) <lisa@ltmnet.com>",
"proller",
"Ilya Zhuravlev (xyz) <xyz@minetest.net>",
"PilzAdam <pilzadam@minetest.net>",
"est31 <MTest31@outlook.com>",
"kahrl <kahrl@gmx.net>",
"Ryan Kwolek (kwolekr) <kwolekr@minetest.net>",
"sapier",
"Zeno",
"Auke Kok (sofar) <sofar@foo-projects.org>",
"Aaron Suen <warr1024@gmail.com>",
"paramat",
"Pierre-Yves Rollo <dev@pyrollo.com>",
"hecks",
"Jude Melton-Houghton (TurkeyMcMac) [RIP]",
}
local previous_contributors = {
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest logo]",
"red-001 <red-001@outlook.ie>",
"Giuseppe Bilotta",
"numzero",
"HybridDog",
"ClobberXD",
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>",
"Jean-Patrick Guerrero (kilbith)",
"MoNTE48",
"Constantin Wenger (SpeedProg)",
"Ciaran Gultnieks (CiaranG)",
"Paul Ouellette (pauloue)",
"stujones11",
"Rogier <rogier777@gmail.com>",
"Gregory Currie (gregorycu)",
"JacobF",
"Jeija <jeija@mesecons.net>",
}
local function prepare_credits(dest, source)
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
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 {
name = "about",
caption = fgettext("About"),
cbf_formspec = function(tabview, name, tabdata)
local logofile = defaulttexturedir .. "logo.png"
local version = core.get_version()
local credit_list = {}
table.insert_all(credit_list, {
core.colorize("#000", "Dedication of the current release"),
"The 5.7.0 release is dedicated to the memory of",
"Minetest developer Jude Melton-Houghton (TurkeyMcMac)",
"who died on February 1, 2023.",
"Our thoughts are with his family and friends.",
"",
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.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 .. "style[label_button2;border=false]" ..
"button[0.1,6;5.3,1;label_button2;" ..
fgettext("Active renderer:") .. "\n" ..
core.formspec_escape(core.get_active_renderer()) .. "]"
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.5,5.1;4.5,0.8;userdata;" .. fgettext("Open User Data Directory") .. "]"
end
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
end,
}

View File

@@ -51,14 +51,12 @@ 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, use_technical_names) ..
pkgmgr.render_packagelist(packages) ..
";" .. tabdata.selected_pkg .. "]" ..
"button[0,4.85;5.25,0.5;btn_contentdb;".. fgettext("Browse online content") .. "]"
@@ -89,17 +87,9 @@ 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(title_and_name) .. "]" ..
"label[8.25,0.6;" .. core.formspec_escape(selected_pkg.name) .. "]" ..
"box[5.5,2.2;6.15,2.35;#000]"
if selected_pkg.type == "mod" then
@@ -155,29 +145,11 @@ local function get_formspec(tabview, name, tabdata)
return retval
end
--------------------------------------------------------------------------------
local function handle_doubleclick(pkg)
if pkg.type == "txp" then
if core.settings:get("texture_path") == pkg.path then
core.settings:set("texture_path", "")
else
core.settings:set("texture_path", pkg.path)
end
packages = nil
mm_game_theme.init()
mm_game_theme.reset()
end
end
--------------------------------------------------------------------------------
local function handle_buttons(tabview, fields, tabname, tabdata)
if fields["pkglist"] ~= nil then
local event = core.explode_table_event(fields["pkglist"])
tabdata.selected_pkg = event.row
if event.type == "DCL" then
handle_doubleclick(packages:get_list()[tabdata.selected_pkg])
end
return true
end
@@ -210,17 +182,17 @@ local function handle_buttons(tabview, fields, tabname, tabdata)
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
core.settings:set("texture_path", txp_path)
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
mm_game_theme.init()
mm_game_theme.reset()
if fields.btn_mod_mgr_disable_txp then
core.settings:set("texture_path", "")
packages = nil
return true
end

View File

@@ -0,0 +1,140 @@
--Minetest
--Copyright (C) 2013 sapier
--
--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 core_developers = {
"Perttu Ahola (celeron55) <celeron55@gmail.com>",
"sfan5 <sfan5@live.de>",
"Nathanaël 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>",
}
-- For updating active/previous contributors, see the script in ./util/gather_git_credits.py
local active_contributors = {
"Wuzzy [devtest game, visual corrections]",
"Zughy [Visual improvements, various fixes]",
"Maksim (MoNTE48) [Android]",
"numzero [Graphics and rendering]",
"appgurueu [Various internal fixes]",
"Desour [Formspec and vector API changes]",
"HybridDog [Rendering fixes and documentation]",
"Hugues Ross [Graphics-related improvements]",
"ANAND (ClobberXD) [Mouse buttons rebinding]",
"luk3yx [Fixes]",
"hecks [Audiovisuals, Lua API]",
"LoneWolfHT [Object crosshair, documentation fixes]",
"Lejo [Server-related improvements]",
"EvidenceB [Compass HUD element]",
"Paul Ouellette (pauloue) [Lua API, documentation]",
"TheTermos [Collision detection, physics]",
"David CARLIER [Unix & Haiku build fixes]",
"dcbrwn [Object shading]",
"Elias Fleckenstein [API features/fixes]",
"Jean-Patrick Guerrero (kilbith) [model element, visual fixes]",
"k.h.lai [Memory leak fixes, documentation]",
}
local previous_core_developers = {
"BlockMen",
"Maciej Kasatkin (RealBadAngel) [RIP]",
"Lisa Milne (darkrose) <lisa@ltmnet.com>",
"proller",
"Ilya Zhuravlev (xyz) <xyz@minetest.net>",
"PilzAdam <pilzadam@minetest.net>",
"est31 <MTest31@outlook.com>",
"kahrl <kahrl@gmx.net>",
"Ryan Kwolek (kwolekr) <kwolekr@minetest.net>",
"sapier",
"Zeno",
"ShadowNinja <shadowninja@minetest.net>",
"Auke Kok (sofar) <sofar@foo-projects.org>",
}
local previous_contributors = {
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest Logo]",
"red-001 <red-001@outlook.ie>",
"Giuseppe Bilotta",
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>",
"Constantin Wenger (SpeedProg)",
"Ciaran Gultnieks (CiaranG)",
"stujones11 [Android UX improvements]",
"Rogier <rogier777@gmail.com> [Fixes]",
"Gregory Currie (gregorycu) [optimisation]",
"srifqi [Fixes]",
"JacobF",
"Jeija <jeija@mesecons.net> [HTTP, particles]",
}
local function buildCreditList(source)
local ret = {}
for i = 1, #source do
ret[i] = core.formspec_escape(source[i])
end
return table.concat(ret, ",,")
end
return {
name = "credits",
caption = fgettext("Credits"),
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) .. "]" ..
"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]"
if PLATFORM ~= "Android" then
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.75;3.5,1;userdata;" .. fgettext("Open User Data Directory") .. "]"
end
return fs
end,
cbf_button_handler = function(this, fields, name, tabdata)
if fields.homepage then
core.open_url("https://www.minetest.net")
end
if fields.userdata then
core.open_dir(core.get_user_path())
end
end,
}

View File

@@ -16,176 +16,123 @@
--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,
["creative_mode"]=true,
["enable_server"]=true,
}
-- 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)
if enable_gamebar then
function current_game()
local last_game_id = core.settings:get("menu_last_game")
local game = pkgmgr.find_by_gameid(last_game_id)
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()
return game
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
function singleplayer_refresh_gamebar()
local old_bar = ui.find_by_name("game_button_bar")
if old_bar ~= nil then
old_bar:delete()
end
for _, game in ipairs(pkgmgr.games) do
if fields["game_btnbar_" .. game.id] then
apply_game(game)
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
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.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
for key,value in pairs(fields) do
for j=1,#pkgmgr.games,1 do
if ("game_btnbar_" .. pkgmgr.games[j].id == key) then
mm_texture.update("singleplayer", pkgmgr.games[j])
core.set_topleft_text(pkgmgr.games[j].name)
core.settings:set("menu_last_game",pkgmgr.games[j].id)
menudata.worldlist:set_filtercriteria(pkgmgr.games[j].id)
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)
return true
end
end
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 btnbar = buttonbar_create("game_button_bar",
game_buttonbar_button_handler,
{x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
local function get_disabled_settings(game)
if not game then
return {}
end
for i=1,#pkgmgr.games,1 do
local btn_name = "game_btnbar_" .. pkgmgr.games[i].id
local gameconfig = Settings(game.path .. "/game.conf")
local disabled_settings = {}
if gameconfig then
local disabled_settings_str = (gameconfig:get("disabled_settings") or ""):split()
for _, value in pairs(disabled_settings_str) do
local state = false
value = value:trim()
if string.sub(value, 1, 1) == "!" then
state = true
value = string.sub(value, 2)
end
if valid_disabled_settings[value] then
disabled_settings[value] = state
local image = nil
local text = nil
local tooltip = core.formspec_escape(pkgmgr.games[i].name)
if pkgmgr.games[i].menuicon_path ~= nil and
pkgmgr.games[i].menuicon_path ~= "" then
image = core.formspec_escape(pkgmgr.games[i].menuicon_path)
else
core.log("error", "Invalid disabled setting in game.conf: "..tostring(value))
local part1 = pkgmgr.games[i].id:sub(1,5)
local part2 = pkgmgr.games[i].id:sub(6,10)
local part3 = pkgmgr.games[i].id:sub(11)
text = part1 .. "\n" .. part2
if part3 ~= nil and
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
else
function current_game()
return nil
end
return disabled_settings
end
local function get_formspec(tabview, name, tabdata)
local retval = ""
local index = filterlist.get_current_index(menudata.worldlist,
tonumber(core.settings:get("mainmenu_last_selected_world")))
local list = menudata.worldlist:get_list()
local world = list and index and list[index]
local game
if world then
game = pkgmgr.find_by_gameid(world.gameid)
else
game = current_game()
end
local disabled_settings = get_disabled_settings(game)
local creative, damage, host = "", "", ""
-- Y offsets for game settings checkboxes
local y = -0.2
local yo = 0.45
if disabled_settings["creative_mode"] == nil then
creative = "checkbox[0,"..y..";cb_creative_mode;".. fgettext("Creative Mode") .. ";" ..
dump(core.settings:get_bool("creative_mode")) .. "]"
y = y + yo
end
if disabled_settings["enable_damage"] == nil then
damage = "checkbox[0,"..y..";cb_enable_damage;".. fgettext("Enable Damage") .. ";" ..
dump(core.settings:get_bool("enable_damage")) .. "]"
y = y + yo
end
if disabled_settings["enable_server"] == nil then
host = "checkbox[0,"..y..";cb_server;".. fgettext("Host Server") ..";" ..
dump(core.settings:get_bool("enable_server")) .. "]"
y = y + yo
end
tonumber(core.settings:get("mainmenu_last_selected_world"))
)
retval = retval ..
"button[3.9,3.8;2.8,1;world_delete;".. fgettext("Delete") .. "]" ..
"button[6.55,3.8;2.8,1;world_configure;".. fgettext("Select Mods") .. "]" ..
"button[9.2,3.8;2.8,1;world_create;".. fgettext("New") .. "]" ..
"label[3.9,-0.05;".. fgettext("Select World:") .. "]"..
creative ..
damage ..
host ..
"checkbox[0,-0.20;cb_creative_mode;".. fgettext("Creative Mode") .. ";" ..
dump(core.settings:get_bool("creative_mode")) .. "]"..
"checkbox[0,0.25;cb_enable_damage;".. fgettext("Enable Damage") .. ";" ..
dump(core.settings:get_bool("enable_damage")) .. "]"..
"checkbox[0,0.7;cb_server;".. fgettext("Host Server") ..";" ..
dump(core.settings:get_bool("enable_server")) .. "]" ..
"textlist[3.9,0.4;7.9,3.45;sp_worlds;" ..
menu_render_worldlist() ..
";" .. index .. "]"
if core.settings:get_bool("enable_server") and disabled_settings["enable_server"] == nil then
if core.settings:get_bool("enable_server") then
retval = retval ..
"button[7.9,4.75;4.1,1;play;".. fgettext("Host Game") .. "]" ..
"checkbox[0,"..y..";cb_server_announce;" .. fgettext("Announce Server") .. ";" ..
"checkbox[0,1.15;cb_server_announce;" .. fgettext("Announce Server") .. ";" ..
dump(core.settings:get_bool("server_announce")) .. "]" ..
"field[0.3,2.85;3.8,0.5;te_playername;" .. fgettext("Name") .. ";" ..
core.formspec_escape(core.settings:get("name")) .. "]" ..
@@ -215,10 +162,6 @@ local function main_button_handler(this, fields, name, tabdata)
assert(name == "local")
if this.dlg_create_world_closed_at == nil then
this.dlg_create_world_closed_at = 0
end
local world_doubleclick = false
if fields["sp_worlds"] ~= nil then
@@ -273,12 +216,6 @@ local function main_button_handler(this, fields, name, tabdata)
end
if fields["play"] ~= nil or world_doubleclick or fields["key_enter"] then
local enter_key_duration = core.get_us_time() - this.dlg_create_world_closed_at
if world_doubleclick and enter_key_duration <= 200000 then -- 200 ms
this.dlg_create_world_closed_at = 0
return true
end
local selected = core.get_textlist_index("sp_worlds")
gamedata.selected_world = menudata.worldlist:get_raw_index(selected)
@@ -290,21 +227,9 @@ local function main_button_handler(this, fields, name, tabdata)
-- Update last game
local world = menudata.worldlist:get_raw_element(gamedata.selected_world)
local game_obj
if world then
game_obj = pkgmgr.find_by_gameid(world.gameid)
core.settings:set("menu_last_game", game_obj.id)
end
local disabled_settings = get_disabled_settings(game_obj)
for k, _ in pairs(valid_disabled_settings) do
local v = disabled_settings[k]
if v ~= nil then
if k == "enable_server" and v == true then
error("Setting 'enable_server' cannot be force-enabled! The game.conf needs to be fixed.")
end
core.settings:set_bool(k, disabled_settings[k])
end
local game = pkgmgr.find_by_gameid(world.gameid)
core.settings:set("menu_last_game", game.id)
end
if core.settings:get_bool("enable_server") then
@@ -326,12 +251,11 @@ local function main_button_handler(this, fields, name, tabdata)
end
if fields["world_create"] ~= nil then
this.dlg_create_world_closed_at = 0
local create_world_dlg = create_create_world_dlg()
local create_world_dlg = create_create_world_dlg(true)
create_world_dlg:set_parent(this)
this:hide()
create_world_dlg:show()
mm_game_theme.update("singleplayer", current_game())
mm_texture.update("singleplayer", current_game())
return true
end
@@ -348,7 +272,7 @@ local function main_button_handler(this, fields, name, tabdata)
delete_world_dlg:set_parent(this)
this:hide()
delete_world_dlg:show()
mm_game_theme.update("singleplayer",current_game())
mm_texture.update("singleplayer",current_game())
end
end
@@ -366,7 +290,7 @@ local function main_button_handler(this, fields, name, tabdata)
configdialog:set_parent(this)
this:hide()
configdialog:show()
mm_game_theme.update("singleplayer",current_game())
mm_texture.update("singleplayer",current_game())
end
end
@@ -374,25 +298,28 @@ local function main_button_handler(this, fields, name, tabdata)
end
end
local function on_change(type, old_tab, new_tab)
if (type == "ENTER") then
local game = current_game()
if game then
apply_game(game)
end
local on_change
if enable_gamebar then
function on_change(type, old_tab, new_tab)
if (type == "ENTER") then
local game = current_game()
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("")
-- If new_tab is nil, a dialog is being shown; avoid resetting the theme
if new_tab then
mm_game_theme.update(new_tab,nil)
if game then
menudata.worldlist:set_filtercriteria(game.id)
core.set_topleft_text(game.name)
mm_texture.update("singleplayer",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_texture.update(new_tab,nil)
end
end
end

View File

@@ -15,50 +15,17 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function get_sorted_servers()
local servers = {
fav = {},
public = {},
incompatible = {}
}
local favs = serverlistmgr.get_favorites()
local taken_favs = {}
local result = menudata.search_result or serverlistmgr.servers
for _, server in ipairs(result) do
server.is_favorite = false
for index, fav in ipairs(favs) do
if server.address == fav.address and server.port == fav.port then
taken_favs[index] = true
server.is_favorite = true
break
end
end
server.is_compatible = is_server_protocol_compat(server.proto_min, server.proto_max)
if server.is_favorite then
table.insert(servers.fav, server)
elseif server.is_compatible then
table.insert(servers.public, server)
else
table.insert(servers.incompatible, server)
end
end
if not menudata.search_result then
for index, fav in ipairs(favs) do
if not taken_favs[index] then
table.insert(servers.fav, fav)
end
end
end
return servers
end
--------------------------------------------------------------------------------
local function get_formspec(tabview, name, tabdata)
-- Update the cached supported proto info,
-- it may have changed after a change by the settings menu.
common_update_cached_supp_proto()
local selected
if menudata.search_result then
selected = menudata.search_result[tabdata.selected]
else
selected = serverlistmgr.servers[tabdata.selected]
end
if not tabdata.search_for then
tabdata.search_for = ""
@@ -66,113 +33,96 @@ local function get_formspec(tabview, name, tabdata)
local retval =
-- Search
"field[0.25,0.25;7,0.75;te_search;;" .. core.formspec_escape(tabdata.search_for) .. "]" ..
"container[7.25,0.25]" ..
"image_button[0,0;0.75,0.75;" .. core.formspec_escape(defaulttexturedir .. "search.png") .. ";btn_mp_search;]" ..
"image_button[0.75,0;0.75,0.75;" .. core.formspec_escape(defaulttexturedir .. "clear.png") .. ";btn_mp_clear;]" ..
"image_button[1.5,0;0.75,0.75;" .. core.formspec_escape(defaulttexturedir .. "refresh.png") .. ";btn_mp_refresh;]" ..
"tooltip[btn_mp_clear;" .. fgettext("Clear") .. "]" ..
"tooltip[btn_mp_search;" .. fgettext("Search") .. "]" ..
"tooltip[btn_mp_refresh;" .. fgettext("Refresh") .. "]" ..
"container_end[]" ..
"container[9.75,0]" ..
"box[0,0;5.75,7.1;#666666]" ..
"field[0.15,0.075;5.91,1;te_search;;" .. core.formspec_escape(tabdata.search_for) .. "]" ..
"image_button[5.63,-.165;.83,.83;" .. core.formspec_escape(defaulttexturedir .. "search.png") .. ";btn_mp_search;]" ..
"image_button[6.3,-.165;.83,.83;" .. core.formspec_escape(defaulttexturedir .. "clear.png") .. ";btn_mp_clear;]" ..
"image_button[6.97,-.165;.83,.83;" .. core.formspec_escape(defaulttexturedir .. "refresh.png")
.. ";btn_mp_refresh;]" ..
-- Address / Port
"label[0.25,0.35;" .. fgettext("Address") .. "]" ..
"label[4.25,0.35;" .. fgettext("Port") .. "]" ..
"field[0.25,0.5;4,0.75;te_address;;" ..
"label[7.75,-0.25;" .. fgettext("Address / Port") .. "]" ..
"field[8,0.65;3.25,0.5;te_address;;" ..
core.formspec_escape(core.settings:get("address")) .. "]" ..
"field[4.25,0.5;1.25,0.75;te_port;;" ..
"field[11.1,0.65;1.4,0.5;te_port;;" ..
core.formspec_escape(core.settings:get("remote_port")) .. "]" ..
-- Description Background
"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[]" ..
"label[7.75,0.95;" .. fgettext("Name / Password") .. "]" ..
"field[8,1.85;2.9,0.5;te_name;;" ..
core.formspec_escape(core.settings:get("name")) .. "]" ..
"pwdfield[10.73,1.85;1.77,0.5;te_pwd;]" ..
-- Description Background
"box[7.73,2.25;4.25,2.6;#999999]"..
-- Connect
"button[3,6;2.5,0.75;btn_mp_login;" .. fgettext("Login") .. "]"
"button[9.88,4.9;2.3,1;btn_mp_connect;" .. fgettext("Connect") .. "]"
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 tabdata.selected and selected then
if gamedata.fav then
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;]"
retval = retval .. "button[7.73,4.9;2.3,1;btn_delete_favorite;" ..
fgettext("Del. Favorite") .. "]"
end
if gamedata.serverdescription then
retval = retval .. "textarea[0.25,1.85;5.2,2.75;;;" ..
core.formspec_escape(gamedata.serverdescription) .. "]"
if selected.description then
retval = retval .. "textarea[8.1,2.3;4.23,2.9;;;" ..
core.formspec_escape((gamedata.serverdescription or ""), true) .. "]"
end
end
retval = retval .. "container_end[]"
-- Table
--favorites
retval = retval .. "tablecolumns[" ..
"image,tooltip=" .. fgettext("Ping") .. "," ..
"0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," ..
"1=" .. core.formspec_escape(defaulttexturedir .. "server_ping_4.png") .. "," ..
"2=" .. core.formspec_escape(defaulttexturedir .. "server_ping_3.png") .. "," ..
"3=" .. core.formspec_escape(defaulttexturedir .. "server_ping_2.png") .. "," ..
"4=" .. core.formspec_escape(defaulttexturedir .. "server_ping_1.png") .. "," ..
"5=" .. core.formspec_escape(defaulttexturedir .. "server_favorite.png") .. "," ..
"6=" .. core.formspec_escape(defaulttexturedir .. "server_public.png") .. "," ..
"7=" .. core.formspec_escape(defaulttexturedir .. "server_incompatible.png") .. ";" ..
"color,span=1;" ..
"text,align=inline;"..
"color,span=1;" ..
"text,align=inline,width=4.25;" ..
"image,tooltip=" .. fgettext("Creative mode") .. "," ..
"0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," ..
"1=" .. core.formspec_escape(defaulttexturedir .. "server_flags_creative.png") .. "," ..
"align=inline,padding=0.25,width=1.5;" ..
image_column(fgettext("Favorite"), "favorite") .. ";" ..
image_column(fgettext("Ping")) .. ",padding=0.25;" ..
"color,span=3;" ..
"text,align=right;" .. -- clients
"text,align=center,padding=0.25;" .. -- "/"
"text,align=right,padding=0.25;" .. -- clients_max
image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" ..
image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" ..
--~ PvP = Player versus Player
"image,tooltip=" .. fgettext("Damage / PvP") .. "," ..
"0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," ..
"1=" .. core.formspec_escape(defaulttexturedir .. "server_flags_damage.png") .. "," ..
"2=" .. core.formspec_escape(defaulttexturedir .. "server_flags_pvp.png") .. "," ..
"align=inline,padding=0.25,width=1.5;" ..
"color,align=inline,span=1;" ..
"text,align=inline,padding=1]" ..
"table[0.25,1;9.25,5.8;servers;"
image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" ..
"color,span=1;" ..
"text,padding=1]" ..
"table[-0.15,0.6;7.75,5.15;favorites;"
local servers = get_sorted_servers()
if menudata.search_result then
local favs = serverlistmgr.get_favorites()
for i = 1, #menudata.search_result do
local server = menudata.search_result[i]
for fav_id = 1, #favs do
if server.address == favs[fav_id].address and
server.port == favs[fav_id].port then
server.is_favorite = true
end
end
local dividers = {
fav = "5,#ffff00," .. fgettext("Favorites") .. ",,,0,0,,",
public = "6,#4bdd42," .. fgettext("Public Servers") .. ",,,0,0,,",
incompatible = "7,"..mt_color_grey.."," .. fgettext("Incompatible Servers") .. ",,,0,0,,"
}
local order = {"fav", "public", "incompatible"}
if i ~= 1 then
retval = retval .. ","
end
tabdata.lookup = {} -- maps row number to server
local rows = {}
for _, section in ipairs(order) do
local section_servers = servers[section]
if next(section_servers) ~= nil then
rows[#rows + 1] = dividers[section]
for _, server in ipairs(section_servers) do
tabdata.lookup[#rows + 1] = server
rows[#rows + 1] = render_serverlist_row(server)
retval = retval .. render_serverlist_row(server, server.is_favorite)
end
elseif #serverlistmgr.servers > 0 then
local favs = serverlistmgr.get_favorites()
if #favs > 0 then
for i = 1, #favs do
for j = 1, #serverlistmgr.servers do
if serverlistmgr.servers[j].address == favs[i].address and
serverlistmgr.servers[j].port == favs[i].port then
table.insert(serverlistmgr.servers, i, table.remove(serverlistmgr.servers, j))
end
end
if favs[i].address ~= serverlistmgr.servers[i].address then
table.insert(serverlistmgr.servers, i, favs[i])
end
end
end
end
retval = retval .. table.concat(rows, ",")
retval = retval .. render_serverlist_row(serverlistmgr.servers[1], (#favs > 0))
for i = 2, #serverlistmgr.servers do
retval = retval .. "," .. render_serverlist_row(serverlistmgr.servers[i], (i <= #favs))
end
end
if tabdata.selected then
retval = retval .. ";" .. tabdata.selected .. "]"
@@ -180,114 +130,31 @@ local function get_formspec(tabview, name, tabdata)
retval = retval .. ";0]"
end
return retval, "size[15.5,7.1,false]real_coordinates[true]"
return retval
end
--------------------------------------------------------------------------------
local function search_server_list(input)
menudata.search_result = nil
if #serverlistmgr.servers < 2 then
return
end
-- setup the keyword list
local keywords = {}
for word in input:gmatch("%S+") do
word = word:gsub("(%W)", "%%%1")
table.insert(keywords, word)
end
if #keywords == 0 then
return
end
menudata.search_result = {}
-- Search the serverlist
local search_result = {}
for i = 1, #serverlistmgr.servers do
local server = serverlistmgr.servers[i]
local found = 0
for k = 1, #keywords do
local keyword = keywords[k]
if server.name then
local sername = server.name:lower()
local _, count = sername:gsub(keyword, keyword)
found = found + count * 4
end
if server.description then
local desc = server.description:lower()
local _, count = desc:gsub(keyword, keyword)
found = found + count * 2
end
end
if found > 0 then
local points = (#serverlistmgr.servers - i) / 5 + found
server.points = points
table.insert(search_result, server)
end
end
if #search_result == 0 then
return
end
table.sort(search_result, function(a, b)
return a.points > b.points
end)
menudata.search_result = search_result
end
local function set_selected_server(tabdata, idx, server)
-- reset selection
if idx == nil or server == nil then
tabdata.selected = nil
core.settings:set("address", "")
core.settings:set("remote_port", "30000")
return
end
local address = server.address
local port = server.port
gamedata.serverdescription = server.description
gamedata.fav = false
for _, fav in ipairs(serverlistmgr.get_favorites()) do
if address == fav.address and port == fav.port then
gamedata.fav = true
break
end
end
if address and port then
core.settings:set("address", address)
core.settings:set("remote_port", port)
end
tabdata.selected = idx
end
local function main_button_handler(tabview, fields, name, tabdata)
local serverlist = menudata.search_result or serverlistmgr.servers
if fields.te_name then
gamedata.playername = fields.te_name
core.settings:set("name", fields.te_name)
end
if fields.servers then
local event = core.explode_table_event(fields.servers)
local server = tabdata.lookup[event.row]
if fields.favorites then
local event = core.explode_table_event(fields.favorites)
local fav = serverlist[event.row]
if server then
if event.type == "DCL" then
if event.type == "DCL" then
if event.row <= #serverlist then
if not is_server_protocol_compat_or_error(
server.proto_min, server.proto_max) then
fav.proto_min, fav.proto_max) then
return true
end
gamedata.address = server.address
gamedata.port = server.port
gamedata.address = fav.address
gamedata.port = fav.port
gamedata.playername = fields.te_name
gamedata.selected_world = 0
@@ -295,32 +162,84 @@ local function main_button_handler(tabview, fields, name, tabdata)
gamedata.password = fields.te_pwd
end
gamedata.servername = server.name
gamedata.serverdescription = server.description
gamedata.servername = fav.name
gamedata.serverdescription = fav.description
if gamedata.address and gamedata.port then
core.settings:set("address", gamedata.address)
core.settings:set("remote_port", gamedata.port)
core.start()
end
return true
end
if event.type == "CHG" then
set_selected_server(tabdata, event.row, server)
return true
return true
end
if event.type == "CHG" then
if event.row <= #serverlist then
gamedata.fav = false
local favs = serverlistmgr.get_favorites()
local address = fav.address
local port = fav.port
gamedata.serverdescription = fav.description
for i = 1, #favs do
if fav.address == favs[i].address and
fav.port == favs[i].port then
gamedata.fav = true
end
end
if address and port then
core.settings:set("address", address)
core.settings:set("remote_port", port)
end
tabdata.selected = event.row
end
return true
end
end
if fields.btn_delete_favorite then
local idx = core.get_table_index("servers")
if not idx then return end
local server = tabdata.lookup[idx]
if not server then return end
if fields.key_up or fields.key_down then
local fav_idx = core.get_table_index("favorites")
local fav = serverlist[fav_idx]
serverlistmgr.delete_favorite(server)
-- the server at [idx+1] will be at idx once list is refreshed
set_selected_server(tabdata, idx, tabdata.lookup[idx+1])
if fav_idx then
if fields.key_up and fav_idx > 1 then
fav_idx = fav_idx - 1
elseif fields.key_down and fav_idx < #serverlistmgr.servers then
fav_idx = fav_idx + 1
end
else
fav_idx = 1
end
if not serverlistmgr.servers or not fav then
tabdata.selected = 0
return true
end
local address = fav.address
local port = fav.port
gamedata.serverdescription = fav.description
if address and port then
core.settings:set("address", address)
core.settings:set("remote_port", port)
end
tabdata.selected = fav_idx
return true
end
if fields.btn_delete_favorite then
local current_favorite = core.get_table_index("favorites")
if not current_favorite then return end
serverlistmgr.delete_favorite(serverlistmgr.servers[current_favorite])
serverlistmgr.sync()
tabdata.selected = nil
core.settings:set("address", "")
core.settings:set("remote_port", "30000")
return true
end
@@ -331,13 +250,63 @@ local function main_button_handler(tabview, fields, name, tabdata)
end
if fields.btn_mp_search or fields.key_enter_field == "te_search" then
tabdata.selected = 1
local input = fields.te_search:lower()
tabdata.search_for = fields.te_search
search_server_list(fields.te_search:lower())
if menudata.search_result then
-- first server in row 2 due to header
set_selected_server(tabdata, 2, menudata.search_result[1])
if #serverlistmgr.servers < 2 then
return true
end
menudata.search_result = {}
-- setup the keyword list
local keywords = {}
for word in input:gmatch("%S+") do
word = word:gsub("(%W)", "%%%1")
table.insert(keywords, word)
end
if #keywords == 0 then
menudata.search_result = nil
return true
end
-- Search the serverlist
local search_result = {}
for i = 1, #serverlistmgr.servers do
local server = serverlistmgr.servers[i]
local found = 0
for k = 1, #keywords do
local keyword = keywords[k]
if server.name then
local sername = server.name:lower()
local _, count = sername:gsub(keyword, keyword)
found = found + count * 4
end
if server.description then
local desc = server.description:lower()
local _, count = desc:gsub(keyword, keyword)
found = found + count * 2
end
end
if found > 0 then
local points = (#serverlistmgr.servers - i) / 5 + found
server.points = points
table.insert(search_result, server)
end
end
if #search_result > 0 then
table.sort(search_result, function(a, b)
return a.points > b.points
end)
menudata.search_result = search_result
local first_server = search_result[1]
core.settings:set("address", first_server.address)
core.settings:set("remote_port", first_server.port)
gamedata.serverdescription = first_server.description
end
return true
end
@@ -346,34 +315,27 @@ local function main_button_handler(tabview, fields, name, tabdata)
return true
end
local host_filled = (fields.te_address ~= "") and fields.te_port:match("^%s*[1-9][0-9]*%s*$")
local te_port_number = tonumber(fields.te_port)
if (fields.btn_mp_login or fields.key_enter) and host_filled then
if (fields.btn_mp_connect 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 = te_port_number
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.port = tonumber(fields.te_port)
gamedata.selected_world = 0
local fav_idx = core.get_table_index("favorites")
local fav = serverlist[fav_idx]
local idx = core.get_table_index("servers")
local server = idx and tabdata.lookup[idx]
if fav_idx and fav_idx <= #serverlist and
fav.address == gamedata.address and
fav.port == gamedata.port then
set_selected_server(tabdata)
serverlistmgr.add_favorite(fav)
if server and server.address == gamedata.address and
server.port == gamedata.port then
serverlistmgr.add_favorite(server)
gamedata.servername = server.name
gamedata.serverdescription = server.description
gamedata.servername = fav.name
gamedata.serverdescription = fav.description
if not is_server_protocol_compat_or_error(
server.proto_min, server.proto_max) then
fav.proto_min, fav.proto_max) then
return true
end
else
@@ -392,26 +354,6 @@ local function main_button_handler(tabview, fields, name, tabdata)
core.start()
return true
end
if fields.btn_mp_register and host_filled 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 ~= te_port_number) 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, te_port_number, server)
dlg:set_parent(tabview)
tabview:hide()
dlg:show()
return true
end
return false
end
@@ -420,6 +362,7 @@ local function on_change(type, old_tab, new_tab)
serverlistmgr.sync()
end
--------------------------------------------------------------------------------
return {
name = "online",
caption = fgettext("Join Game"),

View File

@@ -43,14 +43,6 @@ local labels = {
fgettext("2x"),
fgettext("4x"),
fgettext("8x")
},
shadow_levels = {
fgettext("Disabled"),
fgettext("Very Low"),
fgettext("Low"),
fgettext("Medium"),
fgettext("High"),
fgettext("Very High")
}
}
@@ -74,10 +66,6 @@ local dd_options = {
antialiasing = {
table.concat(labels.antialiasing, ","),
{"0", "2", "4", "8"}
},
shadow_levels = {
table.concat(labels.shadow_levels, ","),
{ "0", "1", "2", "3", "4", "5" }
}
}
@@ -122,15 +110,6 @@ local getSettingIndex = {
end
end
return 1
end,
ShadowMapping = function()
local shadow_setting = core.settings:get("shadow_levels")
for i = 1, #dd_options.shadow_levels[2] do
if shadow_setting == dd_options.shadow_levels[2][i] then
return i
end
end
return 1
end
}
@@ -160,7 +139,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.9;#999999]" ..
"box[4,0;3.75,4.5;#999999]" ..
"label[4.25,0.1;" .. fgettext("Texturing:") .. "]" ..
"dropdown[4.25,0.55;3.5;dd_filters;" .. dd_options.filters[1] .. ";"
.. getSettingIndex.Filter() .. "]" ..
@@ -169,6 +148,9 @@ 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")
@@ -200,15 +182,10 @@ local function formspec(tabview, name, tabdata)
if core.settings:get("touchscreen_threshold") ~= nil then
tab_string = tab_string ..
"label[4.25,3.5;" .. fgettext("Touch threshold (px):") .. "]" ..
"dropdown[4.25,3.95;3.5;dd_touchthreshold;0,10,20,30,40,50;" ..
"label[4.3,4.2;" .. fgettext("Touchthreshold: (px)") .. "]" ..
"dropdown[4.25,4.65;3.5;dd_touchthreshold;0,10,20,30,40,50;" ..
((tonumber(core.settings:get("touchscreen_threshold")) / 10) + 1) ..
"]"
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")) .. "]"
"]box[4.0,4.5;3.75,1.0;#999999]"
end
if shaders_enabled then
@@ -221,18 +198,6 @@ 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")) .. "]"
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",
@@ -242,9 +207,7 @@ 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")) .. "]"
end
return tab_string
@@ -258,7 +221,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
adv_settings_dlg:set_parent(this)
this:hide()
adv_settings_dlg:show()
--mm_game_theme.update("singleplayer", current_game())
--mm_texture.update("singleplayer", current_game())
return true
end
if fields["cb_smooth_lighting"] then
@@ -286,7 +249,13 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
return true
end
if fields["cb_shaders"] then
core.settings:set("enable_shaders", fields["cb_shaders"])
if (core.settings:get("video_driver") == "direct3d8" or
core.settings:get("video_driver") == "direct3d9") then
core.settings:set("enable_shaders", "false")
gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.")
else
core.settings:set("enable_shaders", fields["cb_shaders"])
end
return true
end
if fields["cb_tonemapping"] then
@@ -308,6 +277,10 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
core.show_keys_menu()
return true
end
if fields["cb_touchscreen_target"] then
core.settings:set("touchtarget", fields["cb_touchscreen_target"])
return true
end
--Note dropdowns have to be handled LAST!
local ddhandled = false
@@ -360,34 +333,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
ddhandled = true
end
for i = 1, #labels.shadow_levels do
if fields["dd_shadows"] == labels.shadow_levels[i] then
core.settings:set("shadow_levels", dd_options.shadow_levels[2][i])
ddhandled = true
end
end
if fields["dd_shadows"] == labels.shadow_levels[1] then
core.settings:set("enable_dynamic_shadows", "false")
else
local shadow_presets = {
[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
core.settings:set("enable_dynamic_shadows", "true")
core.settings:set("shadow_map_max_distance", s[1])
core.settings:set("shadow_map_texture_size", s[2])
core.settings:set("shadow_map_texture_32bit", s[3])
core.settings:set("shadow_filters", s[4])
core.settings:set("shadow_map_color", s[5])
end
end
return ddhandled
end

View File

@@ -1,243 +0,0 @@
--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 mods_dir = "/tmp/.minetest/mods"
local games_dir = "/tmp/.minetest/games"
local txp_dir = "/tmp/.minetest/textures"
local function reset()
local env = {
core = {},
unpack = table.unpack or unpack,
pkgmgr = {},
DIR_DELIM = "/",
}
env._G = env
setmetatable(env, { __index = _G })
local core = env.core
local calls = {}
function core.get_games()
return {}
end
function core.delete_dir(...)
table.insert(calls, { "delete_dir", ... })
return true
end
function core.copy_dir(...)
table.insert(calls, { "copy_dir", ... })
return true
end
function core.get_texturepath()
return txp_dir
end
function core.get_modpath()
return mods_dir
end
function core.get_gamepath()
return games_dir
end
function env.fgettext(fmt, ...)
return fmt
end
setfenv(loadfile("builtin/common/misc_helpers.lua"), env)()
setfenv(loadfile("builtin/mainmenu/pkgmgr.lua"), env)()
function env.pkgmgr.update_gamelist()
table.insert(calls, { "update_gamelist" })
end
function env.pkgmgr.refresh_globals()
table.insert(calls, { "refresh_globals" })
end
function env.assert_calls(list)
assert.are.same(list, calls)
end
return env
end
describe("install_dir", function()
it("installs texture pack", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "invalid", path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "mytxp", nil)
assert.is.equal(txp_dir .. "/mytxp", path)
assert.is._nil(message)
env.assert_calls({
{ "delete_dir", txp_dir .. "/mytxp" },
{ "copy_dir", "/tmp/123", txp_dir .. "/mytxp", false },
})
end)
it("prevents installing other as texture pack", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "mod", path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "mytxp", nil)
assert.is._nil(path)
assert.is.equal("Unable to install a $1 as a texture pack", message)
end)
it("installs mod", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "mod", path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "mymod", nil)
assert.is.equal(mods_dir .. "/mymod", path)
assert.is._nil(message)
env.assert_calls({
{ "delete_dir", mods_dir .. "/mymod" },
{ "copy_dir", "/tmp/123", mods_dir .. "/mymod", false },
{ "refresh_globals" },
})
end)
it("installs modpack", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "modpack", path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "mymod", nil)
assert.is.equal(mods_dir .. "/mymod", path)
assert.is._nil(message)
env.assert_calls({
{ "delete_dir", mods_dir .. "/mymod" },
{ "copy_dir", "/tmp/123", mods_dir .. "/mymod", false },
{ "refresh_globals" },
})
end)
it("installs game", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "game", path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("game", "/tmp/123", "mygame", nil)
assert.is.equal(games_dir .. "/mygame", path)
assert.is._nil(message)
env.assert_calls({
{ "delete_dir", games_dir .. "/mygame" },
{ "copy_dir", "/tmp/123", games_dir .. "/mygame", false },
{ "update_gamelist" },
})
end)
it("installs mod detects name", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "mod", path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", nil, nil)
assert.is.equal(mods_dir .. "/123", path)
assert.is._nil(message)
env.assert_calls({
{ "delete_dir", mods_dir .. "/123" },
{ "copy_dir", "/tmp/123", mods_dir .. "/123", false },
{ "refresh_globals" },
})
end)
it("installs mod detects invalid name", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "mod", path = "/tmp/hi!" }
end
local path, message = env.pkgmgr.install_dir("mod", "/tmp/hi!", nil, nil)
assert.is._nil(path)
assert.is.equal("Install: Unable to find suitable folder name for $1", message)
end)
it("installs mod to target (update)", function()
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = "mod", path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "mymod", "/tmp/alt-target")
assert.is.equal("/tmp/alt-target", path)
assert.is._nil(message)
env.assert_calls({
{ "delete_dir", "/tmp/alt-target" },
{ "copy_dir", "/tmp/123", "/tmp/alt-target", false },
{ "refresh_globals" },
})
end)
it("checks expected types", function()
local actual_type = "modpack"
local env = reset()
env.pkgmgr.get_base_folder = function()
return { type = actual_type, path = "/tmp/123" }
end
local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "name", nil)
assert.is._not._nil(path)
assert.is._nil(message)
path, message = env.pkgmgr.install_dir("game", "/tmp/123", "name", nil)
assert.is._nil(path)
assert.is.equal("Unable to install a $1 as a $2", message)
path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil)
assert.is._nil(path)
assert.is.equal("Unable to install a $1 as a texture pack", message)
actual_type = "game"
path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "name", nil)
assert.is._nil(path)
assert.is.equal("Unable to install a $1 as a $2", message)
path, message = env.pkgmgr.install_dir("game", "/tmp/123", "name", nil)
assert.is._not._nil(path)
assert.is._nil(message)
path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil)
assert.is._nil(path)
assert.is.equal("Unable to install a $1 as a texture pack", message)
actual_type = "txp"
path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "name", nil)
assert.is._nil(path)
assert.is.equal("Unable to install a $1 as a $2", message)
path, message = env.pkgmgr.install_dir("game", "/tmp/123", "name", nil)
assert.is._nil(path)
assert.is.equal("Unable to install a $1 as a $2", message)
path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil)
assert.is._not._nil(path)
assert.is._nil(message)
end)
end)

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