Compare commits
1351 Commits
5.5.1
...
5.8.1-andr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d445638265 | ||
|
|
f61f6626b7 | ||
|
|
857ba25115 | ||
|
|
00774bc28b | ||
|
|
f572266f19 | ||
|
|
c3893064a3 | ||
|
|
4183443f02 | ||
|
|
5715434d5e | ||
|
|
ab2419db24 | ||
|
|
35a83c3514 | ||
|
|
de09cac1e9 | ||
|
|
b64f4bdb3a | ||
|
|
008310aad9 | ||
|
|
4d024d737c | ||
|
|
50edb30a18 | ||
|
|
00f6bd0f08 | ||
|
|
81e40a1172 | ||
|
|
fe4f89f3fa | ||
|
|
0d372250c1 | ||
|
|
367720020c | ||
|
|
0dab82085d | ||
|
|
cb211d63cd | ||
|
|
49ce5a2de6 | ||
|
|
30769589bf | ||
|
|
6cf9b7472a | ||
|
|
4be8b77598 | ||
|
|
bae9f65411 | ||
|
|
0a20d30f83 | ||
|
|
7245bcc614 | ||
|
|
51136780d6 | ||
|
|
ea6eb0dfc8 | ||
|
|
ab88fc6835 | ||
|
|
30b28280eb | ||
|
|
e5672111d2 | ||
|
|
ce0aca49c2 | ||
|
|
0d3b71564f | ||
|
|
0a51fde971 | ||
|
|
a13a165e9b | ||
|
|
8a7d3d07de | ||
|
|
0977728ea0 | ||
|
|
92eb63c867 | ||
|
|
0c4a15fa16 | ||
|
|
01ac9e15ef | ||
|
|
dd3fc83777 | ||
|
|
6a5e480a58 | ||
|
|
419d971891 | ||
|
|
9e62cb5c04 | ||
|
|
bf53e7e1ca | ||
|
|
91134015e7 | ||
|
|
047520d91e | ||
|
|
6106e4e72b | ||
|
|
a7e5456099 | ||
|
|
d6a8b546e4 | ||
|
|
7f9326805c | ||
|
|
36f4953502 | ||
|
|
dc6452db1b | ||
|
|
cfe1953c2d | ||
|
|
dfe00f88e1 | ||
|
|
53886dcdb5 | ||
|
|
771da80bbb | ||
|
|
0f3ac7c956 | ||
|
|
71490a417e | ||
|
|
4255ac3022 | ||
|
|
6783734612 | ||
|
|
61db32beee | ||
|
|
31ee7af3ab | ||
|
|
72edfe3d04 | ||
|
|
7199ee4ff8 | ||
|
|
585e6aa80b | ||
|
|
1bc74b0ba1 | ||
|
|
73e85b2ebb | ||
|
|
0e4de28988 | ||
|
|
aa912e90a7 | ||
|
|
8cf76e004f | ||
|
|
7cb20dd6c2 | ||
|
|
2bc0d76f63 | ||
|
|
8abb5796ed | ||
|
|
7a658c1a6a | ||
|
|
1b0a34b9d1 | ||
|
|
8b5fc7f23a | ||
|
|
ee35d7df58 | ||
|
|
8d1f1b4704 | ||
|
|
50f48ce9df | ||
|
|
e14f905299 | ||
|
|
6980f516d4 | ||
|
|
52e66b6dfe | ||
|
|
645e4abf52 | ||
|
|
b0932ef458 | ||
|
|
330aee974e | ||
|
|
a1d7c25587 | ||
|
|
6c4352eaf9 | ||
|
|
2f279d2403 | ||
|
|
842b2bbd36 | ||
|
|
d056bb3ee7 | ||
|
|
adf9a3953b | ||
|
|
c7dd8c18ed | ||
|
|
d8c8bf1897 | ||
|
|
b0c92e885e | ||
|
|
80ae408eb9 | ||
|
|
9720eb50b3 | ||
|
|
1efa3a165e | ||
|
|
b730c0aa9a | ||
|
|
bc26bdc2bf | ||
|
|
3187aca3c9 | ||
|
|
c4b7876a1a | ||
|
|
904dbe730d | ||
|
|
8bf2031310 | ||
|
|
af474d10a4 | ||
|
|
fe8d04d0b3 | ||
|
|
394450758e | ||
|
|
56902745c8 | ||
|
|
9e952603b2 | ||
|
|
80c4c260ae | ||
|
|
570fc90bf6 | ||
|
|
7213ff7a00 | ||
|
|
adec16790b | ||
|
|
726326924d | ||
|
|
4d2227cfa5 | ||
|
|
2025dcffbd | ||
|
|
ec7a1f02e7 | ||
|
|
1d31533601 | ||
|
|
64104585c5 | ||
|
|
96197025b9 | ||
|
|
1363059416 | ||
|
|
b3988d964a | ||
|
|
2ad17136dc | ||
|
|
b2aa5d9261 | ||
|
|
4ee32c5441 | ||
|
|
ddce858c34 | ||
|
|
00be802c5c | ||
|
|
8d2e1289a4 | ||
|
|
454eb3901d | ||
|
|
a464b41d99 | ||
|
|
1a562ca144 | ||
|
|
03ba9370b9 | ||
|
|
2f16227302 | ||
|
|
15c3fb7b7a | ||
|
|
2ce14ce4eb | ||
|
|
906417cc0d | ||
|
|
2fbf5f4250 | ||
|
|
3491509b21 | ||
|
|
7e8831a414 | ||
|
|
341e53f2e2 | ||
|
|
c9655e54ce | ||
|
|
8a9855241c | ||
|
|
72fc564758 | ||
|
|
81fee2207e | ||
|
|
425db09ede | ||
|
|
0f2b196b32 | ||
|
|
bb7c0ceea0 | ||
|
|
24c2ef2996 | ||
|
|
f7775640d5 | ||
|
|
3127dd902a | ||
|
|
6445fbaadc | ||
|
|
4a4861c26f | ||
|
|
332f1af325 | ||
|
|
520cfaf13e | ||
|
|
7b14b867f5 | ||
|
|
8ab517d242 | ||
|
|
c0f0770f65 | ||
|
|
28e06f7d9c | ||
|
|
77f2c94395 | ||
|
|
17e0ec27eb | ||
|
|
d62abbc938 | ||
|
|
00b7208b5a | ||
|
|
92248e8018 | ||
|
|
78aad07be9 | ||
|
|
50cdf0e9bf | ||
|
|
a644e8c70a | ||
|
|
2e96f99e9c | ||
|
|
57cc054bb3 | ||
|
|
8df315378d | ||
|
|
bcfd1fcdba | ||
|
|
6f93853e65 | ||
|
|
c8b98d1eeb | ||
|
|
356ee9d2a9 | ||
|
|
db5a15e14c | ||
|
|
e2ab89d253 | ||
|
|
85884c15e7 | ||
|
|
fc5ff8d8c7 | ||
|
|
34e566f726 | ||
|
|
a18be49827 | ||
|
|
2871a9fee8 | ||
|
|
2817b9d84b | ||
|
|
4cc05906bd | ||
|
|
d04c1b5d73 | ||
|
|
2a518d8661 | ||
|
|
9b71b2f5d9 | ||
|
|
261bf52440 | ||
|
|
9b76946540 | ||
|
|
9c0b546942 | ||
|
|
ac3bb40692 | ||
|
|
54a39e3c4e | ||
|
|
def9db9d16 | ||
|
|
ed1c6b432c | ||
|
|
dc88e6f927 | ||
|
|
ce53230ab2 | ||
|
|
cd17caab7e | ||
|
|
d5bf34271f | ||
|
|
941896ef28 | ||
|
|
f362ed0880 | ||
|
|
be1c441157 | ||
|
|
8c3b8b7b4c | ||
|
|
be1e781399 | ||
|
|
b10fe4ddd6 | ||
|
|
d54d4b4618 | ||
|
|
09104e17a0 | ||
|
|
7a5247cc33 | ||
|
|
475809ed40 | ||
|
|
2f9742c64f | ||
|
|
85c9c27e42 | ||
|
|
8c9a4d9a05 | ||
|
|
1b2396ee9e | ||
|
|
dd587aa30d | ||
|
|
9ccc0bd27e | ||
|
|
b1dec37adb | ||
|
|
62eb6cfed0 | ||
|
|
6026003508 | ||
|
|
6fdc7e0dad | ||
|
|
3c41195986 | ||
|
|
5e0f14266d | ||
|
|
12e98678f6 | ||
|
|
352a403bd0 | ||
|
|
7e678b5686 | ||
|
|
b270c2bd68 | ||
|
|
11ec75c2ad | ||
|
|
929a13a9a0 | ||
|
|
2c74797d34 | ||
|
|
d05da513be | ||
|
|
26bb397852 | ||
|
|
01d26c0e0e | ||
|
|
e02bf9fb1a | ||
|
|
9ec40ce8e9 | ||
|
|
ac8a9f9502 | ||
|
|
c60d971bc4 | ||
|
|
8db4381304 | ||
|
|
5a5697273b | ||
|
|
de0036f4c1 | ||
|
|
33cc29bbda | ||
|
|
3a4bf14c20 | ||
|
|
56965bc814 | ||
|
|
94eba15c34 | ||
|
|
c90c545d33 | ||
|
|
bbc64a2eb5 | ||
|
|
606215fae9 | ||
|
|
8fa2ea71ef | ||
|
|
591e45657f | ||
|
|
b0d5cedeb6 | ||
|
|
d113636a43 | ||
|
|
5109fa7eda | ||
|
|
ff87be6e5f | ||
|
|
4cf900c779 | ||
|
|
c247761213 | ||
|
|
d57c936b08 | ||
|
|
9f47e123d2 | ||
|
|
c3114132d3 | ||
|
|
5949172735 | ||
|
|
e36b2226b9 | ||
|
|
a88e61c2cf | ||
|
|
5bfc5d44c0 | ||
|
|
4f735fba05 | ||
|
|
8ebaf753d3 | ||
|
|
2479d51cc6 | ||
|
|
033128d8dc | ||
|
|
4ef93fe25f | ||
|
|
833c324498 | ||
|
|
48ab1835da | ||
|
|
798b9eae4a | ||
|
|
010d08f6a4 | ||
|
|
7897450b27 | ||
|
|
2ad4c9e0ce | ||
|
|
95056f9783 | ||
|
|
1a568cc491 | ||
|
|
83b85ba16a | ||
|
|
294ad98776 | ||
|
|
f080aa29b5 | ||
|
|
4252f9d4d5 | ||
|
|
0cbf96cc83 | ||
|
|
7b56daa236 | ||
|
|
852d6a7976 | ||
|
|
bf9f831cb2 | ||
|
|
0ba899e239 | ||
|
|
660151572f | ||
|
|
54eacca287 | ||
|
|
f47b00426a | ||
|
|
7e4dccb3b5 | ||
|
|
f98726c516 | ||
|
|
aea9242a96 | ||
|
|
92b6ff4721 | ||
|
|
72ef90885d | ||
|
|
d0ee63c766 | ||
|
|
587e2b2526 | ||
|
|
a65cdbe66e | ||
|
|
7b3ed32003 | ||
|
|
2c987b66c1 | ||
|
|
e48f15c135 | ||
|
|
bf36a90579 | ||
|
|
43c9c38a28 | ||
|
|
f6bddc4e8d | ||
|
|
f9c881eb5a | ||
|
|
16da954bd7 | ||
|
|
45e7a80057 | ||
|
|
7e7aceb8c1 | ||
|
|
d75c956dbc | ||
|
|
f7f3aaf43c | ||
|
|
91c0439922 | ||
|
|
9d62abbe46 | ||
|
|
124d064015 | ||
|
|
2903f692ba | ||
|
|
7f9de5db0b | ||
|
|
14441a289e | ||
|
|
137e4ce866 | ||
|
|
526c5f2348 | ||
|
|
e4bedc7ea8 | ||
|
|
c6a0ead72d | ||
|
|
98f097dc2f | ||
|
|
c816aa5374 | ||
|
|
4d9a67682d | ||
|
|
d16d1a1341 | ||
|
|
752ce1a1b2 | ||
|
|
28fce8aad5 | ||
|
|
e0948f42ab | ||
|
|
21ecdd5681 | ||
|
|
20e9969313 | ||
|
|
3f2a10bb4b | ||
|
|
9f25378ddd | ||
|
|
cc8280426f | ||
|
|
ba6de431a2 | ||
|
|
c14e4d1795 | ||
|
|
e0192e256f | ||
|
|
53c594abe0 | ||
|
|
72ed8514c5 | ||
|
|
6f0d36c41a | ||
|
|
7473e4cafd | ||
|
|
6bf63d4b41 | ||
|
|
05ebe2418b | ||
|
|
9bef3c136a | ||
|
|
307e380f30 | ||
|
|
128d22e6ee | ||
|
|
50234b8e5c | ||
|
|
3552537fc4 | ||
|
|
f41e9e3e0f | ||
|
|
9b310a6e6f | ||
|
|
2061984313 | ||
|
|
3a47559e86 | ||
|
|
8e09077de8 | ||
|
|
1837a11c22 | ||
|
|
bf987bf58a | ||
|
|
4a14a18799 | ||
|
|
136a93f628 | ||
|
|
0218963f1b | ||
|
|
078bd95a49 | ||
|
|
869df17ddf | ||
|
|
26453df2f7 | ||
|
|
d71872af23 | ||
|
|
25ef8f3934 | ||
|
|
ff498fc206 | ||
|
|
0ade097e99 | ||
|
|
dde8f0e20a | ||
|
|
21035bf5d4 | ||
|
|
d7291e0600 | ||
|
|
aaae9d5a77 | ||
|
|
c09a3a52ac | ||
|
|
442d5fc75c | ||
|
|
3b74cc4a41 | ||
|
|
de77fe8ade | ||
|
|
2f6a9d12f1 | ||
|
|
aada2403c9 | ||
|
|
35ad3dabab | ||
|
|
4fb6754903 | ||
|
|
7e51e2dea6 | ||
|
|
84fb663d6c | ||
|
|
659828b142 | ||
|
|
610578e3e2 | ||
|
|
62629939ff | ||
|
|
20b10b5691 | ||
|
|
32ff832108 | ||
|
|
4fdd2dec59 | ||
|
|
524d446757 | ||
|
|
5b6bc8a12b | ||
|
|
6a328197a5 | ||
|
|
03ffc2618c | ||
|
|
7c26cb1c35 | ||
|
|
1102f92dac | ||
|
|
729671d6ae | ||
|
|
d676520526 | ||
|
|
c29d897854 | ||
|
|
b8ddde0a96 | ||
|
|
43c9647fe5 | ||
|
|
03dda13910 | ||
|
|
531122ee86 | ||
|
|
8f25f487fe | ||
|
|
f1feeb319c | ||
|
|
edcbfa31c9 | ||
|
|
8e1af25738 | ||
|
|
6b3deaa170 | ||
|
|
a4e69d6843 | ||
|
|
c549e84abb | ||
|
|
dade95e142 | ||
|
|
f947e2afec | ||
|
|
6a05d63993 | ||
|
|
5e6d144567 | ||
|
|
9c348d057e | ||
|
|
8b108ed5f2 | ||
|
|
e700182f44 | ||
|
|
34ad551efc | ||
|
|
5d863d7e9c | ||
|
|
28766d1879 | ||
|
|
ba80d1ce1f | ||
|
|
c91182e1b3 | ||
|
|
553dc02deb | ||
|
|
1b51ff333a | ||
|
|
1780d1bbde | ||
|
|
08ea467bfe | ||
|
|
cfb1b879e0 | ||
|
|
d0bcdff5ce | ||
|
|
8445c5fe60 | ||
|
|
a1463263b5 | ||
|
|
a857c46e6e | ||
|
|
d9f478cbfb | ||
|
|
252c79d53a | ||
|
|
23f7aab354 | ||
|
|
e5a5d5a672 | ||
|
|
29b7aea38b | ||
|
|
1ef9fc9d1f | ||
|
|
7221de6ede | ||
|
|
8cd1296049 | ||
|
|
a8ec6092e2 | ||
|
|
fc3d6c1dd9 | ||
|
|
6832bf044e | ||
|
|
394dd9ffa5 | ||
|
|
8cccd75e81 | ||
|
|
00c647e4cc | ||
|
|
f4cb16cc2d | ||
|
|
d6eb6ff973 | ||
|
|
b60d38b7f9 | ||
|
|
180ec92ef9 | ||
|
|
95a9f4ab7c | ||
|
|
f393214fef | ||
|
|
5ba70cf5ef | ||
|
|
35112f2453 | ||
|
|
15fb4cab15 | ||
|
|
15445a0fbe | ||
|
|
80574cdbe8 | ||
|
|
3de54039ae | ||
|
|
65692ad1b5 | ||
|
|
bc4fc6d648 | ||
|
|
ad37df7f2e | ||
|
|
d35672e78e | ||
|
|
a421a1d764 | ||
|
|
bec9c68bf3 | ||
|
|
b35aa10579 | ||
|
|
7f6b09dce8 | ||
|
|
8b73743baa | ||
|
|
e9e8eed360 | ||
|
|
062b4d036a | ||
|
|
d197ff0f9d | ||
|
|
9c90358912 | ||
|
|
0fb6dbab36 | ||
|
|
4158b72971 | ||
|
|
8c2c7fadbf | ||
|
|
b1786e88ac | ||
|
|
5cd6a22dd3 | ||
|
|
b89077187b | ||
|
|
c5fb50298a | ||
|
|
ccd696c49a | ||
|
|
50e91b882c | ||
|
|
ae7271b725 | ||
|
|
d49d80a4a0 | ||
|
|
0b08e1b1d2 | ||
|
|
f9b1176fa9 | ||
|
|
3d232e2345 | ||
|
|
9d1ae80e89 | ||
|
|
2a1bc82887 | ||
|
|
d1e5dbefc7 | ||
|
|
fc116ec950 | ||
|
|
7283d2495f | ||
|
|
e139749b5c | ||
|
|
fe75ec8d0d | ||
|
|
68f81ace97 | ||
|
|
2fc7eb3ea2 | ||
|
|
1dd13da37d | ||
|
|
b201c03625 | ||
|
|
ceec560779 | ||
|
|
d39a07efea | ||
|
|
73391013f7 | ||
|
|
1d88d85f1c | ||
|
|
4a742be73e | ||
|
|
8982998681 | ||
|
|
9d736e8b8b | ||
|
|
c26e122485 | ||
|
|
67068cfaf4 | ||
|
|
35929d27e3 | ||
|
|
2e497cc471 | ||
|
|
c2a9ac24ac | ||
|
|
1b95998d11 | ||
|
|
cc29bb473d | ||
|
|
bdebacd3a5 | ||
|
|
036d08fb70 | ||
|
|
803a57a490 | ||
|
|
f2433c7b63 | ||
|
|
f9376ac763 | ||
|
|
6885fd4fd0 | ||
|
|
cee1c5d11d | ||
|
|
22041c5e8a | ||
|
|
f0fcc150d0 | ||
|
|
469a2eb4a4 | ||
|
|
7ae5c91d62 | ||
|
|
051102cde0 | ||
|
|
76b61f07c9 | ||
|
|
52956464a5 | ||
|
|
046ca78dfa | ||
|
|
aa28084604 | ||
|
|
a3a4640257 | ||
|
|
87b3e9cc5f | ||
|
|
4083fcf93a | ||
|
|
1036c2a639 | ||
|
|
7bfea9b0fe | ||
|
|
ba2fee0751 | ||
|
|
f8e0778fc9 | ||
|
|
9c9309cdbb | ||
|
|
d975ebdcb9 | ||
|
|
7048fc25dd | ||
|
|
b01f85d573 | ||
|
|
1c1f1b1615 | ||
|
|
6c0a6925fc | ||
|
|
819e9fc615 | ||
|
|
48fc286a95 | ||
|
|
93898957b6 | ||
|
|
bd88d299b9 | ||
|
|
0a698d92c3 | ||
|
|
0f496f1ed2 | ||
|
|
ea095d3f69 | ||
|
|
2180dc14ef | ||
|
|
baf99f826c | ||
|
|
9c07bf68f1 | ||
|
|
ed632f3854 | ||
|
|
f3b198e490 | ||
|
|
9af587c54e | ||
|
|
6cd2eea487 | ||
|
|
09342c0811 | ||
|
|
0050f12b32 | ||
|
|
5fa63a0b0c | ||
|
|
6fe9bc8ae1 | ||
|
|
38e005294f | ||
|
|
b1c8a7d055 | ||
|
|
2bed338ef7 | ||
|
|
b7359f5fa9 | ||
|
|
cac1dca95b | ||
|
|
02346819f9 | ||
|
|
3e148e2810 | ||
|
|
b1ed0ef721 | ||
|
|
1aeb0280df | ||
|
|
e73a4ea506 | ||
|
|
705195b43e | ||
|
|
1de8a1e962 | ||
|
|
ad41d0af9d | ||
|
|
c77f3d4dc4 | ||
|
|
225659f6e0 | ||
|
|
ad203b75e1 | ||
|
|
37f4d6e447 | ||
|
|
b12eb5e490 | ||
|
|
a32bbc8941 | ||
|
|
0263069255 | ||
|
|
8ac5c45da5 | ||
|
|
7dd28f0751 | ||
|
|
0f50d2d329 | ||
|
|
306b86ee91 | ||
|
|
ff22d650ed | ||
|
|
be5710810b | ||
|
|
8d691e6633 | ||
|
|
6f53af2416 | ||
|
|
7e22d472a3 | ||
|
|
ecb1bd7ac8 | ||
|
|
78d5c95006 | ||
|
|
36c5ea2d5d | ||
|
|
cd0801a4fa | ||
|
|
fb7bfb62d8 | ||
|
|
bc2d863492 | ||
|
|
87d3c773bf | ||
|
|
22350290c5 | ||
|
|
9cb12e3565 | ||
|
|
34097e3a6f | ||
|
|
cbe35af9c9 | ||
|
|
1ca1ea2ca9 | ||
|
|
bf7478b0a8 | ||
|
|
611f6b0cff | ||
|
|
c1d2c0d4ec | ||
|
|
1e4b6ba1a3 | ||
|
|
880c2ecb3b | ||
|
|
a5a991576f | ||
|
|
b37886375f | ||
|
|
7fa9dbccf3 | ||
|
|
0fbdc58303 | ||
|
|
15239d1449 | ||
|
|
6fd412c745 | ||
|
|
1f0d042377 | ||
|
|
9ef3c8ce38 | ||
|
|
4f06df719d | ||
|
|
c91f3f99fd | ||
|
|
847ed04e0a | ||
|
|
e66e583f5e | ||
|
|
dcf6a6a67b | ||
|
|
915befecc5 | ||
|
|
fee2e3ee27 | ||
|
|
a93f3542d9 | ||
|
|
8c7276c9d4 | ||
|
|
514bf3582c | ||
|
|
d6be6682ec | ||
|
|
39f4d26177 | ||
|
|
fbbdae93ee | ||
|
|
fe3ea090d1 | ||
|
|
2083252c05 | ||
|
|
6e1c70e02b | ||
|
|
63c378fb5b | ||
|
|
2553db5c81 | ||
|
|
af4009d924 | ||
|
|
cf19167e99 | ||
|
|
aa5dc0968b | ||
|
|
3bafbaac49 | ||
|
|
2dafce6206 | ||
|
|
0e721f7571 | ||
|
|
c1e430ef68 | ||
|
|
75e6cc190a | ||
|
|
2a8becd650 | ||
|
|
4cd6b773bb | ||
|
|
d3a6ee00e6 | ||
|
|
56d2567b5d | ||
|
|
8bbb673c0b | ||
|
|
69fc206109 | ||
|
|
cded6a3945 | ||
|
|
b8aaad4f1e | ||
|
|
8478796226 | ||
|
|
87d509e462 | ||
|
|
6f5703baf1 | ||
|
|
cf5add1472 | ||
|
|
47c8b5d57a | ||
|
|
ca13c51024 | ||
|
|
e21d5613a6 | ||
|
|
ecd6d61697 | ||
|
|
a2a280691c | ||
|
|
2f9f0c0900 | ||
|
|
8fded9d990 | ||
|
|
3992a13f24 | ||
|
|
5f2925c59c | ||
|
|
956026bb6b | ||
|
|
ab1fe80150 | ||
|
|
a3177b89d8 | ||
|
|
139db66901 | ||
|
|
d69cb4fb5d | ||
|
|
d0b6f217ae | ||
|
|
390b5caaaa | ||
|
|
fb28ca463e | ||
|
|
55804c56e9 | ||
|
|
d82d18bfb1 | ||
|
|
d603619ad3 | ||
|
|
4685849f89 | ||
|
|
07624125ef | ||
|
|
bb74da5903 | ||
|
|
6d45c243f8 | ||
|
|
2715cc8bf6 | ||
|
|
059f62d7d6 | ||
|
|
0dbb20fd63 | ||
|
|
6377ce921d | ||
|
|
89e7f72c92 | ||
|
|
03e710160f | ||
|
|
1e7804aaf6 | ||
|
|
afd5caa26a | ||
|
|
7f01471141 | ||
|
|
33363c2a7e | ||
|
|
d13b12b791 | ||
|
|
5c248c2d7d | ||
|
|
7701e70dc9 | ||
|
|
62ee02b8ba | ||
|
|
475f85fc91 | ||
|
|
0fc97a1483 | ||
|
|
1f3b5e553b | ||
|
|
981d79157a | ||
|
|
111d047b0a | ||
|
|
024c99fc47 | ||
|
|
219904dc54 | ||
|
|
0bff85f7b0 | ||
|
|
b30e0681a0 | ||
|
|
8cba08d423 | ||
|
|
6ae870b27d | ||
|
|
f004432ec2 | ||
|
|
5cfe76bb60 | ||
|
|
866fbf9149 | ||
|
|
8660f90868 | ||
|
|
569c83a8e7 | ||
|
|
4a1ca5e16c | ||
|
|
49183e3555 | ||
|
|
1fb1a9e211 | ||
|
|
f858bb9a75 | ||
|
|
fbe97a1b08 | ||
|
|
d1055aa8d8 | ||
|
|
1dde03dfec | ||
|
|
5240febeef | ||
|
|
c6aea54497 | ||
|
|
15aac97790 | ||
|
|
68493bd14a | ||
|
|
41960c0019 | ||
|
|
eb6c88f8c0 | ||
|
|
928b6d659c | ||
|
|
058bf1e6ec | ||
|
|
7e793438a3 | ||
|
|
c476bc3772 | ||
|
|
5a9982b7e0 | ||
|
|
08279a7565 | ||
|
|
baac5459c4 | ||
|
|
4f77005614 | ||
|
|
ee2d3bc293 | ||
|
|
8797c5bea4 | ||
|
|
9d6a2bda24 | ||
|
|
1cdd690630 | ||
|
|
692163275d | ||
|
|
042c7490ed | ||
|
|
c1aed7b4d3 | ||
|
|
bebf1d1f3e | ||
|
|
db7151be53 | ||
|
|
e160505595 | ||
|
|
9bfbe80d06 | ||
|
|
4159b2860f | ||
|
|
c86adf87ae | ||
|
|
fb738ae85a | ||
|
|
0aae94ba7e | ||
|
|
73c8e1f03a | ||
|
|
3cd99d8e6d | ||
|
|
3e6098f5c6 | ||
|
|
56fc164fdd | ||
|
|
8f881b04f4 | ||
|
|
80c9c66e16 | ||
|
|
5d68026c2d | ||
|
|
ed8dce4e72 | ||
|
|
af8151d827 | ||
|
|
c18c082059 | ||
|
|
1798ad0ec4 | ||
|
|
504e43e0da | ||
|
|
37386b9c3c | ||
|
|
aa3505a9e4 | ||
|
|
b85831e389 | ||
|
|
291c42ed57 | ||
|
|
f80ea73bfb | ||
|
|
b3ffc4b327 | ||
|
|
e84d259ec7 | ||
|
|
281f9a9f88 | ||
|
|
055fc69c11 | ||
|
|
3ff8adf599 | ||
|
|
da4a4086cf | ||
|
|
aac1635bf7 | ||
|
|
38169db765 | ||
|
|
d0a118f5b1 | ||
|
|
3fd5bff128 | ||
|
|
40a45b8c99 | ||
|
|
f04d4d0291 | ||
|
|
8b26bab37d | ||
|
|
7c21347a40 | ||
|
|
8817af07fb | ||
|
|
3c7f26d937 | ||
|
|
1c10988d6a | ||
|
|
9527cc3fa0 | ||
|
|
386bfcda2b | ||
|
|
4da8a18c8c | ||
|
|
70a82b0784 | ||
|
|
5f24a3c0c7 | ||
|
|
475005012a | ||
|
|
7bf64fc61a | ||
|
|
dac05a500e | ||
|
|
b89eb605b7 | ||
|
|
6b6cd42ce4 | ||
|
|
6bf662cb9e | ||
|
|
8de9e2ac84 | ||
|
|
cd8a7fe472 | ||
|
|
1a045da0dd | ||
|
|
00eb65915f | ||
|
|
f8c781b46c | ||
|
|
aaa05f901a | ||
|
|
9dbac989bd | ||
|
|
408af9d17d | ||
|
|
88b04eadc9 | ||
|
|
042f7917e7 | ||
|
|
81bfc9c7a2 | ||
|
|
d1b80b462e | ||
|
|
6191bafcad | ||
|
|
322c8cf270 | ||
|
|
957a3e52fe | ||
|
|
9b24041394 | ||
|
|
fb3085a2c5 | ||
|
|
987277de52 | ||
|
|
88af36dd10 | ||
|
|
260de1c2b5 | ||
|
|
485b3b1203 | ||
|
|
bf1cc1bb84 | ||
|
|
3e7ee499d6 | ||
|
|
a075d83752 | ||
|
|
fcd670e6f7 | ||
|
|
077627181e | ||
|
|
b829231992 | ||
|
|
9aaed75eea | ||
|
|
88820cd31c | ||
|
|
16266397ed | ||
|
|
02c293ec63 | ||
|
|
e86d23daed | ||
|
|
0a82cb4072 | ||
|
|
7a8ac00f9c | ||
|
|
9e186a42bd | ||
|
|
c73d79841c | ||
|
|
11d1a9cc37 | ||
|
|
9f11753930 | ||
|
|
fd1930142e | ||
|
|
a852ebe993 | ||
|
|
0152d39215 | ||
|
|
c761aa268d | ||
|
|
72b83acadc | ||
|
|
998e50725c | ||
|
|
a23701b5e6 | ||
|
|
c1e732448c | ||
|
|
23ef0d0916 | ||
|
|
2da92ed81e | ||
|
|
cb7b96fc90 | ||
|
|
68df0fb2ea | ||
|
|
3a7fffc587 | ||
|
|
48530ccbc0 | ||
|
|
6b9984b7e7 | ||
|
|
97a80b4816 | ||
|
|
8f1593e4e8 | ||
|
|
7e11b8eb72 | ||
|
|
c78d565e01 | ||
|
|
8bdedd2bcf | ||
|
|
7153cb8a0b | ||
|
|
9f0d88407d | ||
|
|
dafdb3edb4 | ||
|
|
b38ffdec27 | ||
|
|
23e9f5db43 | ||
|
|
862419c76f | ||
|
|
4586f3342f | ||
|
|
87051fca26 | ||
|
|
5d8a4917c5 | ||
|
|
25c5400250 | ||
|
|
f680d10259 | ||
|
|
e8ee4cb40d | ||
|
|
af38bae57f | ||
|
|
558cbd89fb | ||
|
|
cb725a4555 | ||
|
|
6b6f886bcd | ||
|
|
f7ae70c3d9 | ||
|
|
f073e37d2f | ||
|
|
b3503e7853 | ||
|
|
8f996e4a7c | ||
|
|
7a28f2c4fa | ||
|
|
9676364c1f | ||
|
|
440d966b93 | ||
|
|
b2a3f53b29 | ||
|
|
c1c68775b2 | ||
|
|
804a318189 | ||
|
|
be5c675263 | ||
|
|
1e96403954 | ||
|
|
579fc93c24 | ||
|
|
7632af3c73 | ||
|
|
b21fb18379 | ||
|
|
b10d6542db | ||
|
|
22cbc05808 | ||
|
|
977f656e09 | ||
|
|
525fc3833c | ||
|
|
bbdb1929c6 | ||
|
|
13a8948edd | ||
|
|
e832cee1e6 | ||
|
|
6eb7d57ed3 | ||
|
|
5e7ea0664a | ||
|
|
9df79a4b2d | ||
|
|
3978b9b8ed | ||
|
|
b1233056b7 | ||
|
|
0251b01da6 | ||
|
|
6ac38aa2c8 | ||
|
|
907dcdcf7b | ||
|
|
3f801bc096 | ||
|
|
f4a01f3a5d | ||
|
|
03428d9825 | ||
|
|
f916398a54 | ||
|
|
8dec3a5ecb | ||
|
|
7069d99aa6 | ||
|
|
5ced5c9b27 | ||
|
|
1317cd12d7 | ||
|
|
11905a6db6 | ||
|
|
2c3f641e0b | ||
|
|
587f6656a4 | ||
|
|
9acf2d3db7 | ||
|
|
310b12b5ed | ||
|
|
006d974c58 | ||
|
|
b89608c624 | ||
|
|
6f5a68b7f7 | ||
|
|
c9ed059d91 | ||
|
|
4377c03168 | ||
|
|
a428a0cf37 | ||
|
|
1e0520074c | ||
|
|
9428917870 | ||
|
|
19e936362a | ||
|
|
1d04903c19 | ||
|
|
b5e7280708 | ||
|
|
b9f6832347 | ||
|
|
f8bb0cd3d1 | ||
|
|
129aef758e | ||
|
|
94f55cf406 | ||
|
|
a20b758e19 | ||
|
|
128842becf | ||
|
|
d51331f51f | ||
|
|
4ef221a645 | ||
|
|
79010e972e | ||
|
|
0ca530e251 | ||
|
|
57b4d46dbc | ||
|
|
50df5e2f59 | ||
|
|
b6db2c7262 | ||
|
|
9441b69ad2 | ||
|
|
08e3d16a58 | ||
|
|
3f3049fdba | ||
|
|
41fb7a8a7e | ||
|
|
5b9828e094 | ||
|
|
96e35585b0 | ||
|
|
f3f3b752f2 | ||
|
|
cea5fd56a4 | ||
|
|
482cc99b2c | ||
|
|
bc3dccca5c | ||
|
|
fe13f9dfd1 | ||
|
|
7486f184c3 | ||
|
|
6c24dc4e23 | ||
|
|
c607bee19e | ||
|
|
adb03ccc6d | ||
|
|
2133fc84c4 | ||
|
|
643971c948 | ||
|
|
038da00e79 | ||
|
|
ff6dcfea82 | ||
|
|
464043b8ab | ||
|
|
2854c19792 | ||
|
|
75d88dcae2 | ||
|
|
0ab9bf926d | ||
|
|
2d10fa7867 | ||
|
|
8c29c4f620 | ||
|
|
3f67215df9 | ||
|
|
c4ffe630f1 | ||
|
|
7c5e3cac6a | ||
|
|
adc89f7977 | ||
|
|
db612c10ee | ||
|
|
0090446ccf | ||
|
|
cae7ec1eb4 | ||
|
|
bce1078ced | ||
|
|
aa2fdc6ef6 | ||
|
|
2690585e99 | ||
|
|
760242c076 | ||
|
|
c7059c4981 | ||
|
|
d1cbb4bd8a | ||
|
|
0e439b2fa3 | ||
|
|
3132efcc01 | ||
|
|
ab8dfb45b4 | ||
|
|
59601eb922 | ||
|
|
023a1c2427 | ||
|
|
8bf1609ccc | ||
|
|
c8ee755c05 | ||
|
|
df1d215f48 | ||
|
|
bcc56803d7 | ||
|
|
4fbcc33ee0 | ||
|
|
f035fe9336 | ||
|
|
ec778508df | ||
|
|
53dd648c96 | ||
|
|
c4277877b6 | ||
|
|
eb49b6d85c | ||
|
|
b91063daef | ||
|
|
7c14b434e0 | ||
|
|
644f145ff2 | ||
|
|
a81259d19a | ||
|
|
f22d40975e | ||
|
|
4c1ef1b72b | ||
|
|
6ec6acc539 | ||
|
|
839600ed70 | ||
|
|
a2bf3a2aa8 | ||
|
|
9542ab5efb | ||
|
|
68a273102f | ||
|
|
13bb815b32 | ||
|
|
ccb982513a | ||
|
|
ef6ca6319b | ||
|
|
5192ce4330 | ||
|
|
62624722d8 | ||
|
|
b2d82125ec | ||
|
|
e80f695bb9 | ||
|
|
d9d38d2172 | ||
|
|
84e939193e | ||
|
|
56811c63d1 | ||
|
|
a4b1e0d124 | ||
|
|
70b71c5013 | ||
|
|
95d7fcb949 | ||
|
|
de509d05e6 | ||
|
|
c14b7536a4 | ||
|
|
a871115889 | ||
|
|
6a269d58ef | ||
|
|
3b37bcd994 | ||
|
|
99c8295e71 | ||
|
|
6611d7e1ee | ||
|
|
f0703f3c5b | ||
|
|
7e78c0171e | ||
|
|
175e132576 | ||
|
|
71f6a5f44e | ||
|
|
d631f21024 | ||
|
|
2351c95612 | ||
|
|
8dcbca1068 | ||
|
|
2183b35ba4 | ||
|
|
2cf52642fa | ||
|
|
eea2a97475 | ||
|
|
71f083d4ad | ||
|
|
32ef0678e5 | ||
|
|
e719690100 | ||
|
|
53fb958f45 | ||
|
|
ea51073e87 | ||
|
|
116de34438 | ||
|
|
816206321e | ||
|
|
eaa9ab754f | ||
|
|
5bd09907a9 | ||
|
|
7a76a00b62 | ||
|
|
cd96c6934d | ||
|
|
356fdc3cf5 | ||
|
|
7001d3de60 | ||
|
|
b64e030e92 | ||
|
|
9bb2cca842 | ||
|
|
639bcd3cae | ||
|
|
cdb9e3dea6 | ||
|
|
4c15dcbe89 | ||
|
|
cd2d25def7 | ||
|
|
2e816c0c1a | ||
|
|
3888376be3 | ||
|
|
71b8204652 | ||
|
|
62d74507d4 | ||
|
|
5d0235e757 | ||
|
|
29fa11ef4d | ||
|
|
919e5fbc5a | ||
|
|
259ceee478 | ||
|
|
ef9cca2f6e | ||
|
|
ae58485b67 | ||
|
|
731ac55a1b | ||
|
|
804fb825bb | ||
|
|
a0b966c5b4 | ||
|
|
001ad8e51b | ||
|
|
e0259b310b | ||
|
|
010694f64c | ||
|
|
398c7bded1 | ||
|
|
1327039824 | ||
|
|
1870c28397 | ||
|
|
a36d6b3688 | ||
|
|
d066cd1ff6 | ||
|
|
7c4db9f425 | ||
|
|
72a6724a46 | ||
|
|
df9b7f6b70 | ||
|
|
3e122f3eff | ||
|
|
6dac32e165 | ||
|
|
24fd5c39f5 | ||
|
|
9f66974cd4 | ||
|
|
c0e03f7ce7 | ||
|
|
93247a8a35 | ||
|
|
efcd1ff707 | ||
|
|
7b242d787e | ||
|
|
68ca317ec2 | ||
|
|
de76686bdc | ||
|
|
c792f3bf57 | ||
|
|
3903a1b4af | ||
|
|
106f0e3af6 | ||
|
|
f275a66f91 | ||
|
|
37f559b4b1 | ||
|
|
1aec8ed330 | ||
|
|
b6f0ebf968 | ||
|
|
3a60050ddd | ||
|
|
bfdbcf1955 | ||
|
|
e5e94070d6 | ||
|
|
70c54abc2a | ||
|
|
b270a46e53 | ||
|
|
d400a98ef0 | ||
|
|
7b6c4bf2e0 | ||
|
|
f090471022 | ||
|
|
4648d8f499 | ||
|
|
5cc7329717 | ||
|
|
9f41b4f72d | ||
|
|
06de82fd86 | ||
|
|
1d512ef7f4 | ||
|
|
8ff3fadba0 | ||
|
|
137eef6590 | ||
|
|
b204655081 | ||
|
|
f4c6ed863d | ||
|
|
ac4eb746fe | ||
|
|
6df69f9b5b | ||
|
|
20bd6bdb68 | ||
|
|
8724fe6e3f | ||
|
|
fae8726a76 | ||
|
|
e51f474613 | ||
|
|
051181fa6e | ||
|
|
7c261118e0 | ||
|
|
b15393c2a5 | ||
|
|
0e63f18a73 | ||
|
|
fc3460470a | ||
|
|
34f15259fa | ||
|
|
f7bcf7fa46 | ||
|
|
5a562a597c | ||
|
|
3e308584a3 | ||
|
|
9ac3b52fdc | ||
|
|
142928e944 | ||
|
|
b095dc4f2b | ||
|
|
45da0d43fd | ||
|
|
7494ff2917 | ||
|
|
de8ce9a8ff | ||
|
|
17709d7d0f | ||
|
|
a5f385917d | ||
|
|
18fbc0394b | ||
|
|
35ad006234 | ||
|
|
4163c872af | ||
|
|
4b087a0de2 | ||
|
|
a463620edb | ||
|
|
0b41533763 | ||
|
|
e92a217bd1 | ||
|
|
a83d81ff45 | ||
|
|
ae555465ba | ||
|
|
622d857bed | ||
|
|
0530ec11c0 | ||
|
|
46e7b51352 | ||
|
|
e9e721b937 | ||
|
|
992f501159 | ||
|
|
0f3f1a001c | ||
|
|
804c255941 | ||
|
|
c10fe7ec18 | ||
|
|
7ffc0268df | ||
|
|
e7d4ec6834 | ||
|
|
f4a53f7ee6 | ||
|
|
3eafcab64e | ||
|
|
ba65e0ace7 | ||
|
|
213d3562bd | ||
|
|
3ac5a24b12 | ||
|
|
3107c98591 | ||
|
|
edc7df5480 | ||
|
|
951604e29f | ||
|
|
381f84ee27 | ||
|
|
14c283a623 | ||
|
|
4baf56520d | ||
|
|
a69b7abe00 | ||
|
|
03d86ea0b4 | ||
|
|
21323ef1ff | ||
|
|
1f39948bc3 | ||
|
|
e82985c0a1 | ||
|
|
8e5bd82c4d | ||
|
|
575caa8015 | ||
|
|
5f3af7d18b | ||
|
|
6a6b579c54 | ||
|
|
6d163b72dc | ||
|
|
b72932b445 | ||
|
|
9fc018ded1 | ||
|
|
a9a207685a | ||
|
|
c1d03695d4 | ||
|
|
ea74680df4 | ||
|
|
bb671c3089 | ||
|
|
1b68fb7683 | ||
|
|
303329f2d6 | ||
|
|
85c824ed13 | ||
|
|
998e4820c9 | ||
|
|
5cd7b0c6e4 | ||
|
|
8908a91016 | ||
|
|
261a8db9dd | ||
|
|
f195db2d14 | ||
|
|
da71e86633 | ||
|
|
bccaf5fc2d | ||
|
|
0c6a029413 | ||
|
|
25ba9d848d | ||
|
|
76000e676b | ||
|
|
e8b2954586 | ||
|
|
fe299e24d6 | ||
|
|
ef22c0206f | ||
|
|
8b74257bf3 | ||
|
|
9a01581cdd | ||
|
|
5d26ac0088 | ||
|
|
ed26ed5a1f | ||
|
|
16a30556df | ||
|
|
5daafc9d33 | ||
|
|
e16a470d59 | ||
|
|
367a2d4b29 | ||
|
|
0f9c78c3eb | ||
|
|
c660218e43 | ||
|
|
fa682270a9 | ||
|
|
ac5e8176b9 | ||
|
|
9f338f5a56 | ||
|
|
2742fef458 | ||
|
|
bc59fcf5c5 | ||
|
|
2f32044273 | ||
|
|
371f21fb35 | ||
|
|
8edc0fae5f | ||
|
|
e1f707d7e1 | ||
|
|
9ee3dc71f1 | ||
|
|
70dc23f996 | ||
|
|
4e9e230e34 | ||
|
|
dc45b85a54 | ||
|
|
a4ef62f5b2 | ||
|
|
604fb2b738 | ||
|
|
273bfee9a1 | ||
|
|
af37f9dc54 | ||
|
|
db9b3aff75 | ||
|
|
eabf05758e | ||
|
|
8756b7a735 | ||
|
|
ec9f157512 | ||
|
|
7f58887ae3 | ||
|
|
d17d7eba14 | ||
|
|
4fb4991f5e | ||
|
|
0f8c46771e | ||
|
|
f5a8593b11 | ||
|
|
c2898f53bc | ||
|
|
089797dbe6 | ||
|
|
53c70b5f27 | ||
|
|
a66e6d4dff | ||
|
|
1fa4f58080 | ||
|
|
7fff9da71d | ||
|
|
f128f4cba1 | ||
|
|
6f0c966877 | ||
|
|
f10a260301 | ||
|
|
54bc8a7627 | ||
|
|
9824a451bb | ||
|
|
e0e897832c | ||
|
|
87472150bc | ||
|
|
45d318a773 | ||
|
|
4e1de06782 | ||
|
|
8735a85a30 | ||
|
|
e108954633 | ||
|
|
47cf257c40 | ||
|
|
cc56ebd90d | ||
|
|
89c82035d8 | ||
|
|
3ce5a68cd1 | ||
|
|
0704ca0550 | ||
|
|
ae7664597e | ||
|
|
e9e671078c | ||
|
|
71a56c3552 | ||
|
|
e7659883cc | ||
|
|
663c936428 | ||
|
|
56a558baf8 | ||
|
|
e6385e2ab7 | ||
|
|
5362f472ff | ||
|
|
41e79d902d | ||
|
|
c7bcebb628 | ||
|
|
a89afe1229 | ||
|
|
faecff570c | ||
|
|
828461c193 | ||
|
|
ec4a789b4f | ||
|
|
a65f6f07f3 | ||
|
|
00f71c3b9d | ||
|
|
3d2bf8fb02 | ||
|
|
391eec9ee7 | ||
|
|
0d91ef78dd | ||
|
|
7e18a1f1be | ||
|
|
7f4fc6f8a7 | ||
|
|
a2f13e479b | ||
|
|
fccf1e2eac | ||
|
|
480d5f2d51 | ||
|
|
a6170963b8 | ||
|
|
77325b92fb | ||
|
|
b55d7cd45a | ||
|
|
23f981c458 | ||
|
|
48d1bca9b8 | ||
|
|
a13cf0e3ce | ||
|
|
1c8614ac9a | ||
|
|
4558793caf | ||
|
|
583257f093 | ||
|
|
7cea688a1c | ||
|
|
062dd8dabc | ||
|
|
1d07a36552 | ||
|
|
a5d29fa1d4 | ||
|
|
9aabd911eb | ||
|
|
1f27bf6380 | ||
|
|
2d8eac4e0a | ||
|
|
833538cc90 | ||
|
|
80db8804c7 | ||
|
|
d9effbb179 | ||
|
|
24a0f55c9c | ||
|
|
f5e54cd398 | ||
|
|
c9317a16c5 | ||
|
|
dae6fe91a1 | ||
|
|
65fdc7ae50 | ||
|
|
00ebedad93 | ||
|
|
35bfffb556 | ||
|
|
8af332c9a7 | ||
|
|
7993909fab | ||
|
|
88b21a72f1 | ||
|
|
ea2fba877a | ||
|
|
5683bb76cc | ||
|
|
3a87fab6c8 | ||
|
|
23516acd0b | ||
|
|
48f7c5603e | ||
|
|
0b5b2b2633 | ||
|
|
1348d9aaf8 | ||
|
|
21f17e871e | ||
|
|
837cea6b4a | ||
|
|
b0b9732359 | ||
|
|
26c046a563 | ||
|
|
cf650fcaac | ||
|
|
3dd7d7867b | ||
|
|
31578303a4 | ||
|
|
06d197cdd0 | ||
|
|
11aab4198b | ||
|
|
8d387433b1 | ||
|
|
0f25fa7af6 | ||
|
|
8d55702d13 | ||
|
|
e54f5e544f | ||
|
|
289c3ff377 | ||
|
|
11f3f72f1c | ||
|
|
ad7c72c164 | ||
|
|
51294163bb | ||
|
|
598efbf7f9 | ||
|
|
b651bbf446 | ||
|
|
4801bdf45a | ||
|
|
25c1974e0d | ||
|
|
12896b22d8 | ||
|
|
e531c59606 | ||
|
|
8f652f4e31 | ||
|
|
97cb404822 | ||
|
|
d2a3bed240 | ||
|
|
e4583cb9b7 | ||
|
|
a684a91bf5 | ||
|
|
1175f48d05 | ||
|
|
54dccc480e | ||
|
|
f2cccf8da7 | ||
|
|
10be033791 | ||
|
|
4e39cdef94 | ||
|
|
2bba53b2c3 | ||
|
|
b9e886726c | ||
|
|
44fc888bd6 | ||
|
|
f2d1295fe6 | ||
|
|
04bd253390 | ||
|
|
7db751df3b | ||
|
|
f7311e0d97 | ||
|
|
633e23bd65 | ||
|
|
7c227d2a00 | ||
|
|
0a0fb11c21 | ||
|
|
c31b301722 | ||
|
|
5d0b18a0d0 | ||
|
|
258ae99491 | ||
|
|
10cf2f3edd | ||
|
|
0cd9c5b5be | ||
|
|
a8707158a5 | ||
|
|
ad1da994b2 | ||
|
|
ba6fbc417e | ||
|
|
ce199d6f9e | ||
|
|
0dd8e8c242 | ||
|
|
b9ee29a945 | ||
|
|
be05c9022d | ||
|
|
afb061c374 | ||
|
|
1ee37148a8 | ||
|
|
1c73902005 | ||
|
|
d387e9b6d3 | ||
|
|
163d3547e6 | ||
|
|
c61998bd20 | ||
|
|
1e4d6672be | ||
|
|
80812b86d6 | ||
|
|
128f6359e9 | ||
|
|
8c0331d244 |
@@ -3,10 +3,10 @@ IndentWidth: 4
|
||||
UseTab: Always
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Custom
|
||||
Standard: Cpp11
|
||||
Standard: c++17
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
@@ -20,7 +20,7 @@ IndentCaseLabels: false
|
||||
AccessModifierOffset: -4
|
||||
ColumnLimit: 90
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
SortIncludes: false
|
||||
SortIncludes: Never
|
||||
IncludeCategories:
|
||||
- Regex: '^".*'
|
||||
Priority: 2
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
end_of_line = lf
|
||||
|
||||
[*.{cpp,h,lua,txt,glsl,md,c,cmake,java,gradle}]
|
||||
charset = utf8
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
|
||||
6
.github/CONTRIBUTING.md
vendored
6
.github/CONTRIBUTING.md
vendored
@@ -19,7 +19,7 @@ Contributions are welcome! Here's how you can help:
|
||||
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
|
||||
[the roadmap](../doc/direction.md) will be closed within a month 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.
|
||||
@@ -30,7 +30,7 @@ Contributions are welcome! Here's how you can help:
|
||||
|
||||
3. Start coding!
|
||||
- Refer to the
|
||||
[Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt),
|
||||
[Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.md),
|
||||
[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
|
||||
@@ -138,7 +138,7 @@ These notes are for those who have push access Minetest (core developers / maint
|
||||
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 labelled as "Roadmap" and the goal stated by number in the description.
|
||||
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
|
||||
|
||||
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Unconfirmed bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
##### Minetest version
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
##### OS / Hardware
|
||||
<!-- General information about your hardware and operating system -->
|
||||
Operating system:
|
||||
CPU:
|
||||
|
||||
<!-- For graphical issues only -->
|
||||
GPU model:
|
||||
OpenGL version:
|
||||
|
||||
##### Summary
|
||||
<!-- Describe your problem here -->
|
||||
|
||||
##### Steps to reproduce
|
||||
<!-- For bug reports or build issues, explain how the problem happened -->
|
||||
91
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
91
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
labels: ["Unconfirmed bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please note the following:
|
||||
1. **Please update your Minetest Engine to the latest stable or dev version** before submitting bug reports. Make sure the bug is still reproducible on the latest version.
|
||||
2. This page is for reporting the bugs of **the engine itself**. For bugs in a particular game, please [search for the game in the ContentDB](https://content.minetest.net/packages/?type=game) and submit a bug report in their issue trackers.
|
||||
* For example, you can submit issues about the Minetest Game (the official game of Minetest) [in its own repository](https://github.com/minetest/minetest_game/issues).
|
||||
3. Please provide as many details as possible for us to spot the problem quicker.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Minetest version
|
||||
description: |
|
||||
Paste the Minetest version below.
|
||||
If you are on a devel version, please add a git commit hash.
|
||||
You can use `minetest --version` to find it.
|
||||
You can also refer to the "About" tab of the menu.
|
||||
placeholder: |
|
||||
Example:
|
||||
Minetest 5.7.0-dev-ca13c51 (Linux)
|
||||
Using Irrlicht 1.9.0mt9
|
||||
Using LuaJIT 2.1.0-beta3
|
||||
BUILD_TYPE=Release
|
||||
RUN_IN_PLACE=1
|
||||
USE_CURL=1
|
||||
USE_GETTEXT=1
|
||||
USE_SOUND=1
|
||||
STATIC_SHAREDIR="."
|
||||
STATIC_LOCALEDIR="locale"
|
||||
render: "true"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Active renderer
|
||||
description: For graphical and input-related issues. You can find these in the About tab in the mainmenu.
|
||||
placeholder: "Example: OpenGL 4.6.0"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Irrlicht device
|
||||
description:
|
||||
placeholder: "Example: X11"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Operating system and version
|
||||
description: It is recommended to upgrade your operating system to see if the problem still exists.
|
||||
placeholder: "Example: Ubuntu 22.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: CPU model
|
||||
description: Usually found in system settings.
|
||||
placeholder: "Example: Intel i5-2410M (4) @ 2.900GHz"
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: The GPU model and OpenGL version can be omitted if the bug is not a graphical issue.
|
||||
- type: input
|
||||
attributes:
|
||||
label: GPU model
|
||||
description: Usually found in system settings.
|
||||
placeholder: "Example: NVIDA GeForce RTX 4090"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: OpenGL version
|
||||
placeholder: "Example: 4.6"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe your problem here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Explain how the problem has happened, providing a minimal test (i.e. a code snippet reduced to the bone) where possible.
|
||||
validations:
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Submit issues about Minetest Game
|
||||
url: https://github.com/minetest/minetest_game/issues/new/choose
|
||||
about: Only submit issues of the engine in this repository's issue tracker. Submit those of Minetest Game in its own issue tracker.
|
||||
- name: Search for issue trackers of third-party games
|
||||
url: https://content.minetest.net/packages/?type=game
|
||||
about: For issues of third-party games, search for the game in the ContentDB and then submit an issue in their issue tracker.
|
||||
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: Feature request
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
A clear and concise description of what the problem is.
|
||||
ie: Why is this needed?
|
||||
Ex. I'm always frustrated when [...]
|
||||
|
||||
## Solutions
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
## Alternatives
|
||||
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
## Additional context
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
||||
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels: ["Feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please note the following:
|
||||
1. Only submit a feature request if the feature does not exist on the latest dev version.
|
||||
2. This page is for suggesting changes to **the engine itself**. To suggest changes to games, please [search for the game in the ContentDB](https://content.minetest.net/packages/?type=game) and submit a feature request in their issue trackers.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem
|
||||
description: |
|
||||
A clear and concise description of the problem, i.e. "Why is this needed?"
|
||||
Example: I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Solutions
|
||||
description: |
|
||||
A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: |
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: |
|
||||
Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,7 +3,7 @@ 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)?
|
||||
- Does this relate to a goal in [the roadmap](https://github.com/minetest/minetest/blob/master/doc/direction.md)?
|
||||
- If not a bug fix, why is this PR needed? What usecases does it solve?
|
||||
|
||||
## To do
|
||||
|
||||
27
.github/workflows/android.yml
vendored
27
.github/workflows/android.yml
vendored
@@ -23,20 +23,39 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- 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
|
||||
- name: Build with Gradle
|
||||
- name: Build AAB with Gradle
|
||||
# We build an AAB as well for uploading to the the Play Store.
|
||||
run: cd android; ./gradlew bundlerelease
|
||||
- name: Build APKs with Gradle
|
||||
# "assemblerelease" is very fast after "bundlerelease".
|
||||
run: cd android; ./gradlew assemblerelease
|
||||
- name: Save AAB artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Minetest-release.aab
|
||||
path: android/app/build/outputs/bundle/release/app-release.aab
|
||||
- name: Save armeabi artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-armeabi-v7a.apk
|
||||
path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk
|
||||
- name: Save arm64 artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-arm64-v8a.apk
|
||||
path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
|
||||
- name: Save x86 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-x86.apk
|
||||
path: android/app/build/outputs/apk/release/app-x86-release-unsigned.apk
|
||||
- name: Save x86_64 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Minetest-x86_64.apk
|
||||
path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk
|
||||
|
||||
142
.github/workflows/build.yml
vendored
142
.github/workflows/build.yml
vendored
@@ -29,110 +29,109 @@ on:
|
||||
- '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:
|
||||
runs-on: ubuntu-18.04
|
||||
gcc_7:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-5
|
||||
install_linux_deps g++-7
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-5
|
||||
CXX: g++-5
|
||||
CC: gcc-7
|
||||
CXX: g++-7
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
# Current gcc version
|
||||
gcc_10:
|
||||
runs-on: ubuntu-20.04
|
||||
gcc_12:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-10
|
||||
install_linux_deps g++-12 libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
CC: gcc-12
|
||||
CXX: g++-12
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
# Older clang version (should be close to our minimum supported version)
|
||||
clang_3_9:
|
||||
runs-on: ubuntu-18.04
|
||||
clang_7:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-3.9 gdb
|
||||
install_linux_deps clang-7 llvm
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-3.9
|
||||
CXX: clang++-3.9
|
||||
CC: clang-7
|
||||
CXX: clang++-7
|
||||
CMAKE_FLAGS: '-DCMAKE_C_FLAGS="-fsanitize=address" -DCMAKE_CXX_FLAGS="-fsanitize=address"'
|
||||
|
||||
- name: Unittest
|
||||
run: |
|
||||
./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
|
||||
|
||||
# Current clang version
|
||||
clang_10:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-10 valgrind libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-10
|
||||
CXX: clang++-10
|
||||
CMAKE_FLAGS: "-DREQUIRE_LUAJIT=1"
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
- name: Valgrind
|
||||
run: |
|
||||
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
|
||||
|
||||
# Build with prometheus-cpp (server-only)
|
||||
clang_9_prometheus:
|
||||
name: "clang_9 (PROMETHEUS=1)"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps --old-irr clang-9
|
||||
install_linux_deps clang-9
|
||||
|
||||
- name: Build prometheus-cpp
|
||||
run: |
|
||||
@@ -152,9 +151,9 @@ jobs:
|
||||
|
||||
docker:
|
||||
name: "Docker image"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build . -t minetest:latest
|
||||
@@ -162,50 +161,45 @@ jobs:
|
||||
|
||||
win32:
|
||||
name: "MinGW cross-compiler (32-bit)"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update -q && sudo apt-get install gettext -qyy
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh winbuild
|
||||
env:
|
||||
NO_MINETEST_GAME: 1
|
||||
NO_PACKAGE: 1
|
||||
|
||||
win64:
|
||||
name: "MinGW cross-compiler (64-bit)"
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update -q && sudo apt-get install gettext -qyy
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh winbuild
|
||||
env:
|
||||
NO_MINETEST_GAME: 1
|
||||
NO_PACKAGE: 1
|
||||
|
||||
msvc:
|
||||
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
runs-on: windows-2019
|
||||
#### Disabled due to Irrlicht switch
|
||||
if: false
|
||||
#### Disabled due to Irrlicht switch
|
||||
env:
|
||||
VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0
|
||||
# 2020.11
|
||||
vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit
|
||||
VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
|
||||
# 2023.10.19
|
||||
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -226,11 +220,15 @@ jobs:
|
||||
# Enable it, when working on the installer.
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout IrrlichtMt
|
||||
run: |
|
||||
$ref = @(Get-Content misc\irrlichtmt_tag.txt)
|
||||
git clone https://github.com/minetest/irrlicht lib\irrlichtmt --depth 1 -b $ref[0]
|
||||
|
||||
- name: Restore from cache and run vcpkg
|
||||
uses: lukka/run-vcpkg@v5
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: ${{env.vcpkg_packages}}
|
||||
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
|
||||
@@ -238,15 +236,17 @@ jobs:
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
|
||||
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
|
||||
|
||||
- name: CMake
|
||||
- name: Minetest CMake
|
||||
run: |
|
||||
cmake ${{matrix.config.generator}} `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
-DENABLE_POSTGRESQL=OFF `
|
||||
-DENABLE_LUAJIT=TRUE `
|
||||
-DREQUIRE_LUAJIT=TRUE `
|
||||
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
|
||||
|
||||
- name: Build
|
||||
- name: Build Minetest
|
||||
run: cmake --build . --config Release
|
||||
|
||||
- name: CPack
|
||||
@@ -265,7 +265,7 @@ jobs:
|
||||
- name: Package Clean
|
||||
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
|
||||
|
||||
- uses: actions/upload-artifact@v1
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
path: .\Package\
|
||||
|
||||
14
.github/workflows/cpp_lint.yml
vendored
14
.github/workflows/cpp_lint.yml
vendored
@@ -26,12 +26,13 @@ on:
|
||||
jobs:
|
||||
|
||||
# clang_format:
|
||||
# runs-on: ubuntu-18.04
|
||||
# runs-on: ubuntu-20.04
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/checkout@v3
|
||||
# - name: Install clang-format
|
||||
# run: |
|
||||
# sudo apt-get install clang-format-9 -qyy
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y clang-format-9
|
||||
#
|
||||
# - name: Run clang-format
|
||||
# run: |
|
||||
@@ -41,14 +42,13 @@ jobs:
|
||||
# CLANG_FORMAT: clang-format-9
|
||||
|
||||
clang_tidy:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get install clang-tidy-9 -qyy
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps
|
||||
install_linux_deps clang-tidy-9
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
|
||||
72
.github/workflows/lua.yml
vendored
Normal file
72
.github/workflows/lua.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: lua_lint
|
||||
|
||||
# Lint on lua changes on builtin or if workflow changed
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- 'games/devtest/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- 'games/devtest/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
jobs:
|
||||
# Note that the integration tests are also run build.yml, but only when C++ code is changed.
|
||||
integration_tests:
|
||||
name: "Compile and run multiplayer tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-10 gdb libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-10
|
||||
CXX: clang++-10
|
||||
CMAKE_FLAGS: "-DENABLE_GETTEXT=0 -DBUILD_SERVER=0"
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
luacheck:
|
||||
name: "Builtin Luacheck and Unit Tests"
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: leafo/gh-actions-lua@v9
|
||||
with:
|
||||
luaVersion: "5.1.5"
|
||||
- uses: leafo/gh-actions-luarocks@v4
|
||||
|
||||
- name: Install LuaJIT
|
||||
run: |
|
||||
cd $HOME
|
||||
git clone https://github.com/LuaJIT/LuaJIT/
|
||||
cd LuaJIT
|
||||
make -j$(nproc)
|
||||
|
||||
- name: Install luarocks tools
|
||||
run: |
|
||||
luarocks install --local luacheck
|
||||
luarocks install --local busted
|
||||
|
||||
- name: Run checks (builtin)
|
||||
run: |
|
||||
$HOME/.luarocks/bin/luacheck builtin
|
||||
$HOME/.luarocks/bin/busted builtin
|
||||
$HOME/.luarocks/bin/busted builtin --lua=$HOME/LuaJIT/src/luajit
|
||||
|
||||
- name: Run checks (devtest)
|
||||
run: |
|
||||
$HOME/.luarocks/bin/luacheck --config=games/devtest/.luacheckrc games/devtest
|
||||
48
.github/workflows/lua_api_deploy.yml
vendored
Normal file
48
.github/workflows/lua_api_deploy.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: lua_api_deploy
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/lua_api_deploy.yml'
|
||||
- 'doc/lua_api.md'
|
||||
- 'doc/mkdocs/'
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
- name: Install mkdocs
|
||||
run: |
|
||||
pip install -U -r doc/mkdocs/requirements.txt
|
||||
|
||||
- name: Build documentation
|
||||
run: |
|
||||
cd doc/mkdocs/
|
||||
./build.sh
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
with:
|
||||
path: 'public/'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
32
.github/workflows/lua_lint.yml
vendored
32
.github/workflows/lua_lint.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: lua_lint
|
||||
|
||||
# Lint on lua changes on builtin or if workflow changed
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'builtin/**.lua'
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
jobs:
|
||||
luacheck:
|
||||
name: "Builtin Luacheck and Unit Tests"
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install luarocks
|
||||
run: |
|
||||
sudo apt-get install luarocks -qyy
|
||||
|
||||
- name: Install luarocks tools
|
||||
run: |
|
||||
luarocks install --local luacheck
|
||||
luarocks install --local busted
|
||||
|
||||
- name: Run checks
|
||||
run: |
|
||||
$HOME/.luarocks/bin/luacheck builtin
|
||||
$HOME/.luarocks/bin/busted builtin
|
||||
42
.github/workflows/macos.yml
vendored
42
.github/workflows/macos.yml
vendored
@@ -21,46 +21,42 @@ on:
|
||||
- 'cmake/Modules/**'
|
||||
- '.github/workflows/macos.yml'
|
||||
|
||||
env:
|
||||
IRRLICHT_TAG: 1.9.0mt4
|
||||
MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git
|
||||
MINETEST_GAME_BRANCH: master
|
||||
MINETEST_GAME_NAME: minetest_game
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd)
|
||||
brew update
|
||||
brew install ${pkgs[@]}
|
||||
brew unlink $(brew ls --formula)
|
||||
brew link ${pkgs[@]}
|
||||
source ./util/ci/common.sh
|
||||
install_macos_deps
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME
|
||||
rm -rvf games/$MINETEST_GAME_NAME/.git
|
||||
git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
|
||||
mkdir cmakebuild
|
||||
cd cmakebuild
|
||||
git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE \
|
||||
-DENABLE_FREETYPE=TRUE -DENABLE_GETTEXT=TRUE
|
||||
make -j2
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE \
|
||||
-DINSTALL_DEVTEST=TRUE
|
||||
cmake --build . -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
# Zipping the built .app preserves permissions on the contained files,
|
||||
# which the GitHub artifact pipeline would otherwise strip away.
|
||||
- name: CPack
|
||||
run: |
|
||||
cd build
|
||||
cpack -G ZIP -B macos
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: minetest-macos
|
||||
path: ./build/macos/
|
||||
path: ./build/macos/*.zip
|
||||
|
||||
19
.gitignore
vendored
19
.gitignore
vendored
@@ -28,8 +28,17 @@ gtags.files
|
||||
# Visual Studio Code & plugins
|
||||
.vscode/
|
||||
build/.cmake/
|
||||
# Fleet
|
||||
.fleet
|
||||
# Gradle
|
||||
.gradle
|
||||
# Clang
|
||||
.cache
|
||||
# AppImage
|
||||
*.AppImage
|
||||
*.zsync
|
||||
appimage-build
|
||||
AppDir
|
||||
|
||||
## Files related to Minetest development cycle
|
||||
/*.patch
|
||||
@@ -41,17 +50,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/minetest/
|
||||
/mods/minetest/*
|
||||
!/mods/minetest/mods_here.txt
|
||||
/worlds
|
||||
/world/
|
||||
!/mods/mods_here.txt
|
||||
/worlds/*
|
||||
!/worlds/worlds_here.txt
|
||||
/clientmods/*
|
||||
!/clientmods/preview/
|
||||
/client/mod_storage/
|
||||
|
||||
189
.gitlab-ci.yml
189
.gitlab-ci.yml
@@ -9,21 +9,18 @@ stages:
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
IRRLICHT_TAG: "1.9.0mt4"
|
||||
MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git"
|
||||
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
|
||||
|
||||
.build_template:
|
||||
stage: build
|
||||
before_script:
|
||||
- apt-get update
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev
|
||||
- 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 -b $IRRLICHT_TAG lib/irrlichtmt
|
||||
- mkdir cmakebuild
|
||||
- cd cmakebuild
|
||||
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE ..
|
||||
- make -j2
|
||||
- git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
- mkdir build && cd build
|
||||
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE ..
|
||||
- make -j $(($(nproc) + 1))
|
||||
- make install
|
||||
artifacts:
|
||||
when: on_success
|
||||
@@ -31,164 +28,14 @@ variables:
|
||||
paths:
|
||||
- artifact/*
|
||||
|
||||
.debpkg_template:
|
||||
stage: package
|
||||
before_script:
|
||||
- apt-get update
|
||||
- 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/JPEG_PLACEHOLDER/'$JPEG_PKG'/g' build/deb/minetest/DEBIAN/control
|
||||
- sed -i 's/LEVELDB_PLACEHOLDER/'$LEVELDB_PKG'/g' build/deb/minetest/DEBIAN/control
|
||||
- sed -i 's/JSONCPP_PLACEHOLDER/'$JSONCPP_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 -qy
|
||||
script:
|
||||
- apt-get install -y ./*.deb
|
||||
- minetest --version
|
||||
|
||||
##
|
||||
## Debian
|
||||
## Ubuntu (prerequisite for AppImage build)
|
||||
##
|
||||
|
||||
# Stretch
|
||||
|
||||
build:debian-9:
|
||||
extends: .build_template
|
||||
image: debian:9
|
||||
|
||||
package:debian-9:
|
||||
extends: .debpkg_template
|
||||
image: debian:9
|
||||
needs:
|
||||
- build:debian-9
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1v5
|
||||
JPEG_PKG: libjpeg62-turbo
|
||||
|
||||
deploy:debian-9:
|
||||
extends: .debpkg_install
|
||||
image: debian:9
|
||||
needs:
|
||||
- package:debian-9
|
||||
|
||||
# Buster
|
||||
|
||||
build:debian-10:
|
||||
extends: .build_template
|
||||
image: debian:10
|
||||
|
||||
package:debian-10:
|
||||
extends: .debpkg_template
|
||||
image: debian:10
|
||||
needs:
|
||||
- build:debian-10
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1d
|
||||
JPEG_PKG: libjpeg62-turbo
|
||||
|
||||
deploy:debian-10:
|
||||
extends: .debpkg_install
|
||||
image: debian:10
|
||||
needs:
|
||||
- package:debian-10
|
||||
|
||||
# Bullseye
|
||||
|
||||
build:debian-11:
|
||||
extends: .build_template
|
||||
image: debian:11
|
||||
|
||||
package:debian-11:
|
||||
extends: .debpkg_template
|
||||
image: debian:11
|
||||
needs:
|
||||
- build:debian-11
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp24
|
||||
LEVELDB_PKG: libleveldb1d
|
||||
JPEG_PKG: libjpeg62-turbo
|
||||
|
||||
deploy:debian-11:
|
||||
extends: .debpkg_install
|
||||
image: debian:11
|
||||
needs:
|
||||
- package:debian-11
|
||||
|
||||
##
|
||||
## Ubuntu
|
||||
##
|
||||
|
||||
# Bionic
|
||||
|
||||
build:ubuntu-18.04:
|
||||
extends: .build_template
|
||||
image: ubuntu:bionic
|
||||
|
||||
package:ubuntu-18.04:
|
||||
extends: .debpkg_template
|
||||
image: ubuntu:bionic
|
||||
needs:
|
||||
- build:ubuntu-18.04
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1v5
|
||||
JPEG_PKG: libjpeg-turbo8
|
||||
|
||||
deploy:ubuntu-18.04:
|
||||
extends: .debpkg_install
|
||||
image: ubuntu:bionic
|
||||
needs:
|
||||
- package:ubuntu-18.04
|
||||
|
||||
# Focal
|
||||
|
||||
build:ubuntu-20.04:
|
||||
extends: .build_template
|
||||
image: ubuntu:focal
|
||||
|
||||
package:ubuntu-20.04:
|
||||
extends: .debpkg_template
|
||||
image: ubuntu:focal
|
||||
needs:
|
||||
- build:ubuntu-20.04
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1d
|
||||
JPEG_PKG: libjpeg-turbo8
|
||||
|
||||
deploy:ubuntu-20.04:
|
||||
extends: .debpkg_install
|
||||
image: ubuntu:focal
|
||||
needs:
|
||||
- package:ubuntu-20.04
|
||||
|
||||
##
|
||||
## Fedora
|
||||
##
|
||||
|
||||
# Fedora 28 <-> RHEL 8
|
||||
build:fedora-28:
|
||||
extends: .build_template
|
||||
image: fedora:28
|
||||
before_script:
|
||||
- dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
|
||||
|
||||
##
|
||||
## MinGW for Windows
|
||||
##
|
||||
@@ -198,7 +45,7 @@ build:fedora-28:
|
||||
before_script:
|
||||
- apt-get update
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext
|
||||
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
|
||||
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
- tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
.build_win_template:
|
||||
@@ -237,10 +84,7 @@ package:docker:
|
||||
before_script:
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
|
||||
script:
|
||||
- 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
|
||||
- ./util/ci/docker.sh
|
||||
|
||||
##
|
||||
## Gitlab Pages (Lua API documentation)
|
||||
@@ -250,9 +94,7 @@ pages:
|
||||
stage: deploy
|
||||
image: python:3.8
|
||||
before_script:
|
||||
- pip install git+https://github.com/Python-Markdown/markdown.git
|
||||
- pip install git+https://github.com/mkdocs/mkdocs.git
|
||||
- pip install pygments
|
||||
- pip install -U -r doc/mkdocs/requirements.txt
|
||||
script:
|
||||
- cd doc/mkdocs && ./build.sh
|
||||
artifacts:
|
||||
@@ -269,22 +111,19 @@ package:appimage-client:
|
||||
stage: package
|
||||
image: appimagecrafters/appimage-builder
|
||||
needs:
|
||||
- build:ubuntu-18.04
|
||||
- build:ubuntu-20.04
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get update
|
||||
- apt-get install -y git
|
||||
# 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/.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
|
||||
- appimage-builder --skip-test
|
||||
script:
|
||||
- export VERSION=$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
|
||||
- appimage-builder --skip-test --recipe misc/AppImageBuilder.yml
|
||||
artifacts:
|
||||
expire_in: 90 day
|
||||
paths:
|
||||
|
||||
@@ -70,6 +70,7 @@ files["builtin/mainmenu"] = {
|
||||
|
||||
read_globals = {
|
||||
"PLATFORM",
|
||||
"TOUCHSCREEN_GUI",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -80,3 +81,9 @@ files["builtin/common/tests"] = {
|
||||
"assert",
|
||||
},
|
||||
}
|
||||
|
||||
files["builtin/fstk"] = {
|
||||
read_globals = {
|
||||
"TOUCHSCREEN_GUI",
|
||||
},
|
||||
}
|
||||
|
||||
17
.mailmap
17
.mailmap
@@ -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>
|
||||
srfqi <muhammadrifqipriyosusanto@gmail.com>
|
||||
srifqi <muhammadrifqipriyosusanto@gmail.com>
|
||||
Dániel Juhász <juhdanad@gmail.com>
|
||||
rubenwardy <rw@rubenwardy.com>
|
||||
rubenwardy <rw@rubenwardy.com> <rubenwardy@gmail.com>
|
||||
@@ -48,15 +48,17 @@ 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>
|
||||
Desour <vorunbekannt75@web.de>
|
||||
Nathanaël Courant <Ekdohibs@users.noreply.github.com> <nathanael.courant@laposte.net>
|
||||
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>
|
||||
Ezhh <owlecho@live.com>
|
||||
paramat <paramat@users.noreply.github.com>
|
||||
paramat <paramat@users.noreply.github.com> <mat.gregory@virginmedia.com>
|
||||
lhofhansl <lhofhansl@yahoo.com> <larsh@apache.org>
|
||||
red-001 <red-001@outlook.ie> <red-001@openmailbox.org>
|
||||
Wuzzy <wuzzy2@mail.ru> <Wuzzy2@mail.ru>
|
||||
Wuzzy <wuzzy2@mail.ru> <almikes@aol.com>
|
||||
Wuzzy <Wuzzy@disroot.org> <wuzzy2@mail.ru>
|
||||
Wuzzy <Wuzzy@disroot.org> <Wuzzy2@mail.ru>
|
||||
Wuzzy <Wuzzy@disroot.org> <almikes@aol.com>
|
||||
Jordach <jordach.snelling@gmail.com>
|
||||
MoNTE48 <MoNTE48@mail.ua>
|
||||
v-rob <robinsonvincent89@gmail.com>
|
||||
@@ -65,3 +67,8 @@ 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>
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
version: 1
|
||||
|
||||
AppDir:
|
||||
path: ./AppDir
|
||||
|
||||
app_info:
|
||||
id: minetest
|
||||
name: Minetest
|
||||
icon: minetest
|
||||
version: !ENV ${VERSION}
|
||||
exec: usr/bin/minetest
|
||||
exec_args: $@
|
||||
runtime:
|
||||
env:
|
||||
APPDIR_LIBRARY_PATH: $APPDIR/usr/lib/x86_64-linux-gnu
|
||||
|
||||
apt:
|
||||
arch: amd64
|
||||
sources:
|
||||
- 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/ 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
|
||||
- libcurl3-gnutls
|
||||
- libfreetype6
|
||||
- libgl1
|
||||
- libjpeg-turbo8
|
||||
- libjsoncpp1
|
||||
- libleveldb1v5
|
||||
- libopenal1
|
||||
- libpng16-16
|
||||
- libsqlite3-0
|
||||
- libstdc++6
|
||||
- libvorbisfile3
|
||||
- libx11-6
|
||||
- libxxf86vm1
|
||||
- zlib1g
|
||||
|
||||
files:
|
||||
exclude:
|
||||
- usr/share/man
|
||||
- usr/share/doc/*/README.*
|
||||
- usr/share/doc/*/changelog.*
|
||||
- usr/share/doc/*/NEWS.*
|
||||
- usr/share/doc/*/TODO.*
|
||||
|
||||
AppImage:
|
||||
update-information: None
|
||||
sign-key: None
|
||||
arch: x86_64
|
||||
182
CMakeLists.txt
182
CMakeLists.txt
@@ -1,23 +1,17 @@
|
||||
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_minimum_required(VERSION 3.12)
|
||||
|
||||
# This can be read from ${PROJECT_NAME} after project() is called
|
||||
project(minetest)
|
||||
set(PROJECT_NAME_CAPITALIZED "Minetest")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(GCC_MINIMUM_VERSION "5.1")
|
||||
set(CLANG_MINIMUM_VERSION "3.5")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(GCC_MINIMUM_VERSION "7.5")
|
||||
set(CLANG_MINIMUM_VERSION "7.0.1")
|
||||
|
||||
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
|
||||
# You should not need to edit these manually, use util/bump_version.sh
|
||||
set(VERSION_MAJOR 5)
|
||||
set(VERSION_MINOR 5)
|
||||
set(VERSION_MINOR 8)
|
||||
set(VERSION_PATCH 1)
|
||||
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
|
||||
|
||||
@@ -36,10 +30,20 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set(VERSION_STRING "${VERSION_STRING}-debug")
|
||||
endif()
|
||||
|
||||
message(STATUS "*** Will build version ${VERSION_STRING} ***")
|
||||
|
||||
|
||||
# Configuration options
|
||||
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(BUILD_DOCUMENTATION TRUE CACHE BOOL "Build documentation")
|
||||
|
||||
set(DEFAULT_ENABLE_LTO TRUE)
|
||||
# by default don't enable on Debug builds to get faster builds
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(DEFAULT_ENABLE_LTO FALSE)
|
||||
endif()
|
||||
set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization")
|
||||
|
||||
set(DEFAULT_RUN_IN_PLACE FALSE)
|
||||
if(WIN32)
|
||||
set(DEFAULT_RUN_IN_PLACE TRUE)
|
||||
@@ -47,10 +51,13 @@ endif()
|
||||
set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL
|
||||
"Run directly in source directory structure")
|
||||
|
||||
|
||||
set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
|
||||
set(BUILD_SERVER FALSE CACHE BOOL "Build server")
|
||||
set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
|
||||
message(STATUS "*** Will build version ${VERSION_STRING} ***")
|
||||
message(STATUS "BUILD_CLIENT: " ${BUILD_CLIENT})
|
||||
message(STATUS "BUILD_SERVER: " ${BUILD_SERVER})
|
||||
message(STATUS "BUILD_UNITTESTS: " ${BUILD_UNITTESTS})
|
||||
message(STATUS "BUILD_BENCHMARKS: " ${BUILD_BENCHMARKS})
|
||||
message(STATUS "BUILD_DOCUMENTATION: " ${BUILD_DOCUMENTATION})
|
||||
message(STATUS "RUN_IN_PLACE: " ${RUN_IN_PLACE})
|
||||
|
||||
set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
|
||||
|
||||
@@ -59,16 +66,41 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Debug or Release" FORCE)
|
||||
endif()
|
||||
|
||||
# FIXME: Windows build fails in multiple places to link, needs to be investigated.
|
||||
if (ENABLE_LTO AND NOT WIN32)
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT lto_supported OUTPUT lto_output)
|
||||
if(lto_supported)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
message(STATUS "LTO/IPO is enabled")
|
||||
else()
|
||||
message(STATUS "LTO/IPO requested but it is not supported by the compiler: ${lto_output}")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "LTO/IPO is not enabled")
|
||||
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/")
|
||||
|
||||
# Load default options for Android
|
||||
if(ANDROID)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
include(MinetestAndroidLibs)
|
||||
endif()
|
||||
|
||||
|
||||
set(IRRLICHTMT_BUILD_DIR "" CACHE PATH "Path to IrrlichtMt build directory.")
|
||||
if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
|
||||
if(ANDROID)
|
||||
# currently manually provided
|
||||
elseif(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
|
||||
find_package(IrrlichtMt QUIET
|
||||
PATHS "${IRRLICHTMT_BUILD_DIR}"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
)
|
||||
|
||||
if(NOT TARGET IrrlichtMt::IrrlichtMt)
|
||||
# find_package() searches certain subdirectories. ${PATH}/cmake is not
|
||||
@@ -76,7 +108,6 @@ if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
|
||||
# IrrlichtMtConfig.cmake
|
||||
message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
|
||||
endif()
|
||||
# This is done here so that relative search paths are more reasonable
|
||||
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
|
||||
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
|
||||
if(BUILD_CLIENT)
|
||||
@@ -106,9 +137,9 @@ else()
|
||||
|
||||
include(MinetestFindIrrlichtHeaders)
|
||||
if(NOT IRRLICHT_INCLUDE_DIR)
|
||||
message(FATAL_ERROR "Irrlicht or IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
|
||||
message(FATAL_ERROR "IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
|
||||
endif()
|
||||
message(STATUS "Found Irrlicht headers: ${IRRLICHT_INCLUDE_DIR}")
|
||||
message(STATUS "Found IrrlichtMt headers: ${IRRLICHT_INCLUDE_DIR}")
|
||||
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
|
||||
# Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
|
||||
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
|
||||
@@ -116,10 +147,35 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TARGET IrrlichtMt::IrrlichtMt)
|
||||
if(ANDROID)
|
||||
# skipped for now
|
||||
elseif(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.0mt13)
|
||||
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
|
||||
|
||||
@@ -220,10 +276,12 @@ if(RUN_IN_PLACE)
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/textures/texture_packs_here.txt" DESTINATION "${SHAREDIR}/textures")
|
||||
endif()
|
||||
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/minetest_game" DESTINATION "${SHAREDIR}/games/"
|
||||
COMPONENT "SUBGAME_MINETEST_GAME" OPTIONAL PATTERN ".git*" EXCLUDE )
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/devtest" DESTINATION "${SHAREDIR}/games/"
|
||||
COMPONENT "SUBGAME_MINIMAL" 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()
|
||||
|
||||
if(BUILD_CLIENT)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/shaders" DESTINATION "${SHAREDIR}/client")
|
||||
@@ -235,11 +293,11 @@ if(BUILD_CLIENT)
|
||||
endif()
|
||||
|
||||
install(FILES "README.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/client_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/menu_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/texture_packs.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/world_format.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/lua_api.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/client_lua_api.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/menu_lua_api.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/texture_packs.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/world_format.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}")
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
@@ -262,28 +320,17 @@ find_package(GMP REQUIRED)
|
||||
find_package(Json REQUIRED)
|
||||
find_package(Lua REQUIRED)
|
||||
if(NOT USE_LUAJIT)
|
||||
set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop)
|
||||
set(LUA_BIT_LIBRARY bitop)
|
||||
add_subdirectory(lib/bitop)
|
||||
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()
|
||||
if(BUILD_BENCHMARKS)
|
||||
add_subdirectory(lib/catch2)
|
||||
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.")
|
||||
@@ -300,25 +347,8 @@ cpack_add_component(Docs
|
||||
DESCRIPTION "Documentation about Minetest and Minetest modding"
|
||||
)
|
||||
|
||||
cpack_add_component(SUBGAME_MINETEST_GAME
|
||||
DISPLAY_NAME "Minetest Game"
|
||||
DESCRIPTION "The default game bundled in the Minetest engine. Mainly used as a modding base."
|
||||
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 libaries such as MSVCRxxx.dll
|
||||
# Include all dynamically linked runtime libraries such as MSVCRxxx.dll
|
||||
include(InstallRequiredSystemLibraries)
|
||||
|
||||
if(RUN_IN_PLACE)
|
||||
@@ -370,13 +400,15 @@ include(CPack)
|
||||
|
||||
|
||||
# Add a target to generate API documentation with Doxygen
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY)
|
||||
add_custom_target(doc
|
||||
${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
|
||||
COMMENT "Generating API documentation with Doxygen" VERBATIM
|
||||
)
|
||||
if(BUILD_DOCUMENTATION)
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY)
|
||||
add_custom_target(doc
|
||||
${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
|
||||
COMMENT "Generating API documentation with Doxygen" VERBATIM
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
502
COPYING.LESSER
Normal file
502
COPYING.LESSER
Normal file
@@ -0,0 +1,502 @@
|
||||
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!
|
||||
80
Dockerfile
80
Dockerfile
@@ -1,8 +1,40 @@
|
||||
ARG DOCKER_IMAGE=alpine:3.14
|
||||
FROM $DOCKER_IMAGE AS builder
|
||||
ARG DOCKER_IMAGE=alpine:3.16
|
||||
FROM $DOCKER_IMAGE AS dev
|
||||
|
||||
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
|
||||
|
||||
FROM dev as builder
|
||||
|
||||
COPY .git /usr/src/minetest/.git
|
||||
COPY CMakeLists.txt /usr/src/minetest/CMakeLists.txt
|
||||
@@ -19,31 +51,7 @@ COPY src /usr/src/minetest/src
|
||||
COPY textures /usr/src/minetest/textures
|
||||
|
||||
WORKDIR /usr/src/minetest
|
||||
|
||||
RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-dev \
|
||||
gmp-dev jsoncpp-dev postgresql-dev ninja 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
|
||||
|
||||
WORKDIR /usr/src/
|
||||
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ -b v1.0.0 && \
|
||||
mkdir prometheus-cpp/build && \
|
||||
cd prometheus-cpp/build && \
|
||||
cmake .. \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_TESTING=0 \
|
||||
-GNinja && \
|
||||
ninja && \
|
||||
ninja install
|
||||
|
||||
RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
|
||||
cp -r irrlicht/include /usr/include/irrlichtmt
|
||||
|
||||
WORKDIR /usr/src/minetest
|
||||
RUN mkdir build && \
|
||||
cd build && \
|
||||
cmake .. \
|
||||
RUN cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SERVER=TRUE \
|
||||
@@ -51,13 +59,14 @@ RUN mkdir build && \
|
||||
-DBUILD_UNITTESTS=FALSE \
|
||||
-DBUILD_CLIENT=FALSE \
|
||||
-GNinja && \
|
||||
ninja && \
|
||||
ninja install
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
ARG DOCKER_IMAGE=alpine:3.14
|
||||
ARG DOCKER_IMAGE=alpine:3.16
|
||||
FROM $DOCKER_IMAGE AS runtime
|
||||
|
||||
RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq luajit jsoncpp zstd-libs && \
|
||||
RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \
|
||||
sqlite-libs postgresql hiredis leveldb && \
|
||||
adduser -D minetest --uid 30000 -h /var/lib/minetest && \
|
||||
chown -R minetest:minetest /var/lib/minetest
|
||||
|
||||
@@ -66,9 +75,12 @@ 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/
|
||||
USER minetest:minetest
|
||||
|
||||
EXPOSE 30000/udp 30000/tcp
|
||||
VOLUME /var/lib/minetest/ /etc/minetest/
|
||||
|
||||
CMD ["/usr/local/bin/minetestserver", "--config", "/etc/minetest/minetest.conf"]
|
||||
ENTRYPOINT ["/usr/local/bin/minetestserver"]
|
||||
CMD ["--config", "/etc/minetest/minetest.conf"]
|
||||
|
||||
30
LICENSE.txt
30
LICENSE.txt
@@ -14,6 +14,9 @@ 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/
|
||||
|
||||
@@ -55,15 +58,40 @@ 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_update_cropped.png
|
||||
textures/base/pack/cdb_viewonline.png
|
||||
textures/base/pack/settings_btn.png
|
||||
textures/base/pack/settings_info.png
|
||||
textures/base/pack/settings_reset.png
|
||||
|
||||
appgurueu:
|
||||
textures/base/pack/server_incompatible.png
|
||||
|
||||
erlehmann, Warr1024, rollerozxa:
|
||||
textures/base/pack/no_screenshot.png
|
||||
|
||||
kilbith:
|
||||
textures/base/pack/server_favorite.png
|
||||
textures/base/pack/progress_bar.png
|
||||
textures/base/pack/progress_bar_bg.png
|
||||
|
||||
SmallJoker:
|
||||
textures/base/pack/cdb_clear.png
|
||||
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
|
||||
|
||||
DS:
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_bigfoot.png
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_jukebox.png
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_racecar.png
|
||||
games/devtest/mods/soundstuff/sounds/soundstuff_sinus.ogg
|
||||
games/devtest/mods/testtools/textures/testtools_branding_iron.png
|
||||
|
||||
grorp:
|
||||
textures/base/pack/exit_btn.png
|
||||
|
||||
License of Minetest source code
|
||||
-------------------------------
|
||||
|
||||
|
||||
336
README.md
336
README.md
@@ -7,15 +7,9 @@ Minetest
|
||||
|
||||
Minetest is a free open-source voxel game engine with easy modding and game creation.
|
||||
|
||||
Copyright (C) 2010-2020 Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2010-2022 Perttu Ahola <celeron55@gmail.com>
|
||||
and contributors (see source file comments and the version control log)
|
||||
|
||||
In case you downloaded the source code
|
||||
--------------------------------------
|
||||
If you downloaded the Minetest Engine source code in which this file is
|
||||
contained, you probably want to download the [Minetest Game](https://github.com/minetest/minetest_game/)
|
||||
project too. See its README.txt for more information.
|
||||
|
||||
Table of Contents
|
||||
------------------
|
||||
|
||||
@@ -51,7 +45,7 @@ Some can be changed in the key config dialog in the settings tab.
|
||||
| Shift | Sneak/move down |
|
||||
| Q | Drop itemstack |
|
||||
| Shift + Q | Drop single item |
|
||||
| Left mouse button | Dig/punch/take item |
|
||||
| Left mouse button | Dig/punch/use |
|
||||
| Right mouse button | Place/use |
|
||||
| Shift + right mouse button | Build (without using) |
|
||||
| I | Inventory menu |
|
||||
@@ -61,11 +55,9 @@ 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) |
|
||||
@@ -98,15 +90,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`
|
||||
* `user` = `%APPDATA%\Minetest` or `%MINETEST_USER_PATH%`
|
||||
* Linux installed:
|
||||
* `bin` = `/usr/bin`
|
||||
* `share` = `/usr/share/minetest`
|
||||
* `user` = `~/.minetest`
|
||||
* `user` = `~/.minetest` or `$MINETEST_USER_PATH`
|
||||
* macOS:
|
||||
* `bin` = `Contents/MacOS`
|
||||
* `share` = `Contents/Resources`
|
||||
* `user` = `Contents/User OR ~/Library/Application Support/minetest`
|
||||
* `user` = `Contents/User` or `~/Library/Application Support/minetest` or `$MINETEST_USER_PATH`
|
||||
|
||||
Worlds can be found as separate folders in: `user/worlds/`
|
||||
|
||||
@@ -126,322 +118,16 @@ Command-line options
|
||||
|
||||
Compiling
|
||||
---------
|
||||
### Compiling on GNU/Linux
|
||||
|
||||
#### Dependencies
|
||||
|
||||
| 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+ | |
|
||||
| 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 libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
|
||||
|
||||
For Fedora users:
|
||||
|
||||
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
|
||||
|
||||
For Arch users:
|
||||
|
||||
sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
|
||||
|
||||
For Alpine users:
|
||||
|
||||
sudo apk add build-base cmake libpng-dev jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev
|
||||
|
||||
#### Download
|
||||
|
||||
You can install Git for easily keeping your copy up to date.
|
||||
If you don’t want Git, read below on how to get the source without Git.
|
||||
This is an example for installing Git on Debian/Ubuntu:
|
||||
|
||||
sudo apt install git
|
||||
|
||||
For Fedora users:
|
||||
|
||||
sudo dnf install 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:
|
||||
|
||||
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:
|
||||
|
||||
cd games/
|
||||
wget https://github.com/minetest/minetest_game/archive/master.tar.gz
|
||||
tar xf master.tar.gz
|
||||
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:
|
||||
|
||||
cmake . -DRUN_IN_PLACE=TRUE
|
||||
make -j$(nproc)
|
||||
|
||||
Run it:
|
||||
|
||||
./bin/minetest
|
||||
|
||||
- Use `cmake . -LH` to see all CMake options and their current state.
|
||||
- If you want to install it system-wide (or are making a distribution package),
|
||||
you will want to use `-DRUN_IN_PLACE=FALSE`.
|
||||
- You can build a bare server by specifying `-DBUILD_SERVER=TRUE`.
|
||||
- You can disable the client build by specifying `-DBUILD_CLIENT=FALSE`.
|
||||
- You can select between Release and Debug build by `-DCMAKE_BUILD_TYPE=<Debug or Release>`.
|
||||
- Debug build is slower, but gives much more useful output in a debugger.
|
||||
- If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed.
|
||||
- In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`.
|
||||
|
||||
- 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`)
|
||||
|
||||
### CMake options
|
||||
|
||||
General options and their default values:
|
||||
|
||||
BUILD_CLIENT=TRUE - Build Minetest client
|
||||
BUILD_SERVER=FALSE - Build Minetest server
|
||||
BUILD_UNITTESTS=TRUE - Build unittest sources
|
||||
CMAKE_BUILD_TYPE=Release - Type of build (Release vs. Debug)
|
||||
Release - Release build
|
||||
Debug - Debug build
|
||||
SemiDebug - Partially optimized debug build
|
||||
RelWithDebInfo - Release build with debug information
|
||||
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_GETTEXT=ON - Build with Gettext; Allows using translations
|
||||
ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by IrrlichtMt)
|
||||
ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend
|
||||
ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
|
||||
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
|
||||
ENABLE_SPATIAL=ON - Build with LibSpatial; Speeds up AreaStores
|
||||
ENABLE_SOUND=ON - Build with OpenAL, libogg & libvorbis; in-game sounds
|
||||
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
|
||||
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)
|
||||
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:
|
||||
|
||||
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 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
|
||||
IRRLICHT_DLL - Only on Windows; path to IrrlichtMt.dll
|
||||
IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h (usable for server build only)
|
||||
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
|
||||
PostgreSQL_INCLUDE_DIR - Only when building with PostgreSQL; directory that contains libpq-fe.h
|
||||
PostgreSQL_LIBRARY - Only when building with PostgreSQL; path to libpq.a/libpq.so/libpq.lib
|
||||
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_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
|
||||
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_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
|
||||
VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs
|
||||
VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
|
||||
VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
|
||||
XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so
|
||||
ZLIB_DLL - Only on Windows; path to zlib1.dll
|
||||
ZLIB_INCLUDE_DIR - Directory that contains zlib.h
|
||||
ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib
|
||||
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
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Visual Studio 2015 or newer](https://visualstudio.microsoft.com)
|
||||
- [CMake](https://cmake.org/download/)
|
||||
- [vcpkg](https://github.com/Microsoft/vcpkg)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
### Compiling and installing the dependencies
|
||||
|
||||
It is highly recommended to use vcpkg as package manager.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
- **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.
|
||||
- `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
|
||||
|
||||
There are other optional libraries, but they are not tested if they can build and link correctly.
|
||||
|
||||
Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-windows`.
|
||||
|
||||
### Compile Minetest
|
||||
|
||||
#### a) Using the vcpkg toolchain and CMake GUI
|
||||
1. Start up the CMake GUI
|
||||
2. Select **Browse Source...** and select DIR/minetest
|
||||
3. Select **Browse Build...** and select DIR/minetest-build
|
||||
4. Select **Configure**
|
||||
5. Choose the right visual Studio version and target platform. It has to match the version of the installed dependencies
|
||||
6. Choose **Specify toolchain file for cross-compiling**
|
||||
7. Click **Next**
|
||||
8. Select the vcpkg toolchain file e.g. `D:/vcpkg/scripts/buildsystems/vcpkg.cmake`
|
||||
9. Click Finish
|
||||
10. Wait until cmake have generated the cash file
|
||||
11. If there are any errors, solve them and hit **Configure**
|
||||
12. Click **Generate**
|
||||
13. Click **Open Project**
|
||||
14. Compile Minetest inside Visual studio.
|
||||
|
||||
#### b) Using the vcpkg toolchain and the commandline
|
||||
|
||||
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 --build . --config Release
|
||||
```
|
||||
Make sure that the right compiler is selected and the path to the vcpkg toolchain is correct.
|
||||
|
||||
### Windows Installer using WiX Toolset
|
||||
|
||||
Requirements:
|
||||
* [Visual Studio 2017](https://visualstudio.microsoft.com/)
|
||||
* [WiX Toolset](https://wixtoolset.org/)
|
||||
|
||||
In the Visual Studio 2017 Installer select **Optional Features -> WiX Toolset**.
|
||||
|
||||
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 cmakebuild
|
||||
cd cmakebuild
|
||||
|
||||
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$(nproc)
|
||||
make install
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
```
|
||||
open ./build/macos/minetest.app
|
||||
```
|
||||
- [Compiling on GNU/Linux](doc/compiling/linux.md)
|
||||
- [Compiling on Windows](doc/compiling/windows.md)
|
||||
- [Compiling on MacOS](doc/compiling/macos.md)
|
||||
|
||||
Docker
|
||||
------
|
||||
|
||||
- [Developing minetestserver with Docker](doc/developing/docker.md)
|
||||
|
||||
We provide Minetest server Docker images using the GitLab mirror registry.
|
||||
|
||||
Images are built on each commit and available using the following tag scheme:
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.3'
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion '33.0.2'
|
||||
ndkVersion "$ndk_version"
|
||||
defaultConfig {
|
||||
applicationId 'net.minetest.minetest'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||
versionCode project.versionCode
|
||||
}
|
||||
@@ -40,7 +41,7 @@ android {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'armeabi-v7a', 'arm64-v8a'
|
||||
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,51 +53,60 @@ android {
|
||||
|
||||
task prepareAssets() {
|
||||
def assetsFolder = "build/assets"
|
||||
def projRoot = "../.."
|
||||
def gameToCopy = "minetest_game"
|
||||
def projRoot = rootDir.parent
|
||||
|
||||
copy {
|
||||
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
|
||||
// See issue #4638
|
||||
def unsupportedLanguages = new File("${projRoot}/src/unsupported_language_list.txt").text.readLines()
|
||||
|
||||
doFirst {
|
||||
logger.lifecycle('Preparing assets at {}', 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}"
|
||||
}
|
||||
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
|
||||
doLast {
|
||||
copy {
|
||||
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
|
||||
}
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/textures" into "${assetsFolder}/textures"
|
||||
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}/textures/base/pack" into "${assetsFolder}/textures/base/pack"
|
||||
}
|
||||
|
||||
// compile translations
|
||||
fileTree("${projRoot}/po").include("**/*.po").grep {
|
||||
it.parentFile.name !in unsupportedLanguages
|
||||
}.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 = ""
|
||||
}
|
||||
|
||||
file("${assetsFolder}/.nomedia").text = "";
|
||||
|
||||
task zipAssets(type: Zip) {
|
||||
archiveName "Minetest.zip"
|
||||
from "${assetsFolder}"
|
||||
destinationDir file("src/main/assets")
|
||||
task zipAssets(dependsOn: prepareAssets, type: Zip) {
|
||||
archiveFileName = "Minetest.zip"
|
||||
from assetsFolder
|
||||
destinationDirectory = 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
|
||||
@@ -112,5 +122,5 @@ android.applicationVariants.all { variant ->
|
||||
|
||||
dependencies {
|
||||
implementation project(':native')
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
}
|
||||
|
||||
@@ -5,19 +5,13 @@
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<!--
|
||||
`android:requestLegacyExternalStorage="true"` is workaround for using `/sdcard`
|
||||
instead of the `getFilesDir()` patch for assets. Check link below for more information:
|
||||
https://developer.android.com/training/data-storage/compatibility
|
||||
-->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-feature android:glEsVersion="0x00020000" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/label"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:resizeableActivity="false"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
@@ -52,13 +46,24 @@
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="Minetest" />
|
||||
android:value="minetest" />
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".UnzipService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="net.minetest.minetest.fileprovider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
Minetest
|
||||
Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik <MoNTE48@mail.ua>
|
||||
Copyright (C) 2014-2020 ubulem, Bektur Mambetov <berkut87@gmail.com>
|
||||
Copyright (C) 2023 srifqi, Muhammad Rifqi Priyo Susanto
|
||||
<muhammadrifqipriyosusanto@gmail.com>
|
||||
|
||||
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
|
||||
@@ -29,17 +31,52 @@ import androidx.appcompat.widget.AppCompatEditText;
|
||||
import java.util.Objects;
|
||||
|
||||
public class CustomEditText extends AppCompatEditText {
|
||||
private int editType = 2; // single line text input as default
|
||||
private boolean wantsToShowKeyboard = false;
|
||||
|
||||
public CustomEditText(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CustomEditText(Context context, int _editType) {
|
||||
super(context);
|
||||
editType = _editType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// For multi-line, do not close the dialog after pressing back button
|
||||
if (editType != 1 && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
InputMethodManager mgr = (InputMethodManager)
|
||||
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
Objects.requireNonNull(mgr).hideSoftInputFromWindow(this.getWindowToken(), 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
super.onWindowFocusChanged(hasWindowFocus);
|
||||
tryShowKeyboard();
|
||||
}
|
||||
|
||||
public void requestFocusTryShow() {
|
||||
requestFocus();
|
||||
wantsToShowKeyboard = true;
|
||||
tryShowKeyboard();
|
||||
}
|
||||
|
||||
private void tryShowKeyboard() {
|
||||
if (hasWindowFocus() && wantsToShowKeyboard) {
|
||||
if (isFocused()) {
|
||||
CustomEditText that = this;
|
||||
post(() -> {
|
||||
final InputMethodManager imm = (InputMethodManager)
|
||||
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(that, 0);
|
||||
});
|
||||
}
|
||||
wantsToShowKeyboard = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,32 +23,37 @@ package net.minetest.minetest;
|
||||
import android.app.NativeActivity;
|
||||
import android.content.Intent;
|
||||
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.Locale;
|
||||
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");
|
||||
System.loadLibrary("Minetest");
|
||||
System.loadLibrary("minetest");
|
||||
}
|
||||
|
||||
private int messageReturnCode = -1;
|
||||
private String messageReturnValue = "";
|
||||
|
||||
public static native void putMessageBoxResult(String text);
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -56,11 +61,10 @@ public class GameActivity extends NativeActivity {
|
||||
}
|
||||
|
||||
private void makeFullScreen() {
|
||||
if (Build.VERSION.SDK_INT >= 19)
|
||||
this.getWindow().getDecorView().setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
this.getWindow().getDecorView().setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,6 +80,17 @@ public class GameActivity extends NativeActivity {
|
||||
makeFullScreen();
|
||||
}
|
||||
|
||||
private native void saveSettings();
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
// Avoid losing setting changes in case the app is onDestroy()ed later.
|
||||
// Saving stuff in onStop() is recommended in the Android activity
|
||||
// lifecycle documentation.
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Ignore the back press so Minetest can handle it
|
||||
@@ -91,21 +106,11 @@ public class GameActivity extends NativeActivity {
|
||||
container.setOrientation(LinearLayout.VERTICAL);
|
||||
builder.setView(container);
|
||||
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);
|
||||
}
|
||||
CustomEditText editText = new CustomEditText(this, editType);
|
||||
container.addView(editText);
|
||||
editText.setMaxLines(8);
|
||||
editText.requestFocus();
|
||||
editText.setHint(hint);
|
||||
editText.setText(current);
|
||||
final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED,
|
||||
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
if (editType == 1)
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
@@ -114,7 +119,8 @@ public class GameActivity extends NativeActivity {
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
else
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
editText.setSelection(editText.getText().length());
|
||||
editText.setSelection(Objects.requireNonNull(editText.getText()).length());
|
||||
final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
editText.setOnKeyListener((view, keyCode, event) -> {
|
||||
// For multi-line, do not submit the text after pressing Enter key
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) {
|
||||
@@ -138,12 +144,13 @@ public class GameActivity extends NativeActivity {
|
||||
alertDialog.dismiss();
|
||||
}));
|
||||
}
|
||||
alertDialog.show();
|
||||
alertDialog.setOnCancelListener(dialog -> {
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
messageReturnValue = current;
|
||||
messageReturnCode = -1;
|
||||
});
|
||||
alertDialog.show();
|
||||
editText.requestFocusTryShow();
|
||||
}
|
||||
|
||||
public int getDialogState() {
|
||||
@@ -179,4 +186,46 @@ 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);
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
String langCode = Locale.getDefault().getLanguage();
|
||||
|
||||
// getLanguage() still uses old language codes to preserve compatibility.
|
||||
// List of code changes in ISO 639-2:
|
||||
// https://www.loc.gov/standards/iso639-2/php/code_changes.php
|
||||
switch (langCode) {
|
||||
case "in":
|
||||
langCode = "id"; // Indonesian
|
||||
break;
|
||||
case "iw":
|
||||
langCode = "he"; // Hebrew
|
||||
break;
|
||||
case "ji":
|
||||
langCode = "yi"; // Yiddish
|
||||
break;
|
||||
case "jw":
|
||||
langCode = "jv"; // Javanese
|
||||
break;
|
||||
}
|
||||
|
||||
return langCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,38 +20,30 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
package net.minetest.minetest;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static net.minetest.minetest.UnzipService.*;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public static final String NOTIFICATION_CHANNEL_ID = "Minetest channel";
|
||||
|
||||
private final static int versionCode = BuildConfig.VERSION_CODE;
|
||||
private final static int PERMISSIONS = 1;
|
||||
private static final String[] REQUIRED_SDK_PERMISSIONS =
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
private static final String SETTINGS = "MinetestSettings";
|
||||
private static final String TAG_VERSION_CODE = "versionCode";
|
||||
|
||||
@@ -95,59 +87,21 @@ public class MainActivity extends AppCompatActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
IntentFilter filter = new IntentFilter(ACTION_UPDATE);
|
||||
registerReceiver(myReceiver, filter);
|
||||
|
||||
mProgressBar = findViewById(R.id.progressBar);
|
||||
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)
|
||||
checkPermission();
|
||||
else
|
||||
checkAppVersion();
|
||||
}
|
||||
checkAppVersion();
|
||||
|
||||
private void checkPermission() {
|
||||
final List<String> missingPermissions = new ArrayList<>();
|
||||
for (final String permission : REQUIRED_SDK_PERMISSIONS) {
|
||||
final int result = ContextCompat.checkSelfPermission(this, permission);
|
||||
if (result != PackageManager.PERMISSION_GRANTED)
|
||||
missingPermissions.add(permission);
|
||||
}
|
||||
if (!missingPermissions.isEmpty()) {
|
||||
final String[] permissions = missingPermissions
|
||||
.toArray(new String[0]);
|
||||
ActivityCompat.requestPermissions(this, permissions, PERMISSIONS);
|
||||
} else {
|
||||
final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
|
||||
Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
|
||||
onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
@NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == PERMISSIONS) {
|
||||
for (int grantResult : grantResults) {
|
||||
if (grantResult != PackageManager.PERMISSION_GRANTED) {
|
||||
Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
checkAppVersion();
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
createNotificationChannel();
|
||||
}
|
||||
|
||||
private void checkAppVersion() {
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
Toast.makeText(this, R.string.no_external_storage, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (UnzipService.getIsRunning()) {
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressBar.setIndeterminate(true);
|
||||
@@ -172,6 +126,28 @@ public class MainActivity extends AppCompatActivity {
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private void createNotificationChannel() {
|
||||
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (notifyManager == null)
|
||||
return;
|
||||
|
||||
NotificationChannel notifyChannel = new NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
getString(R.string.notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
notifyChannel.setDescription(getString(R.string.notification_channel_description));
|
||||
// Configure the notification channel without sound set
|
||||
notifyChannel.setSound(null, null);
|
||||
notifyChannel.enableLights(false);
|
||||
notifyChannel.enableVibration(false);
|
||||
|
||||
// It is fine to always create the notification channel because creating a channel
|
||||
// with the same ID is the same as overriding it (only its name and description).
|
||||
notifyManager.createNotificationChannel(notifyChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent abrupt interruption when copy game files from assets
|
||||
|
||||
@@ -22,17 +22,15 @@ package net.minetest.minetest;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
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;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.io.File;
|
||||
@@ -59,9 +57,11 @@ public class UnzipService extends IntentService {
|
||||
private String failureMessage;
|
||||
|
||||
private static boolean isRunning = false;
|
||||
|
||||
public static synchronized boolean getIsRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
private static synchronized void setIsRunning(boolean v) {
|
||||
isRunning = v;
|
||||
}
|
||||
@@ -77,9 +77,6 @@ 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)) {
|
||||
@@ -91,39 +88,25 @@ public class UnzipService extends IntentService {
|
||||
}
|
||||
}
|
||||
|
||||
migrate(notificationBuilder, userDataDirectory);
|
||||
unzip(notificationBuilder, zipFile, userDataDirectory);
|
||||
} catch (IOException e) {
|
||||
isSuccess = false;
|
||||
failureMessage = e.getLocalizedMessage();
|
||||
} finally {
|
||||
setIsRunning(false);
|
||||
zipFile.delete();
|
||||
if (!zipFile.delete()) {
|
||||
Log.w("UnzipService", "Minetest installation ZIP cannot be deleted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Notification.Builder createNotification() {
|
||||
String name = "net.minetest.minetest";
|
||||
String channelId = "Minetest channel";
|
||||
String description = "notifications from Minetest";
|
||||
Notification.Builder builder;
|
||||
if (mNotifyManager == null)
|
||||
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
NotificationChannel mChannel = null;
|
||||
if (mNotifyManager != null)
|
||||
mChannel = mNotifyManager.getNotificationChannel(channelId);
|
||||
if (mChannel == null) {
|
||||
mChannel = new NotificationChannel(channelId, name, importance);
|
||||
mChannel.setDescription(description);
|
||||
// Configure the notification channel, NO SOUND
|
||||
mChannel.setSound(null, null);
|
||||
mChannel.enableLights(false);
|
||||
mChannel.enableVibration(false);
|
||||
mNotifyManager.createNotificationChannel(mChannel);
|
||||
}
|
||||
builder = new Notification.Builder(this, channelId);
|
||||
builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID);
|
||||
} else {
|
||||
builder = new Notification.Builder(this);
|
||||
}
|
||||
@@ -131,12 +114,16 @@ 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, 0);
|
||||
notificationIntent, pendingIntentFlag);
|
||||
|
||||
builder.setContentTitle(getString(R.string.notification_title))
|
||||
builder.setContentTitle(getString(R.string.unzip_notification_title))
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setContentText(getString(R.string.notification_description))
|
||||
.setContentText(getString(R.string.unzip_notification_description))
|
||||
.setContentIntent(intent)
|
||||
.setOngoing(true)
|
||||
.setProgress(0, 0, true);
|
||||
@@ -179,9 +166,9 @@ public class UnzipService extends IntentService {
|
||||
try {
|
||||
Process p = new ProcessBuilder("/system/bin/mv",
|
||||
src.getAbsolutePath(), dst.getAbsolutePath()).start();
|
||||
int exitcode = p.waitFor();
|
||||
if (exitcode != 0)
|
||||
throw new IOException("Move failed with exit code " + exitcode);
|
||||
int exitCode = p.waitFor();
|
||||
if (exitCode != 0)
|
||||
throw new IOException("Move failed with exit code " + exitCode);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Move operation interrupted");
|
||||
}
|
||||
@@ -197,41 +184,7 @@ 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);
|
||||
newLocation.mkdir();
|
||||
|
||||
String[] dirs = new String[] { "worlds", "games", "mods", "textures", "client" };
|
||||
for (int i = 0; i < dirs.length; i++) {
|
||||
publishProgress(notificationBuilder, R.string.migrating, 100 * i / dirs.length);
|
||||
File dir = new File(oldLocation, dirs[i]), dir2 = new File(newLocation, dirs[i]);
|
||||
if (dir.isDirectory() && !dir2.isDirectory()) {
|
||||
moveFileOrDir(dir, dir2);
|
||||
}
|
||||
}
|
||||
|
||||
for (String filename : new String[] { "minetest.conf" }) {
|
||||
File file = new File(oldLocation, filename), file2 = new File(newLocation, filename);
|
||||
if (file.isFile() && !file2.isFile()) {
|
||||
moveFileOrDir(file, file2);
|
||||
}
|
||||
}
|
||||
|
||||
recursivelyDeleteDirectory(oldLocation);
|
||||
}
|
||||
|
||||
private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
|
||||
private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
|
||||
Intent intentUpdate = new Intent(ACTION_UPDATE);
|
||||
intentUpdate.putExtra(ACTION_PROGRESS, progress);
|
||||
intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message);
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
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 {
|
||||
public static @NonNull File createDirs(File root, String dir) {
|
||||
@NonNull
|
||||
public static File createDirs(@NonNull File root, @NonNull String dir) {
|
||||
File f = new File(root, dir);
|
||||
if (!f.isDirectory())
|
||||
f.mkdirs();
|
||||
if (!f.mkdirs())
|
||||
Log.e("Utils", "Directory " + dir + " cannot be created");
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static @Nullable File getUserDataDirectory(Context context) {
|
||||
File extDir = context.getExternalFilesDir(null);
|
||||
if (extDir == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static File getUserDataDirectory(@NonNull Context context) {
|
||||
File extDir = Objects.requireNonNull(
|
||||
context.getExternalFilesDir(null),
|
||||
"Cannot get external file directory"
|
||||
);
|
||||
return createDirs(extDir, "Minetest");
|
||||
}
|
||||
|
||||
public static @Nullable File getCacheDirectory(Context context) {
|
||||
return context.getCacheDir();
|
||||
@NonNull
|
||||
public static File getCacheDirectory(@NonNull Context context) {
|
||||
return Objects.requireNonNull(
|
||||
context.getCacheDir(),
|
||||
"Cannot get cache directory"
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean isInstallValid(Context context) {
|
||||
public static boolean isInstallValid(@NonNull Context context) {
|
||||
File userDataDirectory = getUserDataDirectory(context);
|
||||
return userDataDirectory != null && userDataDirectory.isDirectory() &&
|
||||
new File(userDataDirectory, "games").isDirectory() &&
|
||||
return userDataDirectory.isDirectory() &&
|
||||
new File(userDataDirectory, "builtin").isDirectory() &&
|
||||
new File(userDataDirectory, "client").isDirectory() &&
|
||||
new File(userDataDirectory, "textures").isDirectory();
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="label">Minetest</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="migrating">Migrating save data from old install… (this may take a while)</string>
|
||||
<string name="not_granted">Required permission wasn\'t granted, Minetest can\'t run without it</string>
|
||||
<string name="notification_title">Loading Minetest</string>
|
||||
<string name="notification_description">Less than 1 minute…</string>
|
||||
<string name="notification_channel_name">General notification</string>
|
||||
<string name="notification_channel_description">Notifications from Minetest</string>
|
||||
<string name="unzip_notification_title">Loading Minetest</string>
|
||||
<string name="unzip_notification_description">Less than 1 minute…</string>
|
||||
<string name="ime_dialog_done">Done</string>
|
||||
<string name="no_external_storage">External storage isn\'t available. If you use an SDCard, please reinsert it. Otherwise, try restarting your phone or contacting the Minetest developers</string>
|
||||
|
||||
</resources>
|
||||
|
||||
3
android/app/src/main/res/xml/filepaths.xml
Normal file
3
android/app/src/main/res/xml/filepaths.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<paths>
|
||||
<external-files-path path="Minetest/" name="minetest" />
|
||||
</paths>
|
||||
@@ -1,22 +1,22 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
project.ext.set("versionMajor", 5) // Version Major
|
||||
project.ext.set("versionMinor", 5) // Version Minor
|
||||
project.ext.set("versionMinor", 8) // Version Minor
|
||||
project.ext.set("versionPatch", 1) // Version Patch
|
||||
project.ext.set("versionExtra", "") // Version Extra
|
||||
project.ext.set("versionCode", 40) // Android Version Code
|
||||
// ^ keep in sync with cmake
|
||||
project.ext.set("versionCode", 48) // Android Version Code
|
||||
// NOTE: +2 after each release!
|
||||
// +1 for ARM and +1 for ARM64 APK's, because
|
||||
// each APK must have a larger `versionCode` than the previous
|
||||
|
||||
buildscript {
|
||||
ext.ndk_version = '23.0.7599858'
|
||||
ext.ndk_version = '25.2.9519653'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
classpath 'com.android.tools.build:gradle:7.4.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
|
||||
@@ -26,11 +26,10 @@ buildscript {
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
delete 'native/deps'
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.parallel.threads=8
|
||||
org.gradle.configureondemand=true
|
||||
android.enableJetifier=true
|
||||
android.enableJetifier=false
|
||||
android.useAndroidX=true
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
|
||||
111
android/icons/exit_btn.svg
Normal file
111
android/icons/exit_btn.svg
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46667"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
sodipodi:docname="exit_btn.svg"
|
||||
inkscape:export-filename="../../textures/base/pack/exit_btn.png"
|
||||
inkscape:export-xdpi="24.000002"
|
||||
inkscape:export-ydpi="24.000002"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#404040"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.84958349"
|
||||
inkscape:cx="-94.752312"
|
||||
inkscape:cy="291.31922"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1001"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:snap-grids="true"
|
||||
inkscape:snap-page="true"
|
||||
showguides="false"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#404040">
|
||||
<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" />
|
||||
<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
|
||||
id="rect5028"
|
||||
style="display:inline;fill:none;stroke:#ffffff;stroke-width:5.99996;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
|
||||
d="m 78.052082,90.48746 v 17.4625 l -50.535415,4e-5 V 27.516667 l 50.535415,3.7e-5 v 17.462423"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#ffffff;stroke-width:5.99996;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 101.49853,55.033202 12.69966,12.700052 -12.69966,12.699942"
|
||||
id="path4737"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#ffffff;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 113.36416,67.733332 H 59.484405"
|
||||
id="path4729"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -2,26 +2,25 @@ apply plugin: 'com.android.library'
|
||||
apply plugin: 'de.undercouch.download'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.3'
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion '33.0.2'
|
||||
ndkVersion "$ndk_version"
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments '-j' + Runtime.getRuntime().availableProcessors(),
|
||||
"versionMajor=${versionMajor}",
|
||||
"versionMinor=${versionMinor}",
|
||||
"versionPatch=${versionPatch}",
|
||||
"versionExtra=${versionExtra}"
|
||||
cmake {
|
||||
arguments "-DANDROID_STL=c++_shared",
|
||||
"-DENABLE_CURL=1", "-DENABLE_SOUND=1",
|
||||
"-DENABLE_TOUCH=1", "-DENABLE_GETTEXT=1",
|
||||
"-DBUILD_UNITTESTS=0", "-DENABLE_UPDATE_CHECKER=0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path file('jni/Android.mk')
|
||||
cmake {
|
||||
path file("../../CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,18 +29,12 @@ android {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'armeabi-v7a', 'arm64-v8a'//, 'x86'
|
||||
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments 'NDEBUG=1'
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
debugSymbolLevel 'SYMBOL_TABLE'
|
||||
}
|
||||
@@ -50,19 +43,30 @@ android {
|
||||
}
|
||||
|
||||
// get precompiled deps
|
||||
task downloadDeps(type: Download) {
|
||||
src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps.zip'
|
||||
dest new File(buildDir, 'deps.zip')
|
||||
overwrite false
|
||||
}
|
||||
def depsDir = new File(buildDir.parent, 'deps')
|
||||
if (new File(depsDir, 'armeabi-v7a').exists()) {
|
||||
task getDeps {
|
||||
doLast { logger.lifecycle('Using existing deps from {}', depsDir) }
|
||||
}
|
||||
} else {
|
||||
task downloadDeps(type: Download) {
|
||||
def depsZip = new File(buildDir, 'deps.zip')
|
||||
|
||||
task getDeps(dependsOn: downloadDeps, type: Copy) {
|
||||
def deps = new File(buildDir.parent, 'deps')
|
||||
if (!deps.exists()) {
|
||||
deps.mkdir()
|
||||
from zipTree(downloadDeps.dest)
|
||||
into deps
|
||||
src 'https://github.com/minetest/minetest_android_deps/releases/download/5.8.0/deps.zip'
|
||||
dest depsZip
|
||||
overwrite false
|
||||
|
||||
task getDeps(dependsOn: downloadDeps, type: Copy) {
|
||||
depsDir.mkdir()
|
||||
from zipTree(depsZip)
|
||||
into depsDir
|
||||
doFirst { logger.lifecycle('Extracting to {}', depsDir) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn getDeps
|
||||
|
||||
clean {
|
||||
delete new File(buildDir.parent, 'deps')
|
||||
}
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
LOCAL_PATH := $(call my-dir)/..
|
||||
|
||||
#LOCAL_ADDRESS_SANITIZER:=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
|
||||
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
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Irrlicht
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libIrrlichtMt.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := LuaJIT
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := OpenAL
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/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)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := SQLite3
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/SQLite/libsqlite3.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
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Minetest
|
||||
|
||||
LOCAL_CFLAGS += \
|
||||
-DJSONCPP_NO_LOCALE_SUPPORT \
|
||||
-DHAVE_TOUCHSCREENGUI \
|
||||
-DENABLE_GLES=1 \
|
||||
-DUSE_CURL=1 \
|
||||
-DUSE_SOUND=1 \
|
||||
-DUSE_LEVELDB=0 \
|
||||
-DUSE_LUAJIT=1 \
|
||||
-DUSE_GETTEXT=1 \
|
||||
-DVERSION_MAJOR=${versionMajor} \
|
||||
-DVERSION_MINOR=${versionMinor} \
|
||||
-DVERSION_PATCH=${versionPatch} \
|
||||
-DVERSION_EXTRA=${versionExtra} \
|
||||
$(GPROF_DEF)
|
||||
|
||||
ifdef NDEBUG
|
||||
LOCAL_CFLAGS += -DNDEBUG=1
|
||||
endif
|
||||
|
||||
ifdef GPROF
|
||||
GPROF_DEF := -DGPROF
|
||||
PROFILER_LIBS := android-ndk-profiler
|
||||
LOCAL_CFLAGS += -pg
|
||||
endif
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
../../src \
|
||||
../../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)/LuaJIT/include \
|
||||
deps/$(APP_ABI)/OpenAL-Soft/include \
|
||||
deps/$(APP_ABI)/SQLite/include \
|
||||
deps/$(APP_ABI)/Vorbis/include \
|
||||
deps/$(APP_ABI)/Zstd/include
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(wildcard ../../src/client/*.cpp) \
|
||||
$(wildcard ../../src/client/*/*.cpp) \
|
||||
$(wildcard ../../src/content/*.cpp) \
|
||||
../../src/database/database.cpp \
|
||||
../../src/database/database-dummy.cpp \
|
||||
../../src/database/database-files.cpp \
|
||||
../../src/database/database-sqlite3.cpp \
|
||||
$(wildcard ../../src/gui/*.cpp) \
|
||||
$(wildcard ../../src/irrlicht_changes/*.cpp) \
|
||||
$(wildcard ../../src/mapgen/*.cpp) \
|
||||
$(wildcard ../../src/network/*.cpp) \
|
||||
$(wildcard ../../src/script/*.cpp) \
|
||||
$(wildcard ../../src/script/*/*.cpp) \
|
||||
$(wildcard ../../src/server/*.cpp) \
|
||||
$(wildcard ../../src/threading/*.cpp) \
|
||||
$(wildcard ../../src/util/*.c) \
|
||||
$(wildcard ../../src/util/*.cpp) \
|
||||
../../src/ban.cpp \
|
||||
../../src/chat.cpp \
|
||||
../../src/clientiface.cpp \
|
||||
../../src/collision.cpp \
|
||||
../../src/content_mapnode.cpp \
|
||||
../../src/content_nodemeta.cpp \
|
||||
../../src/convert_json.cpp \
|
||||
../../src/craftdef.cpp \
|
||||
../../src/debug.cpp \
|
||||
../../src/defaultsettings.cpp \
|
||||
../../src/emerge.cpp \
|
||||
../../src/environment.cpp \
|
||||
../../src/face_position_cache.cpp \
|
||||
../../src/filesys.cpp \
|
||||
../../src/gettext.cpp \
|
||||
../../src/httpfetch.cpp \
|
||||
../../src/hud.cpp \
|
||||
../../src/inventory.cpp \
|
||||
../../src/inventorymanager.cpp \
|
||||
../../src/itemdef.cpp \
|
||||
../../src/itemstackmetadata.cpp \
|
||||
../../src/light.cpp \
|
||||
../../src/log.cpp \
|
||||
../../src/main.cpp \
|
||||
../../src/map.cpp \
|
||||
../../src/map_settings_manager.cpp \
|
||||
../../src/mapblock.cpp \
|
||||
../../src/mapnode.cpp \
|
||||
../../src/mapsector.cpp \
|
||||
../../src/metadata.cpp \
|
||||
../../src/modchannels.cpp \
|
||||
../../src/nameidmapping.cpp \
|
||||
../../src/nodedef.cpp \
|
||||
../../src/nodemetadata.cpp \
|
||||
../../src/nodetimer.cpp \
|
||||
../../src/noise.cpp \
|
||||
../../src/objdef.cpp \
|
||||
../../src/object_properties.cpp \
|
||||
../../src/particles.cpp \
|
||||
../../src/pathfinder.cpp \
|
||||
../../src/player.cpp \
|
||||
../../src/porting.cpp \
|
||||
../../src/porting_android.cpp \
|
||||
../../src/profiler.cpp \
|
||||
../../src/raycast.cpp \
|
||||
../../src/reflowscan.cpp \
|
||||
../../src/remoteplayer.cpp \
|
||||
../../src/rollback.cpp \
|
||||
../../src/rollback_interface.cpp \
|
||||
../../src/serialization.cpp \
|
||||
../../src/server.cpp \
|
||||
../../src/serverenvironment.cpp \
|
||||
../../src/serverlist.cpp \
|
||||
../../src/settings.cpp \
|
||||
../../src/staticobject.cpp \
|
||||
../../src/texture_override.cpp \
|
||||
../../src/tileanimation.cpp \
|
||||
../../src/tool.cpp \
|
||||
../../src/translation.cpp \
|
||||
../../src/version.cpp \
|
||||
../../src/voxel.cpp \
|
||||
../../src/voxelalgorithms.cpp
|
||||
|
||||
# GMP
|
||||
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 \
|
||||
LuaJIT \
|
||||
OpenAL \
|
||||
Gettext \
|
||||
SQLite3 \
|
||||
Vorbis libvorbisfile libogg \
|
||||
Zstd
|
||||
LOCAL_STATIC_LIBRARIES += android_native_app_glue $(PROFILER_LIBS)
|
||||
|
||||
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
ifdef GPROF
|
||||
$(call import-module,android-ndk-profiler)
|
||||
endif
|
||||
$(call import-module,android/native_app_glue)
|
||||
@@ -1,32 +0,0 @@
|
||||
APP_PLATFORM := ${APP_PLATFORM}
|
||||
APP_ABI := ${TARGET_ABI}
|
||||
APP_STL := c++_shared
|
||||
NDK_TOOLCHAIN_VERSION := clang
|
||||
APP_SHORT_COMMANDS := true
|
||||
APP_MODULES := Minetest
|
||||
|
||||
APP_CPPFLAGS := -O2 -fvisibility=hidden
|
||||
|
||||
ifeq ($(APP_ABI),armeabi-v7a)
|
||||
APP_CPPFLAGS += -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb
|
||||
endif
|
||||
|
||||
ifeq ($(APP_ABI),x86)
|
||||
APP_CPPFLAGS += -mssse3 -mfpmath=sse -funroll-loops
|
||||
endif
|
||||
|
||||
ifndef NDEBUG
|
||||
APP_CPPFLAGS := -g -Og -fno-omit-frame-pointer
|
||||
endif
|
||||
|
||||
APP_CFLAGS := $(APP_CPPFLAGS) -Wno-inconsistent-missing-override -Wno-parentheses-equality
|
||||
APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++17
|
||||
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe
|
||||
|
||||
ifeq ($(APP_ABI),arm64-v8a)
|
||||
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections
|
||||
endif
|
||||
|
||||
ifndef NDEBUG
|
||||
APP_LDFLAGS :=
|
||||
endif
|
||||
69
builtin/async/game.lua
Normal file
69
builtin/async/game.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
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
|
||||
getmetatable(v).__index = all.nodedef_default
|
||||
all.registered_nodes[k] = v
|
||||
elseif v.type == "craft" then
|
||||
getmetatable(v).__index = all.craftitemdef_default
|
||||
all.registered_craftitems[k] = v
|
||||
elseif v.type == "tool" then
|
||||
getmetatable(v).__index = all.tooldef_default
|
||||
all.registered_tools[k] = v
|
||||
else
|
||||
getmetatable(v).__index = all.noneitemdef_default
|
||||
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()
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
core.log("info", "Initializing Asynchronous environment")
|
||||
core.log("info", "Initializing asynchronous environment")
|
||||
|
||||
function core.job_processor(func, serialized_param)
|
||||
local param = core.deserialize(serialized_param)
|
||||
@@ -8,4 +7,3 @@ function core.job_processor(func, serialized_param)
|
||||
|
||||
return retval or core.serialize(nil)
|
||||
end
|
||||
|
||||
@@ -5,6 +5,9 @@ local commonpath = scriptpath.."common"..DIR_DELIM
|
||||
|
||||
dofile(clientpath .. "register.lua")
|
||||
dofile(commonpath .. "after.lua")
|
||||
dofile(commonpath .. "mod_storage.lua")
|
||||
dofile(commonpath .. "chatcommands.lua")
|
||||
dofile(clientpath .. "chatcommands.lua")
|
||||
dofile(clientpath .. "death_formspec.lua")
|
||||
dofile(clientpath .. "misc.lua")
|
||||
assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions
|
||||
|
||||
18
builtin/client/misc.lua
Normal file
18
builtin/client/misc.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
|
||||
|
||||
-- old non-method sound functions
|
||||
|
||||
function core.sound_stop(handle, ...)
|
||||
return handle:stop(...)
|
||||
end
|
||||
|
||||
function core.sound_fade(handle, ...)
|
||||
return handle:fade(...)
|
||||
end
|
||||
@@ -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 aquire correct raw data --
|
||||
-- parameter passed to raw_fct to acquire correct raw data --
|
||||
-- --
|
||||
--------------------------------------------------------------------------------
|
||||
filterlist = {}
|
||||
|
||||
239
builtin/common/item_s.lua
Normal file
239
builtin/common/item_s.lua
Normal file
@@ -0,0 +1,239 @@
|
||||
-- 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
|
||||
@@ -170,6 +170,9 @@ 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
|
||||
@@ -204,7 +207,7 @@ end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function string:trim()
|
||||
return (self:gsub("^%s*(.-)%s*$", "%1"))
|
||||
return self:match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -245,16 +248,17 @@ function math.round(x)
|
||||
return math.ceil(x - 0.5)
|
||||
end
|
||||
|
||||
|
||||
local formspec_escapes = {
|
||||
["\\"] = "\\\\",
|
||||
["["] = "\\[",
|
||||
["]"] = "\\]",
|
||||
[";"] = "\\;",
|
||||
[","] = "\\,",
|
||||
["$"] = "\\$",
|
||||
}
|
||||
function core.formspec_escape(text)
|
||||
if text ~= nil then
|
||||
text = string.gsub(text,"\\","\\\\")
|
||||
text = string.gsub(text,"%]","\\]")
|
||||
text = string.gsub(text,"%[","\\[")
|
||||
text = string.gsub(text,";","\\;")
|
||||
text = string.gsub(text,",","\\,")
|
||||
end
|
||||
return text
|
||||
-- Use explicit character set instead of dot here because it doubles the performance
|
||||
return text and string.gsub(text, "[\\%[%];,$]", formspec_escapes)
|
||||
end
|
||||
|
||||
|
||||
@@ -265,18 +269,21 @@ function core.wrap_text(text, max_length, as_table)
|
||||
return as_table and {text} or text
|
||||
end
|
||||
|
||||
for word in text:gmatch('%S+') do
|
||||
local cur_length = #table.concat(line, ' ')
|
||||
if cur_length > 0 and cur_length + #word + 1 >= max_length then
|
||||
local line_length = 0
|
||||
for word in text:gmatch("%S+") do
|
||||
if line_length > 0 and line_length + #word + 1 >= max_length then
|
||||
-- word wouldn't fit on current line, move to next line
|
||||
table.insert(result, table.concat(line, ' '))
|
||||
line = {}
|
||||
table.insert(result, table.concat(line, " "))
|
||||
line = {word}
|
||||
line_length = #word
|
||||
else
|
||||
table.insert(line, word)
|
||||
line_length = line_length + 1 + #word
|
||||
end
|
||||
table.insert(line, word)
|
||||
end
|
||||
|
||||
table.insert(result, table.concat(line, ' '))
|
||||
return as_table and result or table.concat(result, '\n')
|
||||
table.insert(result, table.concat(line, " "))
|
||||
return as_table and result or table.concat(result, "\n")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -425,54 +432,50 @@ function core.string_to_pos(value)
|
||||
return nil
|
||||
end
|
||||
|
||||
local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
|
||||
if x and y and z then
|
||||
x = tonumber(x)
|
||||
y = tonumber(y)
|
||||
z = tonumber(z)
|
||||
return vector.new(x, y, z)
|
||||
end
|
||||
x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
|
||||
value = value:match("^%((.-)%)$") or value -- strip parentheses
|
||||
|
||||
local x, y, z = value:trim():match("^([%d.-]+)[,%s]%s*([%d.-]+)[,%s]%s*([%d.-]+)$")
|
||||
if x and y and z then
|
||||
x = tonumber(x)
|
||||
y = tonumber(y)
|
||||
z = tonumber(z)
|
||||
return vector.new(x, y, z)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function core.string_to_area(value)
|
||||
local p1, p2 = unpack(value:split(") ("))
|
||||
if p1 == nil or p2 == nil then
|
||||
return nil
|
||||
|
||||
do
|
||||
local rel_num_cap = "(~?-?%d*%.?%d*)" -- may be overly permissive as this will be tonumber'ed anyways
|
||||
local num_delim = "[,%s]%s*"
|
||||
local pattern = "^" .. table.concat({rel_num_cap, rel_num_cap, rel_num_cap}, num_delim) .. "$"
|
||||
|
||||
local function parse_area_string(pos, relative_to)
|
||||
local pp = {}
|
||||
pp.x, pp.y, pp.z = pos:trim():match(pattern)
|
||||
return core.parse_coordinates(pp.x, pp.y, pp.z, relative_to)
|
||||
end
|
||||
|
||||
p1 = core.string_to_pos(p1 .. ")")
|
||||
p2 = core.string_to_pos("(" .. p2)
|
||||
if p1 == nil or p2 == nil then
|
||||
return nil
|
||||
function core.string_to_area(value, relative_to)
|
||||
local p1, p2 = value:match("^%((.-)%)%s*%((.-)%)$")
|
||||
if not p1 then
|
||||
return
|
||||
end
|
||||
|
||||
p1 = parse_area_string(p1, relative_to)
|
||||
p2 = parse_area_string(p2, relative_to)
|
||||
|
||||
if p1 == nil or p2 == nil then
|
||||
return
|
||||
end
|
||||
|
||||
return p1, p2
|
||||
end
|
||||
|
||||
return p1, p2
|
||||
end
|
||||
|
||||
local function test_string_to_area()
|
||||
local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)")
|
||||
assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2)
|
||||
assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
|
||||
|
||||
p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end
|
||||
|
||||
test_string_to_area()
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function table.copy(t, seen)
|
||||
local n = {}
|
||||
@@ -520,18 +523,6 @@ end
|
||||
--------------------------------------------------------------------------------
|
||||
-- mainmenu only functions
|
||||
--------------------------------------------------------------------------------
|
||||
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 core.gettext then -- for client and mainmenu
|
||||
function fgettext_ne(text, ...)
|
||||
text = core.gettext(text)
|
||||
@@ -701,3 +692,71 @@ end
|
||||
function core.is_nan(number)
|
||||
return number ~= number
|
||||
end
|
||||
|
||||
--[[ Helper function for parsing an optionally relative number
|
||||
of a chat command parameter, using the chat command tilde notation.
|
||||
|
||||
Parameters:
|
||||
* arg: String snippet containing the number; possible values:
|
||||
* "<number>": return as number
|
||||
* "~<number>": return relative_to + <number>
|
||||
* "~": return relative_to
|
||||
* Anything else will return `nil`
|
||||
* relative_to: Number to which the `arg` number might be relative to
|
||||
|
||||
Returns:
|
||||
A number or `nil`, depending on `arg.
|
||||
|
||||
Examples:
|
||||
* `core.parse_relative_number("5", 10)` returns 5
|
||||
* `core.parse_relative_number("~5", 10)` returns 15
|
||||
* `core.parse_relative_number("~", 10)` returns 10
|
||||
]]
|
||||
function core.parse_relative_number(arg, relative_to)
|
||||
if not arg then
|
||||
return nil
|
||||
elseif arg == "~" then
|
||||
return relative_to
|
||||
elseif string.sub(arg, 1, 1) == "~" then
|
||||
local number = tonumber(string.sub(arg, 2))
|
||||
if not number then
|
||||
return nil
|
||||
end
|
||||
if core.is_nan(number) or number == math.huge or number == -math.huge then
|
||||
return nil
|
||||
end
|
||||
return relative_to + number
|
||||
else
|
||||
local number = tonumber(arg)
|
||||
if core.is_nan(number) or number == math.huge or number == -math.huge then
|
||||
return nil
|
||||
end
|
||||
return number
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Helper function to parse coordinates that might be relative
|
||||
to another position; supports chat command tilde notation.
|
||||
Intended to be used in chat command parameter parsing.
|
||||
|
||||
Parameters:
|
||||
* x, y, z: Parsed x, y, and z coordinates as strings
|
||||
* relative_to: Position to which to compare the position
|
||||
|
||||
Syntax of x, y and z:
|
||||
* "<number>": return as number
|
||||
* "~<number>": return <number> + player position on this axis
|
||||
* "~": return player position on this axis
|
||||
|
||||
Returns: a vector or nil for invalid input or if player does not exist
|
||||
]]
|
||||
function core.parse_coordinates(x, y, z, relative_to)
|
||||
if not relative_to then
|
||||
x, y, z = tonumber(x), tonumber(y), tonumber(z)
|
||||
return x and y and z and { x = x, y = y, z = z }
|
||||
end
|
||||
local rx = core.parse_relative_number(x, relative_to.x)
|
||||
local ry = core.parse_relative_number(y, relative_to.y)
|
||||
local rz = core.parse_relative_number(z, relative_to.z)
|
||||
return rx and ry and rz and { x = rx, y = ry, z = rz }
|
||||
end
|
||||
|
||||
19
builtin/common/mod_storage.lua
Normal file
19
builtin/common/mod_storage.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Modify core.get_mod_storage to return the storage for the current mod.
|
||||
|
||||
local get_current_modname = core.get_current_modname
|
||||
|
||||
local old_get_mod_storage = core.get_mod_storage
|
||||
|
||||
local storages = setmetatable({}, {
|
||||
__mode = "v", -- values are weak references (can be garbage-collected)
|
||||
__index = function(self, modname)
|
||||
local storage = old_get_mod_storage(modname)
|
||||
self[modname] = storage
|
||||
return storage
|
||||
end,
|
||||
})
|
||||
|
||||
function core.get_mod_storage()
|
||||
local modname = get_current_modname()
|
||||
return modname and storages[modname]
|
||||
end
|
||||
74
builtin/common/register.lua
Normal file
74
builtin/common/register.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
local builtin_shared = ...
|
||||
|
||||
do
|
||||
local default = {mod = "??", name = "??"}
|
||||
core.callback_origins = setmetatable({}, {
|
||||
__index = function()
|
||||
return default
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function core.run_callbacks(callbacks, mode, ...)
|
||||
assert(type(callbacks) == "table")
|
||||
local cb_len = #callbacks
|
||||
if cb_len == 0 then
|
||||
if mode == 2 or mode == 3 then
|
||||
return true
|
||||
elseif mode == 4 or mode == 5 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
local ret = nil
|
||||
for i = 1, cb_len do
|
||||
local origin = core.callback_origins[callbacks[i]]
|
||||
core.set_last_run_mod(origin.mod)
|
||||
local cb_ret = callbacks[i](...)
|
||||
|
||||
if mode == 0 and i == 1 then
|
||||
ret = cb_ret
|
||||
elseif mode == 1 and i == cb_len then
|
||||
ret = cb_ret
|
||||
elseif mode == 2 then
|
||||
if not cb_ret or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 3 then
|
||||
if cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
ret = cb_ret
|
||||
elseif mode == 4 then
|
||||
if (cb_ret and not ret) or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 5 and cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function builtin_shared.make_registration()
|
||||
local t = {}
|
||||
local registerfunc = function(func)
|
||||
t[#t + 1] = func
|
||||
core.callback_origins[func] = {
|
||||
mod = core.get_current_modname() or "??",
|
||||
name = debug.getinfo(1, "n").name or "??"
|
||||
}
|
||||
end
|
||||
return t, registerfunc
|
||||
end
|
||||
|
||||
function builtin_shared.make_registration_reverse()
|
||||
local t = {}
|
||||
local registerfunc = function(func)
|
||||
table.insert(t, 1, func)
|
||||
core.callback_origins[func] = {
|
||||
mod = core.get_current_modname() or "??",
|
||||
name = debug.getinfo(1, "n").name or "??"
|
||||
}
|
||||
end
|
||||
return t, registerfunc
|
||||
end
|
||||
@@ -1,205 +1,238 @@
|
||||
--- Lua module to serialize values as Lua code.
|
||||
-- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
|
||||
-- From: https://github.com/appgurueu/modlib/blob/master/luon.lua
|
||||
-- License: MIT
|
||||
-- @copyright 2006-2997 Fabien Fleutot <metalua@gmail.com>
|
||||
-- @author Fabien Fleutot <metalua@gmail.com>
|
||||
-- @author ShadowNinja <shadowninja@minetest.net>
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--- Serialize an object into a source code string. This string, when passed as
|
||||
-- an argument to deserialize(), returns an object structurally identical to
|
||||
-- the original one. The following are currently supported:
|
||||
-- * Booleans, numbers, strings, and nil.
|
||||
-- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode!
|
||||
-- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved.
|
||||
-- This works in two phases:
|
||||
-- 1. Recursively find and record multiple references and recursion.
|
||||
-- 2. Recursively dump the value into a string.
|
||||
-- @param x Value to serialize (nil is allowed).
|
||||
-- @return load()able string containing the value.
|
||||
function core.serialize(x)
|
||||
local local_index = 1 -- Top index of the "_" local table in the dump
|
||||
-- table->nil/1/2 set of tables seen.
|
||||
-- nil = not seen, 1 = seen once, 2 = seen multiple times.
|
||||
local seen = {}
|
||||
local next, rawget, pairs, pcall, error, type, setfenv, loadstring
|
||||
= next, rawget, pairs, pcall, error, type, setfenv, loadstring
|
||||
|
||||
-- nest_points are places where a table appears within itself, directly
|
||||
-- or not. For instance, all of these chunks create nest points in
|
||||
-- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x",
|
||||
-- "x = {}; x[1] = {y = {x}}".
|
||||
-- To handle those, two tables are used by mark_nest_point:
|
||||
-- * nested - Transient set of tables being currently traversed.
|
||||
-- Used for detecting nested tables.
|
||||
-- * nest_points - parent->{key=value, ...} table cantaining the nested
|
||||
-- keys and values in the parent. They're all dumped after all the
|
||||
-- other table operations have been performed.
|
||||
--
|
||||
-- mark_nest_point(p, k, v) fills nest_points with information required
|
||||
-- to remember that key/value (k, v) creates a nest point in table
|
||||
-- parent. It also marks "parent" and the nested item(s) as occuring
|
||||
-- multiple times, since several references to it will be required in
|
||||
-- order to patch the nest points.
|
||||
local nest_points = {}
|
||||
local nested = {}
|
||||
local function mark_nest_point(parent, k, v)
|
||||
local nk, nv = nested[k], nested[v]
|
||||
local np = nest_points[parent]
|
||||
if not np then
|
||||
np = {}
|
||||
nest_points[parent] = np
|
||||
end
|
||||
np[k] = v
|
||||
seen[parent] = 2
|
||||
if nk then seen[k] = 2 end
|
||||
if nv then seen[v] = 2 end
|
||||
local table_concat, string_dump, string_format, string_match, math_huge
|
||||
= table.concat, string.dump, string.format, string.match, math.huge
|
||||
|
||||
-- Recursively counts 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
|
||||
end
|
||||
|
||||
-- First phase, list the tables and functions which appear more than
|
||||
-- once in x.
|
||||
local function mark_multiple_occurences(x)
|
||||
local tp = type(x)
|
||||
if tp ~= "table" and tp ~= "function" then
|
||||
-- No identity (comparison is done by value, not by instance)
|
||||
local function count_values(val)
|
||||
local type_ = type(val)
|
||||
if type_ == "boolean" or type_ == "number" then
|
||||
return
|
||||
end
|
||||
if seen[x] == 1 then
|
||||
seen[x] = 2
|
||||
elseif seen[x] ~= 2 then
|
||||
seen[x] = 1
|
||||
end
|
||||
|
||||
if tp == "table" then
|
||||
nested[x] = true
|
||||
for k, v in pairs(x) do
|
||||
if nested[k] or nested[v] then
|
||||
mark_nest_point(x, k, v)
|
||||
else
|
||||
mark_multiple_occurences(k)
|
||||
mark_multiple_occurences(v)
|
||||
local count = counts[val]
|
||||
counts[val] = (count or 0) + 1
|
||||
if type_ == "table" then
|
||||
if not count then
|
||||
for k, v in pairs(val) do
|
||||
count_values(k)
|
||||
count_values(v)
|
||||
end
|
||||
end
|
||||
nested[x] = nil
|
||||
elseif type_ ~= "string" and type_ ~= "function" then
|
||||
error("unsupported type: " .. type_)
|
||||
end
|
||||
end
|
||||
|
||||
local dumped = {} -- object->varname set
|
||||
local local_defs = {} -- Dumped local definitions as source code lines
|
||||
|
||||
-- Mutually recursive local functions:
|
||||
local dump_val, dump_or_ref_val
|
||||
|
||||
-- If x occurs multiple times, dump the local variable rather than
|
||||
-- the value. If it's the first time it's dumped, also dump the
|
||||
-- content in local_defs.
|
||||
function dump_or_ref_val(x)
|
||||
if seen[x] ~= 2 then
|
||||
return dump_val(x)
|
||||
end
|
||||
local var = dumped[x]
|
||||
if var then -- Already referenced
|
||||
return var
|
||||
end
|
||||
-- First occurence, create and register reference
|
||||
local val = dump_val(x)
|
||||
local i = local_index
|
||||
local_index = local_index + 1
|
||||
var = "_["..i.."]"
|
||||
local_defs[#local_defs + 1] = var.." = "..val
|
||||
dumped[x] = var
|
||||
return var
|
||||
end
|
||||
|
||||
-- Second phase. Dump the object; subparts occuring multiple times
|
||||
-- are dumped in local variables which can be referenced multiple
|
||||
-- times. Care is taken to dump local vars in a sensible order.
|
||||
function dump_val(x)
|
||||
local tp = type(x)
|
||||
if x == nil then return "nil"
|
||||
elseif tp == "string" then return string.format("%q", x)
|
||||
elseif tp == "boolean" then return x and "true" or "false"
|
||||
elseif tp == "function" then
|
||||
return string.format("loadstring(%q)", string.dump(x))
|
||||
elseif tp == "number" then
|
||||
-- Serialize numbers reversibly with string.format
|
||||
return string.format("%.17g", x)
|
||||
elseif tp == "table" then
|
||||
local vals = {}
|
||||
local idx_dumped = {}
|
||||
local np = nest_points[x]
|
||||
for i, v in ipairs(x) do
|
||||
if not np or not np[i] then
|
||||
vals[#vals + 1] = dump_or_ref_val(v)
|
||||
end
|
||||
idx_dumped[i] = true
|
||||
end
|
||||
for k, v in pairs(x) do
|
||||
if (not np or not np[k]) and
|
||||
not idx_dumped[k] then
|
||||
vals[#vals + 1] = "["..dump_or_ref_val(k).."] = "
|
||||
..dump_or_ref_val(v)
|
||||
end
|
||||
end
|
||||
return "{"..table.concat(vals, ", ").."}"
|
||||
else
|
||||
error("Can't serialize data of type "..tp)
|
||||
end
|
||||
end
|
||||
|
||||
local function dump_nest_points()
|
||||
for parent, vals in pairs(nest_points) do
|
||||
for k, v in pairs(vals) do
|
||||
local_defs[#local_defs + 1] = dump_or_ref_val(parent)
|
||||
.."["..dump_or_ref_val(k).."] = "
|
||||
..dump_or_ref_val(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mark_multiple_occurences(x)
|
||||
local top_level = dump_or_ref_val(x)
|
||||
dump_nest_points()
|
||||
|
||||
if next(local_defs) then
|
||||
return "local _ = {}\n"
|
||||
..table.concat(local_defs, "\n")
|
||||
.."\nreturn "..top_level
|
||||
else
|
||||
return "return "..top_level
|
||||
end
|
||||
count_values(value)
|
||||
return counts
|
||||
end
|
||||
|
||||
-- Deserialization
|
||||
-- Build a "set" of Lua keywords. These can't be used as short key names.
|
||||
-- See https://www.lua.org/manual/5.1/manual.html#2.1
|
||||
local keywords = {}
|
||||
for _, keyword in pairs({
|
||||
"and", "break", "do", "else", "elseif",
|
||||
"end", "false", "for", "function", "if",
|
||||
"in", "local", "nil", "not", "or",
|
||||
"repeat", "return", "then", "true", "until", "while",
|
||||
"goto" -- LuaJIT, Lua 5.2+
|
||||
}) do
|
||||
keywords[keyword] = true
|
||||
end
|
||||
|
||||
local function safe_loadstring(...)
|
||||
local func, err = loadstring(...)
|
||||
if func then
|
||||
setfenv(func, {})
|
||||
return func
|
||||
local function quote(string)
|
||||
return string_format("%q", string)
|
||||
end
|
||||
|
||||
local function dump_func(func)
|
||||
return string_format("loadstring(%q)", string_dump(func))
|
||||
end
|
||||
|
||||
-- Serializes Lua nil, booleans, numbers, strings, tables and even functions
|
||||
-- Tables are referenced by reference, strings are referenced by value. Supports circular tables.
|
||||
local function serialize(value, write)
|
||||
local reference, refnum = "1", 1
|
||||
-- [object] = reference
|
||||
local references = {}
|
||||
-- Circular tables that must be filled using `table[key] = value` statements
|
||||
local to_fill = {}
|
||||
for object, count in pairs(count_objects(value)) do
|
||||
local type_ = type(object)
|
||||
-- Object must appear more than once. If it is a string, the reference has to be shorter than the string.
|
||||
if count >= 2 and (type_ ~= "string" or #reference + 5 < #object) then
|
||||
if refnum == 1 then
|
||||
write"local _={};" -- initialize reference table
|
||||
end
|
||||
write"_["
|
||||
write(reference)
|
||||
write("]=")
|
||||
if type_ == "table" then
|
||||
write("{}")
|
||||
elseif type_ == "function" then
|
||||
write(dump_func(object))
|
||||
elseif type_ == "string" then
|
||||
write(quote(object))
|
||||
end
|
||||
write(";")
|
||||
references[object] = reference
|
||||
if type_ == "table" then
|
||||
to_fill[object] = reference
|
||||
end
|
||||
refnum = refnum + 1
|
||||
reference = ("%d"):format(refnum)
|
||||
end
|
||||
end
|
||||
return nil, err
|
||||
-- Used to decide whether we should do "key=..."
|
||||
local function use_short_key(key)
|
||||
return not references[key] and type(key) == "string" and (not keywords[key]) and string_match(key, "^[%a_][%a%d_]*$")
|
||||
end
|
||||
local function dump(value)
|
||||
-- Primitive types
|
||||
if value == nil then
|
||||
return write("nil")
|
||||
end
|
||||
if value == true then
|
||||
return write("true")
|
||||
end
|
||||
if value == false then
|
||||
return write("false")
|
||||
end
|
||||
local type_ = type(value)
|
||||
if type_ == "number" then
|
||||
if value ~= value then -- nan
|
||||
return write"0/0"
|
||||
elseif value == math_huge then
|
||||
return write"1/0"
|
||||
elseif value == -math_huge then
|
||||
return write"-1/0"
|
||||
else
|
||||
return write(string_format("%.17g", value))
|
||||
end
|
||||
end
|
||||
-- Reference types: table, function and string
|
||||
local ref = references[value]
|
||||
if ref then
|
||||
write"_["
|
||||
write(ref)
|
||||
return write"]"
|
||||
end
|
||||
if type_ == "string" then
|
||||
return write(quote(value))
|
||||
end
|
||||
if type_ == "function" then
|
||||
return write(dump_func(value))
|
||||
end
|
||||
if type_ == "table" then
|
||||
write("{")
|
||||
-- First write list keys:
|
||||
-- Don't use the table length #value here as it may horribly fail
|
||||
-- for tables which use large integers as keys in the hash part;
|
||||
-- stop at the first "hole" (nil value) instead
|
||||
local len = 0
|
||||
local first = true -- whether this is the first entry, which may not have a leading comma
|
||||
while true do
|
||||
local v = rawget(value, len + 1) -- use rawget to avoid metatables like the vector metatable
|
||||
if v == nil then break end
|
||||
if first then first = false else write(",") end
|
||||
dump(v)
|
||||
len = len + 1
|
||||
end
|
||||
-- Now write map keys ([key] = value)
|
||||
for k, v in next, value do
|
||||
-- We have written all non-float keys in [1, len] already
|
||||
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
|
||||
if first then first = false else write(",") end
|
||||
if use_short_key(k) then
|
||||
write(k)
|
||||
else
|
||||
write("[")
|
||||
dump(k)
|
||||
write("]")
|
||||
end
|
||||
write("=")
|
||||
dump(v)
|
||||
end
|
||||
end
|
||||
write("}")
|
||||
return
|
||||
end
|
||||
end
|
||||
-- Write the statements to fill circular tables
|
||||
for table, ref in pairs(to_fill) do
|
||||
for k, v in pairs(table) do
|
||||
write("_[")
|
||||
write(ref)
|
||||
write("]")
|
||||
if use_short_key(k) then
|
||||
write(".")
|
||||
write(k)
|
||||
else
|
||||
write("[")
|
||||
dump(k)
|
||||
write("]")
|
||||
end
|
||||
write("=")
|
||||
dump(v)
|
||||
write(";")
|
||||
end
|
||||
end
|
||||
write("return ")
|
||||
dump(value)
|
||||
end
|
||||
|
||||
function core.serialize(value)
|
||||
local rope = {}
|
||||
serialize(value, function(text)
|
||||
-- Faster than table.insert(rope, text) on PUC Lua 5.1
|
||||
rope[#rope + 1] = text
|
||||
end)
|
||||
return table_concat(rope)
|
||||
end
|
||||
|
||||
local function dummy_func() end
|
||||
|
||||
function core.deserialize(str, safe)
|
||||
if type(str) ~= "string" then
|
||||
return nil, "Cannot deserialize type '"..type(str)
|
||||
.."'. Argument must be a string."
|
||||
-- Backwards compatibility
|
||||
if str == nil then
|
||||
core.log("deprecated", "minetest.deserialize called with nil (expected string).")
|
||||
return nil, "Invalid type: Expected a string, got nil"
|
||||
end
|
||||
if str:byte(1) == 0x1B then
|
||||
return nil, "Bytecode prohibited"
|
||||
local t = type(str)
|
||||
if t ~= "string" then
|
||||
error(("minetest.deserialize called with %s (expected string)."):format(t))
|
||||
end
|
||||
local f, err = loadstring(str)
|
||||
if not f then return nil, err end
|
||||
|
||||
-- The environment is recreated every time so deseralized code cannot
|
||||
-- pollute it with permanent references.
|
||||
setfenv(f, {loadstring = safe and dummy_func or safe_loadstring})
|
||||
local func, err = loadstring(str)
|
||||
if not func then return nil, err end
|
||||
|
||||
local good, data = pcall(f)
|
||||
if good then
|
||||
return data
|
||||
-- math.huge was serialized to inf and NaNs to nan by Lua in Minetest 5.6, so we have to support this here
|
||||
local env = {inf = math_huge, nan = 0/0}
|
||||
if safe then
|
||||
env.loadstring = dummy_func
|
||||
else
|
||||
return nil, data
|
||||
env.loadstring = function(str, ...)
|
||||
local func, err = loadstring(str, ...)
|
||||
if func then
|
||||
setfenv(func, env)
|
||||
return func
|
||||
end
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
setfenv(func, env)
|
||||
local success, value_or_err = pcall(func)
|
||||
if success then
|
||||
return value_or_err
|
||||
end
|
||||
return nil, value_or_err
|
||||
end
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
|
||||
-- Always warn when creating a global variable, even outside of a function.
|
||||
-- This ignores mod namespaces (variables with the same name as the current mod).
|
||||
local WARN_INIT = false
|
||||
|
||||
local getinfo = debug.getinfo
|
||||
local getinfo, rawget, rawset = debug.getinfo, rawget, rawset
|
||||
|
||||
function core.global_exists(name)
|
||||
if type(name) ~= "string" then
|
||||
@@ -15,43 +10,37 @@ end
|
||||
|
||||
local meta = {}
|
||||
local declared = {}
|
||||
-- Key is source file, line, and variable name; seperated by NULs
|
||||
-- Key is source file, line, and variable name; separated 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)
|
||||
if not declared[name] then
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source,
|
||||
info.currentline, name)
|
||||
if not warned[warn_key] and info.what ~= "main" and
|
||||
info.what ~= "C" then
|
||||
core.log("warning", ("Assignment to undeclared "..
|
||||
"global %q inside a function at %s.")
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
|
||||
core.log("warning", ("Assignment to undeclared global %q inside a function at %s.")
|
||||
:format(name, desc))
|
||||
warned[warn_key] = true
|
||||
end
|
||||
declared[name] = true
|
||||
warned[warn_key] = true
|
||||
end
|
||||
-- Ignore mod namespaces
|
||||
if WARN_INIT and name ~= core.get_current_modname() then
|
||||
core.log("warning", ("Global variable %q created at %s.")
|
||||
:format(name, desc))
|
||||
end
|
||||
rawset(self, name, value)
|
||||
declared[name] = true
|
||||
end
|
||||
|
||||
|
||||
function meta:__index(name)
|
||||
if declared[name] then
|
||||
return
|
||||
end
|
||||
local info = getinfo(2, "Sl")
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||
if not declared[name] and not warned[warn_key] and info.what ~= "C" then
|
||||
if not warned[warn_key] and info.what ~= "C" then
|
||||
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")
|
||||
:format(name, info.short_src, info.currentline))
|
||||
warned[warn_key] = true
|
||||
end
|
||||
return rawget(self, name)
|
||||
end
|
||||
|
||||
setmetatable(_G, meta)
|
||||
|
||||
|
||||
@@ -38,6 +38,12 @@ 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)
|
||||
|
||||
@@ -66,9 +72,107 @@ describe("pos", function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("area parsing", function()
|
||||
describe("valid inputs", function()
|
||||
it("accepts absolute numbers", function()
|
||||
local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2 4 -12.53)")
|
||||
assert(p1.x == 10 and p1.y == 5 and p1.z == -2)
|
||||
assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
|
||||
end)
|
||||
|
||||
it("accepts relative numbers", function()
|
||||
local p1, p2 = core.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})
|
||||
assert(type(p1) == "table" and type(p2) == "table")
|
||||
assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
|
||||
assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
|
||||
|
||||
p1, p2 = core.string_to_area("(1 2 3) (~5 ~-5 ~)", {x=10,y=10,z=10})
|
||||
assert(type(p1) == "table" and type(p2) == "table")
|
||||
assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
|
||||
assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
|
||||
end)
|
||||
end)
|
||||
describe("invalid inputs", function()
|
||||
it("rejects too few numbers", function()
|
||||
local p1, p2 = core.string_to_area("(1,1) (1,1,1,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("rejects too many numbers", function()
|
||||
local p1, p2 = core.string_to_area("(1,1,1,1) (1,1,1,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("rejects nan & inf", function()
|
||||
local p1, p2 = core.string_to_area("(1,1,1) (1,1,nan)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,1,~nan)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,~nan,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,1,inf)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,1,~inf)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(1,1,1) (1,~inf,1)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("rejects words", function()
|
||||
local p1, p2 = core.string_to_area("bananas", {x=1,y=1,z=1})
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("bananas", "foobar")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("bananas")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(bananas,bananas,bananas)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(bananas,bananas,bananas) (bananas,bananas,bananas)")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
|
||||
it("requires parenthesis & valid numbers", function()
|
||||
local p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
|
||||
p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
|
||||
assert(p1 == nil and p2 == nil)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("table", function()
|
||||
it("indexof()", function()
|
||||
assert.equal(1, table.indexof({"foo", "bar"}, "foo"))
|
||||
assert.equal(-1, table.indexof({"foo", "bar"}, "baz"))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("formspec_escape", function()
|
||||
it("escapes", function()
|
||||
assert.equal(nil, core.formspec_escape(nil))
|
||||
assert.equal("", core.formspec_escape(""))
|
||||
assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\["))
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -5,38 +5,92 @@ _G.setfenv = require 'busted.compatibility'.setfenv
|
||||
dofile("builtin/common/serialize.lua")
|
||||
dofile("builtin/common/vector.lua")
|
||||
|
||||
describe("serialize", function()
|
||||
it("works", function()
|
||||
local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
-- Supports circular tables; does not support table keys
|
||||
-- Correctly checks whether a mapping of references ("same") exists
|
||||
-- Is significantly more efficient than assert.same
|
||||
local function assert_same(a, b, same)
|
||||
same = same or {}
|
||||
if same[a] or same[b] then
|
||||
assert(same[a] == b and same[b] == a)
|
||||
return
|
||||
end
|
||||
if a == b then
|
||||
return
|
||||
end
|
||||
if type(a) ~= "table" or type(b) ~= "table" then
|
||||
assert(a == b)
|
||||
return
|
||||
end
|
||||
same[a] = b
|
||||
same[b] = a
|
||||
local count = 0
|
||||
for k, v in pairs(a) do
|
||||
count = count + 1
|
||||
assert(type(k) ~= "table")
|
||||
assert_same(v, b[k], same)
|
||||
end
|
||||
for _ in pairs(b) do
|
||||
count = count - 1
|
||||
end
|
||||
assert(count == 0)
|
||||
end
|
||||
|
||||
assert.same(test_in, test_out)
|
||||
local x, y = {}, {}
|
||||
local t1, t2 = {x, x, y, y}, {x, y, x, y}
|
||||
assert.same(t1, t2) -- will succeed because it only checks whether the depths match
|
||||
assert(not pcall(assert_same, t1, t2)) -- will correctly fail because it checks whether the refs match
|
||||
|
||||
describe("serialize", function()
|
||||
local function assert_preserves(value)
|
||||
local preserved_value = core.deserialize(core.serialize(value))
|
||||
assert_same(value, preserved_value)
|
||||
end
|
||||
it("works", function()
|
||||
assert_preserves({cat={sound="nyan", speed=400}, dog={sound="woof"}})
|
||||
end)
|
||||
|
||||
it("handles characters", function()
|
||||
local test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
assert_preserves({escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"})
|
||||
end)
|
||||
|
||||
it("handles NaN & infinities", function()
|
||||
local nan = core.deserialize(core.serialize(0/0))
|
||||
assert(nan ~= nan)
|
||||
assert_preserves(math.huge)
|
||||
assert_preserves(-math.huge)
|
||||
end)
|
||||
|
||||
it("handles precise numbers", function()
|
||||
local test_in = 0.2695949158945771
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
assert_preserves(0.2695949158945771)
|
||||
end)
|
||||
|
||||
it("handles big integers", function()
|
||||
local test_in = 269594915894577
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
assert_preserves(269594915894577)
|
||||
end)
|
||||
|
||||
it("handles recursive structures", function()
|
||||
local test_in = { hello = "world" }
|
||||
test_in.foo = test_in
|
||||
assert_preserves(test_in)
|
||||
end)
|
||||
|
||||
local test_out = core.deserialize(core.serialize(test_in))
|
||||
assert.same(test_in, test_out)
|
||||
it("handles cross-referencing structures", function()
|
||||
local test_in = {
|
||||
foo = {
|
||||
baz = {
|
||||
{}
|
||||
},
|
||||
},
|
||||
bar = {
|
||||
baz = {},
|
||||
},
|
||||
}
|
||||
|
||||
test_in.foo.baz[1].foo = test_in.foo
|
||||
test_in.foo.baz[1].bar = test_in.bar
|
||||
test_in.bar.baz[1] = test_in.foo.baz[1]
|
||||
|
||||
assert_preserves(test_in)
|
||||
end)
|
||||
|
||||
it("strips functions in safe mode", function()
|
||||
@@ -46,6 +100,7 @@ describe("serialize", function()
|
||||
end,
|
||||
foo = "bar"
|
||||
}
|
||||
setfenv(test_in.func, _G)
|
||||
|
||||
local str = core.serialize(test_in)
|
||||
assert.not_nil(str:find("loadstring"))
|
||||
@@ -57,13 +112,77 @@ describe("serialize", function()
|
||||
|
||||
it("vectors work", function()
|
||||
local v = vector.new(1, 2, 3)
|
||||
assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v})))
|
||||
assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v)))
|
||||
assert_preserves({v})
|
||||
assert_preserves(v)
|
||||
|
||||
-- abuse
|
||||
v = vector.new(1, 2, 3)
|
||||
v.a = "bla"
|
||||
assert.same({x = 1, y = 2, z = 3, a = "bla"},
|
||||
core.deserialize(core.serialize(v)))
|
||||
assert_preserves(v)
|
||||
end)
|
||||
|
||||
it("handles keywords as keys", function()
|
||||
assert_preserves({["and"] = "keyword", ["for"] = "keyword"})
|
||||
end)
|
||||
|
||||
describe("fuzzing", function()
|
||||
local atomics = {true, false, math.huge, -math.huge} -- no NaN or nil
|
||||
local function atomic()
|
||||
return atomics[math.random(1, #atomics)]
|
||||
end
|
||||
local function num()
|
||||
local sign = math.random() < 0.5 and -1 or 1
|
||||
-- HACK math.random(a, b) requires a, b & b - a to fit within a 32-bit int
|
||||
-- Use two random calls to generate a random number from 0 - 2^50 as lower & upper 25 bits
|
||||
local val = math.random(0, 2^25) * 2^25 + math.random(0, 2^25 - 1)
|
||||
local exp = math.random() < 0.5 and 1 or 2^(math.random(-120, 120))
|
||||
return sign * val * exp
|
||||
end
|
||||
local function charcodes(count)
|
||||
if count == 0 then return end
|
||||
return math.random(0, 0xFF), charcodes(count - 1)
|
||||
end
|
||||
local function str()
|
||||
return string.char(charcodes(math.random(0, 100)))
|
||||
end
|
||||
local primitives = {atomic, num, str}
|
||||
local function primitive()
|
||||
return primitives[math.random(1, #primitives)]()
|
||||
end
|
||||
local function tab(max_actions)
|
||||
local root = {}
|
||||
local tables = {root}
|
||||
local function random_table()
|
||||
return tables[math.random(1, #tables)]
|
||||
end
|
||||
for _ = 1, math.random(1, max_actions) do
|
||||
local tab = random_table()
|
||||
local value
|
||||
if math.random() < 0.5 then
|
||||
if math.random() < 0.5 then
|
||||
value = random_table()
|
||||
else
|
||||
value = {}
|
||||
table.insert(tables, value)
|
||||
end
|
||||
else
|
||||
value = primitive()
|
||||
end
|
||||
tab[math.random() < 0.5 and (#tab + 1) or primitive()] = value
|
||||
end
|
||||
return root
|
||||
end
|
||||
it("primitives work", function()
|
||||
for _ = 1, 1e3 do
|
||||
assert_preserves(primitive())
|
||||
end
|
||||
end)
|
||||
it("tables work", function()
|
||||
for _ = 1, 100 do
|
||||
local fuzzed_table = tab(1e3)
|
||||
assert_same(fuzzed_table, table.copy(fuzzed_table))
|
||||
assert_preserves(fuzzed_table)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -128,6 +128,14 @@ describe("vector", function()
|
||||
assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
|
||||
end)
|
||||
|
||||
it("combine()", function()
|
||||
local a = vector.new(1, 2, 3)
|
||||
local b = vector.new(3, 2, 1)
|
||||
assert.equal(vector.add(a, b), vector.combine(a, b, function(x, y) return x + y end))
|
||||
assert.equal(vector.new(3, 2, 3), vector.combine(a, b, math.max))
|
||||
assert.equal(vector.new(1, 2, 1), vector.combine(a, b, math.min))
|
||||
end)
|
||||
|
||||
it("equals()", function()
|
||||
local function assertE(a, b)
|
||||
assert.is_true(vector.equals(a, b))
|
||||
@@ -454,4 +462,11 @@ describe("vector", function()
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
it("in_area()", function()
|
||||
assert.True(vector.in_area(vector.zero(), vector.new(-10, -10, -10), vector.new(10, 10, 10)))
|
||||
assert.True(vector.in_area(vector.new(-2, 5, -8), vector.new(-10, -10, -10), vector.new(10, 10, 10)))
|
||||
assert.True(vector.in_area(vector.new(-10, -10, -10), vector.new(-10, -10, -10), vector.new(10, 10, 10)))
|
||||
assert.False(vector.in_area(vector.new(-10, -10, -10), vector.new(10, 10, 10), vector.new(-11, -10, -10)))
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -81,7 +81,7 @@ metatable.__eq = vector.equals
|
||||
function vector.length(v)
|
||||
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||
end
|
||||
-- Note: we can not use __len because it is already used for primitive table length
|
||||
-- Note: we cannot use __len because it is already used for primitive table length
|
||||
|
||||
function vector.normalize(v)
|
||||
local len = vector.length(v)
|
||||
@@ -112,6 +112,14 @@ function vector.apply(v, func)
|
||||
)
|
||||
end
|
||||
|
||||
function vector.combine(a, b, func)
|
||||
return fast_new(
|
||||
func(a.x, b.x),
|
||||
func(a.y, b.y),
|
||||
func(a.z, b.z)
|
||||
)
|
||||
end
|
||||
|
||||
function vector.distance(a, b)
|
||||
local x = a.x - b.x
|
||||
local y = a.y - b.y
|
||||
@@ -348,7 +356,7 @@ function vector.dir_to_rotation(forward, 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 wether this is the case, we rotate the up vector back around
|
||||
-- To determine whether 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)
|
||||
|
||||
@@ -360,3 +368,28 @@ function vector.dir_to_rotation(forward, up)
|
||||
end
|
||||
return rot
|
||||
end
|
||||
|
||||
function vector.in_area(pos, min, max)
|
||||
return (pos.x >= min.x) and (pos.x <= max.x) and
|
||||
(pos.y >= min.y) and (pos.y <= max.y) and
|
||||
(pos.z >= min.z) and (pos.z <= max.z)
|
||||
end
|
||||
|
||||
if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then
|
||||
local function read_vector(v)
|
||||
return v.x, v.y, v.z
|
||||
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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2014 sapier
|
||||
--Copyright (C) 2023 Gregor Parzefall
|
||||
--
|
||||
--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
|
||||
@@ -16,115 +17,101 @@
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
|
||||
local function buttonbar_formspec(self)
|
||||
local BASE_SPACING = 0.1
|
||||
local SCROLL_BTN_WIDTH = TOUCHSCREEN_GUI and 0.8 or 0.5
|
||||
|
||||
local function buttonbar_formspec(self)
|
||||
if self.hidden then
|
||||
return ""
|
||||
end
|
||||
|
||||
local formspec = string.format("box[%f,%f;%f,%f;%s]",
|
||||
self.pos.x,self.pos.y ,self.size.x,self.size.y,self.bgcolor)
|
||||
local formspec = {
|
||||
"style_type[box;noclip=true]",
|
||||
string.format("box[%f,%f;%f,%f;%s]", self.pos.x, self.pos.y, self.size.x,
|
||||
self.size.y, self.bgcolor),
|
||||
"style_type[box;noclip=false]",
|
||||
}
|
||||
|
||||
for i=self.startbutton,#self.buttons,1 do
|
||||
local btn_name = self.buttons[i].name
|
||||
local btn_pos = {}
|
||||
local btn_size = self.size.y - 2*BASE_SPACING
|
||||
|
||||
if self.orientation == "horizontal" then
|
||||
btn_pos.x = self.pos.x + --base pos
|
||||
(i - self.startbutton) * self.btn_size + --button offset
|
||||
self.btn_initial_offset
|
||||
else
|
||||
btn_pos.x = self.pos.x + (self.btn_size * 0.05)
|
||||
end
|
||||
-- Spacing works like CSS Flexbox with `justify-content: space-evenly;`.
|
||||
-- `BASE_SPACING` is used as the minimum spacing, like `gap` in CSS Flexbox.
|
||||
|
||||
if self.orientation == "vertical" then
|
||||
btn_pos.y = self.pos.y + --base pos
|
||||
(i - self.startbutton) * self.btn_size + --button offset
|
||||
self.btn_initial_offset
|
||||
else
|
||||
btn_pos.y = self.pos.y + (self.btn_size * 0.05)
|
||||
end
|
||||
-- The number of buttons per page is always calculated as if the scroll
|
||||
-- buttons were visible.
|
||||
local avail_space = self.size.x - 2*BASE_SPACING - 2*SCROLL_BTN_WIDTH
|
||||
local btns_per_page = math.floor((avail_space - BASE_SPACING) / (btn_size + BASE_SPACING))
|
||||
|
||||
if (self.orientation == "vertical" and
|
||||
(btn_pos.y + self.btn_size <= self.pos.y + self.size.y)) or
|
||||
(self.orientation == "horizontal" and
|
||||
(btn_pos.x + self.btn_size <= self.pos.x + self.size.x)) then
|
||||
self.num_pages = math.ceil(#self.buttons / btns_per_page)
|
||||
self.cur_page = math.min(self.cur_page, self.num_pages)
|
||||
local first_btn = (self.cur_page - 1) * btns_per_page + 1
|
||||
|
||||
local borders="true"
|
||||
local show_scroll_btns = self.num_pages > 1
|
||||
|
||||
if self.buttons[i].image ~= nil then
|
||||
borders="false"
|
||||
end
|
||||
-- In contrast, the button spacing calculation takes hidden scroll buttons
|
||||
-- into account.
|
||||
local real_avail_space = show_scroll_btns and avail_space or self.size.x
|
||||
local btn_spacing = (real_avail_space - btns_per_page * btn_size) / (btns_per_page + 1)
|
||||
|
||||
formspec = formspec ..
|
||||
string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;%s]tooltip[%s;%s]",
|
||||
btn_pos.x, btn_pos.y, self.btn_size, self.btn_size,
|
||||
self.buttons[i].image, btn_name, self.buttons[i].caption,
|
||||
borders, btn_name, self.buttons[i].tooltip)
|
||||
else
|
||||
--print("end of displayable buttons: orientation: " .. self.orientation)
|
||||
--print( "button_end: " .. (btn_pos.y + self.btn_size - (self.btn_size * 0.05)))
|
||||
--print( "bar_end: " .. (self.pos.x + self.size.x))
|
||||
local btn_start_x = self.pos.x + btn_spacing
|
||||
if show_scroll_btns then
|
||||
btn_start_x = btn_start_x + BASE_SPACING + SCROLL_BTN_WIDTH
|
||||
end
|
||||
|
||||
for i = first_btn, first_btn + btns_per_page - 1 do
|
||||
local btn = self.buttons[i]
|
||||
if btn == nil then
|
||||
break
|
||||
end
|
||||
|
||||
local btn_pos = {
|
||||
x = btn_start_x + (i - first_btn) * (btn_size + btn_spacing),
|
||||
y = self.pos.y + BASE_SPACING,
|
||||
}
|
||||
|
||||
table.insert(formspec, string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;false]tooltip[%s;%s]",
|
||||
btn_pos.x, btn_pos.y, btn_size, btn_size, btn.image, btn.name,
|
||||
btn.caption, btn.name, btn.tooltip))
|
||||
end
|
||||
|
||||
if (self.have_move_buttons) then
|
||||
local btn_dec_pos = {}
|
||||
btn_dec_pos.x = self.pos.x + (self.btn_size * 0.05)
|
||||
btn_dec_pos.y = self.pos.y + (self.btn_size * 0.05)
|
||||
local btn_inc_pos = {}
|
||||
local btn_size = {}
|
||||
if show_scroll_btns then
|
||||
local btn_prev_pos = {
|
||||
x = self.pos.x + BASE_SPACING,
|
||||
y = self.pos.y + BASE_SPACING,
|
||||
}
|
||||
local btn_next_pos = {
|
||||
x = self.pos.x + self.size.x - BASE_SPACING - SCROLL_BTN_WIDTH,
|
||||
y = self.pos.y + BASE_SPACING,
|
||||
}
|
||||
|
||||
if self.orientation == "horizontal" then
|
||||
btn_size.x = 0.5
|
||||
btn_size.y = self.btn_size
|
||||
btn_inc_pos.x = self.pos.x + self.size.x - 0.5
|
||||
btn_inc_pos.y = self.pos.y + (self.btn_size * 0.05)
|
||||
else
|
||||
btn_size.x = self.btn_size
|
||||
btn_size.y = 0.5
|
||||
btn_inc_pos.x = self.pos.x + (self.btn_size * 0.05)
|
||||
btn_inc_pos.y = self.pos.y + self.size.y - 0.5
|
||||
end
|
||||
table.insert(formspec, string.format("style[%s,%s;noclip=true]",
|
||||
self.btn_prev_name, self.btn_next_name))
|
||||
|
||||
local text_dec = "<"
|
||||
local text_inc = ">"
|
||||
if self.orientation == "vertical" then
|
||||
text_dec = "^"
|
||||
text_inc = "v"
|
||||
end
|
||||
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;<]",
|
||||
btn_prev_pos.x, btn_prev_pos.y, SCROLL_BTN_WIDTH, btn_size,
|
||||
self.btn_prev_name))
|
||||
|
||||
formspec = formspec ..
|
||||
string.format("image_button[%f,%f;%f,%f;;btnbar_dec_%s;%s;true;true]",
|
||||
btn_dec_pos.x, btn_dec_pos.y, btn_size.x, btn_size.y,
|
||||
self.name, text_dec)
|
||||
|
||||
formspec = formspec ..
|
||||
string.format("image_button[%f,%f;%f,%f;;btnbar_inc_%s;%s;true;true]",
|
||||
btn_inc_pos.x, btn_inc_pos.y, btn_size.x, btn_size.y,
|
||||
self.name, text_inc)
|
||||
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;>]",
|
||||
btn_next_pos.x, btn_next_pos.y, SCROLL_BTN_WIDTH, btn_size,
|
||||
self.btn_next_name))
|
||||
end
|
||||
|
||||
return formspec
|
||||
return table.concat(formspec)
|
||||
end
|
||||
|
||||
local function buttonbar_buttonhandler(self, fields)
|
||||
|
||||
if fields["btnbar_inc_" .. self.name] ~= nil and
|
||||
self.startbutton < #self.buttons then
|
||||
|
||||
self.startbutton = self.startbutton + 1
|
||||
if fields[self.btn_prev_name] and self.cur_page > 1 then
|
||||
self.cur_page = self.cur_page - 1
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["btnbar_dec_" .. self.name] ~= nil and self.startbutton > 1 then
|
||||
self.startbutton = self.startbutton - 1
|
||||
if fields[self.btn_next_name] and self.cur_page < self.num_pages then
|
||||
self.cur_page = self.cur_page + 1
|
||||
return true
|
||||
end
|
||||
|
||||
for i=1,#self.buttons,1 do
|
||||
if fields[self.buttons[i].name] ~= nil then
|
||||
for _, btn in ipairs(self.buttons) do
|
||||
if fields[btn.name] then
|
||||
return self.userbuttonhandler(fields)
|
||||
end
|
||||
end
|
||||
@@ -141,74 +128,45 @@ local buttonbar_metatable = {
|
||||
delete = function(self) ui.delete(self) end,
|
||||
|
||||
add_button = function(self, name, caption, image, tooltip)
|
||||
if caption == nil then caption = "" end
|
||||
if image == nil then image = "" end
|
||||
if tooltip == nil then tooltip = "" end
|
||||
if caption == nil then caption = "" end
|
||||
if image == nil then image = "" end
|
||||
if tooltip == nil then tooltip = "" end
|
||||
|
||||
self.buttons[#self.buttons + 1] = {
|
||||
name = name,
|
||||
caption = caption,
|
||||
image = image,
|
||||
tooltip = tooltip
|
||||
}
|
||||
if self.orientation == "horizontal" then
|
||||
if ( (self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2)
|
||||
> self.size.x ) then
|
||||
|
||||
self.btn_initial_offset = self.btn_size * 0.05 + 0.5
|
||||
self.have_move_buttons = true
|
||||
end
|
||||
else
|
||||
if ((self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2)
|
||||
> self.size.y ) then
|
||||
|
||||
self.btn_initial_offset = self.btn_size * 0.05 + 0.5
|
||||
self.have_move_buttons = true
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
set_bgparams = function(self, bgcolor)
|
||||
if (type(bgcolor) == "string") then
|
||||
self.bgcolor = bgcolor
|
||||
end
|
||||
end,
|
||||
table.insert(self.buttons, {
|
||||
name = name,
|
||||
caption = caption,
|
||||
image = image,
|
||||
tooltip = tooltip,
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
buttonbar_metatable.__index = buttonbar_metatable
|
||||
|
||||
function buttonbar_create(name, cbf_buttonhandler, pos, orientation, size)
|
||||
assert(name ~= nil)
|
||||
assert(cbf_buttonhandler ~= nil)
|
||||
assert(orientation == "vertical" or orientation == "horizontal")
|
||||
assert(pos ~= nil and type(pos) == "table")
|
||||
assert(size ~= nil and type(size) == "table")
|
||||
function buttonbar_create(name, pos, size, bgcolor, cbf_buttonhandler)
|
||||
assert(type(name) == "string" )
|
||||
assert(type(pos) == "table" )
|
||||
assert(type(size) == "table" )
|
||||
assert(type(bgcolor) == "string" )
|
||||
assert(type(cbf_buttonhandler) == "function")
|
||||
|
||||
local self = {}
|
||||
self.name = name
|
||||
self.type = "addon"
|
||||
self.bgcolor = "#000000"
|
||||
self.name = name
|
||||
self.pos = pos
|
||||
self.size = size
|
||||
self.orientation = orientation
|
||||
self.startbutton = 1
|
||||
self.have_move_buttons = false
|
||||
self.hidden = false
|
||||
|
||||
if self.orientation == "horizontal" then
|
||||
self.btn_size = self.size.y
|
||||
else
|
||||
self.btn_size = self.size.x
|
||||
end
|
||||
|
||||
if (self.btn_initial_offset == nil) then
|
||||
self.btn_initial_offset = self.btn_size * 0.05
|
||||
end
|
||||
|
||||
self.bgcolor = bgcolor
|
||||
self.userbuttonhandler = cbf_buttonhandler
|
||||
self.buttons = {}
|
||||
|
||||
setmetatable(self,buttonbar_metatable)
|
||||
self.hidden = false
|
||||
self.buttons = {}
|
||||
self.num_pages = 1
|
||||
self.cur_page = 1
|
||||
|
||||
self.btn_prev_name = "btnbar_prev_" .. self.name
|
||||
self.btn_next_name = "btnbar_next_" .. self.name
|
||||
|
||||
setmetatable(self, buttonbar_metatable)
|
||||
|
||||
ui.add(self)
|
||||
return self
|
||||
|
||||
@@ -38,8 +38,18 @@ local dialog_metatable = {
|
||||
handle_events = function(self,event)
|
||||
if not self.hidden then return self.eventhandler(self,event) end
|
||||
end,
|
||||
hide = function(self) self.hidden = true end,
|
||||
show = function(self) self.hidden = false end,
|
||||
hide = function(self)
|
||||
if not self.hidden then
|
||||
self.hidden = true
|
||||
self.eventhandler(self, "DialogHide")
|
||||
end
|
||||
end,
|
||||
show = function(self)
|
||||
if self.hidden then
|
||||
self.hidden = false
|
||||
self.eventhandler(self, "DialogShow")
|
||||
end
|
||||
end,
|
||||
delete = function(self)
|
||||
if self.parent ~= nil then
|
||||
self.parent:show()
|
||||
|
||||
@@ -42,6 +42,7 @@ local function add_tab(self,tab)
|
||||
event_handler = tab.cbf_events,
|
||||
get_formspec = tab.cbf_formspec,
|
||||
tabsize = tab.tabsize,
|
||||
formspec_version = tab.formspec_version or 6,
|
||||
on_change = tab.on_change,
|
||||
tabdata = {},
|
||||
}
|
||||
@@ -65,13 +66,38 @@ local function get_formspec(self)
|
||||
|
||||
local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize)
|
||||
|
||||
local tsize = tab.tabsize or { width = self.width, height = self.height }
|
||||
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))
|
||||
|
||||
if tab.formspec_version then
|
||||
prepend = ("formspec_version[%d]"):format(tab.formspec_version) .. prepend
|
||||
end
|
||||
end
|
||||
|
||||
local end_button_size = 0.75
|
||||
|
||||
local tab_header_size = { width = tsize.width, height = 0.85 }
|
||||
if self.end_button then
|
||||
tab_header_size.width = tab_header_size.width - end_button_size - 0.1
|
||||
end
|
||||
|
||||
local formspec = (prepend or "") .. self:tab_header(tab_header_size) .. content
|
||||
|
||||
if self.end_button then
|
||||
formspec = formspec ..
|
||||
("style[%s;noclip=true;border=false]"):format(self.end_button.name) ..
|
||||
("tooltip[%s;%s]"):format(self.end_button.name, self.end_button.label) ..
|
||||
("image_button[%f,%f;%f,%f;%s;%s;]"):format(
|
||||
self.width - end_button_size,
|
||||
(-tab_header_size.height - end_button_size) / 2,
|
||||
end_button_size,
|
||||
end_button_size,
|
||||
core.formspec_escape(self.end_button.icon),
|
||||
self.end_button.name)
|
||||
end
|
||||
|
||||
local formspec = (prepend or "") .. self:tab_header() .. content
|
||||
return formspec
|
||||
end
|
||||
|
||||
@@ -86,8 +112,12 @@ local function handle_buttons(self,fields)
|
||||
return true
|
||||
end
|
||||
|
||||
if self.end_button and fields[self.end_button.name] then
|
||||
return self.end_button.on_click(self)
|
||||
end
|
||||
|
||||
if self.glb_btn_handler ~= nil and
|
||||
self.glb_btn_handler(self,fields) then
|
||||
self.glb_btn_handler(self, fields) then
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -121,20 +151,23 @@ end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
local function tab_header(self)
|
||||
|
||||
local function tab_header(self, size)
|
||||
local toadd = ""
|
||||
|
||||
for i=1,#self.tablist,1 do
|
||||
|
||||
for i = 1, #self.tablist do
|
||||
if toadd ~= "" then
|
||||
toadd = toadd .. ","
|
||||
end
|
||||
|
||||
toadd = toadd .. self.tablist[i].caption
|
||||
local caption = self.tablist[i].caption
|
||||
if type(caption) == "function" then
|
||||
caption = caption(self)
|
||||
end
|
||||
|
||||
toadd = toadd .. caption
|
||||
end
|
||||
return string.format("tabheader[%f,%f;%s;%s;%i;true;false]",
|
||||
self.header_x, self.header_y, self.name, toadd, self.last_tab_index);
|
||||
return string.format("tabheader[%f,%f;%f,%f;%s;%s;%i;true;false]",
|
||||
self.header_x, self.header_y, size.width, size.height, self.name, toadd, self.last_tab_index)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -225,6 +258,8 @@ local tabview_metatable = {
|
||||
function(self,handler) self.glb_evt_handler = handler end,
|
||||
set_fixed_size =
|
||||
function(self,state) self.fixed_size = state end,
|
||||
set_end_button =
|
||||
function(self, v) self.end_button = v end,
|
||||
tab_header = tab_header,
|
||||
handle_tab_buttons = handle_tab_buttons
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ 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>"))
|
||||
local error_message = core.formspec_escape(gamedata.errormessage)
|
||||
or fgettext("<none available>")
|
||||
formspec = {
|
||||
"size[14,8]",
|
||||
"real_coordinates[true]",
|
||||
|
||||
22
builtin/game/async.lua
Normal file
22
builtin/game/async.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
core.async_jobs = {}
|
||||
|
||||
function core.async_event_handler(jobid, retval)
|
||||
local callback = core.async_jobs[jobid]
|
||||
assert(type(callback) == "function")
|
||||
callback(unpack(retval, 1, retval.n))
|
||||
core.async_jobs[jobid] = nil
|
||||
end
|
||||
|
||||
function core.handle_async(func, callback, ...)
|
||||
assert(type(func) == "function" and type(callback) == "function",
|
||||
"Invalid minetest.handle_async invocation")
|
||||
local args = {n = select("#", ...), ...}
|
||||
local mod_origin = core.get_last_run_mod()
|
||||
|
||||
local jobid = core.do_async_callback(func, args, mod_origin)
|
||||
core.async_jobs[jobid] = callback
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -130,8 +130,13 @@ local function parse_range_str(player_name, str)
|
||||
return false, S("Unable to get position of player @1.", player_name)
|
||||
end
|
||||
else
|
||||
p1, p2 = core.string_to_area(str)
|
||||
if p1 == nil then
|
||||
local player = core.get_player_by_name(player_name)
|
||||
local relpos
|
||||
if player then
|
||||
relpos = player:get_pos()
|
||||
end
|
||||
p1, p2 = core.string_to_area(str, relpos)
|
||||
if p1 == nil or p2 == nil then
|
||||
return false, S("Incorrect area format. "
|
||||
.. "Expected: (x1,y1,z1) (x2,y2,z2)")
|
||||
end
|
||||
@@ -310,12 +315,7 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
|
||||
and revokename == core.settings:get("name")
|
||||
and revokename ~= ""
|
||||
if revokeprivstr == "all" then
|
||||
revokeprivs = privs
|
||||
privs = {}
|
||||
else
|
||||
for priv, _ in pairs(revokeprivs) do
|
||||
privs[priv] = nil
|
||||
end
|
||||
revokeprivs = table.copy(privs)
|
||||
end
|
||||
|
||||
local privs_unknown = ""
|
||||
@@ -332,7 +332,10 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
|
||||
end
|
||||
local def = core.registered_privileges[priv]
|
||||
if not def then
|
||||
privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
|
||||
-- Old/removed privileges might still be granted to certain players
|
||||
if not privs[priv] then
|
||||
privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
|
||||
end
|
||||
elseif is_singleplayer and def.give_to_singleplayer then
|
||||
irrevokable[priv] = true
|
||||
elseif is_admin and def.give_to_admin then
|
||||
@@ -359,19 +362,22 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
|
||||
end
|
||||
|
||||
local revokecount = 0
|
||||
for priv, _ in pairs(revokeprivs) do
|
||||
privs[priv] = nil
|
||||
revokecount = revokecount + 1
|
||||
end
|
||||
|
||||
if revokecount == 0 then
|
||||
return false, S("No privileges were revoked.")
|
||||
end
|
||||
|
||||
core.set_player_privs(revokename, privs)
|
||||
for priv, _ in pairs(revokeprivs) do
|
||||
-- call the on_revoke callbacks
|
||||
core.run_priv_callbacks(revokename, priv, caller, "revoke")
|
||||
revokecount = revokecount + 1
|
||||
end
|
||||
local new_privs = core.get_player_privs(revokename)
|
||||
|
||||
if revokecount == 0 then
|
||||
return false, S("No privileges were revoked.")
|
||||
end
|
||||
|
||||
core.log("action", caller..' revoked ('
|
||||
..core.privs_to_string(revokeprivs, ', ')
|
||||
..') privileges from '..revokename)
|
||||
@@ -410,7 +416,7 @@ core.register_chatcommand("revokeme", {
|
||||
|
||||
core.register_chatcommand("setpassword", {
|
||||
params = S("<name> <password>"),
|
||||
description = S("Set player's password"),
|
||||
description = S("Set player's password (sent unencrypted, thus insecure)"),
|
||||
privs = {password=true},
|
||||
func = function(name, param)
|
||||
local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
|
||||
@@ -569,10 +575,15 @@ core.register_chatcommand("teleport", {
|
||||
description = S("Teleport to position or player"),
|
||||
privs = {teleport=true},
|
||||
func = function(name, param)
|
||||
local player = core.get_player_by_name(name)
|
||||
local relpos
|
||||
if player then
|
||||
relpos = player:get_pos()
|
||||
end
|
||||
local p = {}
|
||||
p.x, p.y, p.z = param:match("^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
|
||||
p = vector.apply(p, tonumber)
|
||||
if p.x and p.y and p.z then
|
||||
p.x, p.y, p.z = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
|
||||
p = core.parse_coordinates(p.x, p.y, p.z, relpos)
|
||||
if p and p.x and p.y and p.z then
|
||||
return teleport_to_pos(name, p)
|
||||
end
|
||||
|
||||
@@ -586,9 +597,19 @@ core.register_chatcommand("teleport", {
|
||||
"other players (missing privilege: @1).", "bring")
|
||||
|
||||
local teleportee_name
|
||||
p = {}
|
||||
teleportee_name, p.x, p.y, p.z = param:match(
|
||||
"^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
|
||||
"^([^ ]+) +([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
|
||||
if teleportee_name then
|
||||
local teleportee = core.get_player_by_name(teleportee_name)
|
||||
if not teleportee then
|
||||
return
|
||||
end
|
||||
relpos = teleportee:get_pos()
|
||||
p = core.parse_coordinates(p.x, p.y, p.z, relpos)
|
||||
end
|
||||
p = vector.apply(p, tonumber)
|
||||
|
||||
if teleportee_name and p.x and p.y and p.z then
|
||||
if not has_bring_priv then
|
||||
return false, missing_bring_msg
|
||||
@@ -621,6 +642,10 @@ core.register_chatcommand("set", {
|
||||
|
||||
setname, setvalue = string.match(param, "([^ ]+) (.+)")
|
||||
if setname and setvalue then
|
||||
if setname:sub(1, 7) == "secure." then
|
||||
return false, S("Failed. Cannot modify secure settings. "
|
||||
.. "Edit the settings file manually.")
|
||||
end
|
||||
if not core.settings:get(setname) then
|
||||
return false, S("Failed. Use '/set -n <name> <value>' "
|
||||
.. "to create a new setting.")
|
||||
@@ -837,7 +862,7 @@ core.register_chatcommand("spawnentity", {
|
||||
description = S("Spawn entity at given (or your) position"),
|
||||
privs = {give=true, interact=true},
|
||||
func = function(name, param)
|
||||
local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
|
||||
local entityname, pstr = string.match(param, "^([^ ]+) *(.*)$")
|
||||
if not entityname then
|
||||
return false, S("EntityName required.")
|
||||
end
|
||||
@@ -851,11 +876,15 @@ core.register_chatcommand("spawnentity", {
|
||||
if not core.registered_entities[entityname] then
|
||||
return false, S("Cannot spawn an unknown entity.")
|
||||
end
|
||||
if p == "" then
|
||||
local p
|
||||
if pstr == "" then
|
||||
p = player:get_pos()
|
||||
else
|
||||
p = core.string_to_pos(p)
|
||||
if p == nil then
|
||||
p = {}
|
||||
p.x, p.y, p.z = string.match(pstr, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
|
||||
local relpos = player:get_pos()
|
||||
p = core.parse_coordinates(p.x, p.y, p.z, relpos)
|
||||
if not (p and p.x and p.y and p.z) then
|
||||
return false, S("Invalid parameters (@1).", param)
|
||||
end
|
||||
end
|
||||
@@ -1014,6 +1043,13 @@ core.register_chatcommand("status", {
|
||||
end,
|
||||
})
|
||||
|
||||
local function get_time(timeofday)
|
||||
local time = math.floor(timeofday * 1440)
|
||||
local minute = time % 60
|
||||
local hour = (time - minute) / 60
|
||||
return time, hour, minute
|
||||
end
|
||||
|
||||
core.register_chatcommand("time", {
|
||||
params = S("[<0..23>:<0..59> | <0..24000>]"),
|
||||
description = S("Show or set time of day"),
|
||||
@@ -1032,9 +1068,14 @@ core.register_chatcommand("time", {
|
||||
return false, S("You don't have permission to run "
|
||||
.. "this command (missing privilege: @1).", "settime")
|
||||
end
|
||||
local hour, minute = param:match("^(%d+):(%d+)$")
|
||||
if not hour then
|
||||
local new_time = tonumber(param) or -1
|
||||
local relative, negative, hour, minute = param:match("^(~?)(%-?)(%d+):(%d+)$")
|
||||
if not relative then -- checking the first capture against nil suffices
|
||||
local new_time = core.parse_relative_number(param, core.get_timeofday() * 24000)
|
||||
if not new_time then
|
||||
new_time = tonumber(param) or -1
|
||||
else
|
||||
new_time = new_time % 24000
|
||||
end
|
||||
if new_time ~= new_time or new_time < 0 or new_time > 24000 then
|
||||
return false, S("Invalid time (must be between 0 and 24000).")
|
||||
end
|
||||
@@ -1042,14 +1083,29 @@ core.register_chatcommand("time", {
|
||||
core.log("action", name .. " sets time to " .. new_time)
|
||||
return true, S("Time of day changed.")
|
||||
end
|
||||
local new_time
|
||||
hour = tonumber(hour)
|
||||
minute = tonumber(minute)
|
||||
if hour < 0 or hour > 23 then
|
||||
return false, S("Invalid hour (must be between 0 and 23 inclusive).")
|
||||
elseif minute < 0 or minute > 59 then
|
||||
return false, S("Invalid minute (must be between 0 and 59 inclusive).")
|
||||
if relative == "" then
|
||||
if hour < 0 or hour > 23 then
|
||||
return false, S("Invalid hour (must be between 0 and 23 inclusive).")
|
||||
elseif minute < 0 or minute > 59 then
|
||||
return false, S("Invalid minute (must be between 0 and 59 inclusive).")
|
||||
end
|
||||
new_time = (hour * 60 + minute) / 1440
|
||||
else
|
||||
if minute < 0 or minute > 59 then
|
||||
return false, S("Invalid minute (must be between 0 and 59 inclusive).")
|
||||
end
|
||||
local current_time = core.get_timeofday()
|
||||
if negative == "-" then -- negative time
|
||||
hour, minute = -hour, -minute
|
||||
end
|
||||
new_time = (current_time + (hour * 60 + minute) / 1440) % 1
|
||||
local _
|
||||
_, hour, minute = get_time(new_time)
|
||||
end
|
||||
core.set_timeofday((hour * 60 + minute) / 1440)
|
||||
core.set_timeofday(new_time)
|
||||
core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
|
||||
return true, S("Time of day changed.")
|
||||
end,
|
||||
@@ -1131,6 +1187,9 @@ core.register_chatcommand("ban", {
|
||||
return true, S("Ban list: @1", ban_list)
|
||||
end
|
||||
end
|
||||
if core.is_singleplayer() then
|
||||
return false, S("You cannot ban players in singleplayer!")
|
||||
end
|
||||
if not core.get_player_by_name(param) then
|
||||
return false, S("Player is not online.")
|
||||
end
|
||||
|
||||
@@ -158,7 +158,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
|
||||
local fdir = node.param2 % 32 % 24
|
||||
-- Get rotation from a precalculated lookup table
|
||||
local euler = facedir_to_euler[fdir + 1]
|
||||
if euler then
|
||||
self.object:set_rotation(euler)
|
||||
end
|
||||
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
|
||||
@@ -264,7 +271,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
end
|
||||
|
||||
-- Decide if we're replacing the node or placing on top
|
||||
local np = vector.new(bcp)
|
||||
local np = vector.copy(bcp)
|
||||
if bcd and bcd.buildable_to and
|
||||
(not self.floats or bcd.liquidtype == "none") then
|
||||
core.remove_node(bcp)
|
||||
@@ -436,7 +443,7 @@ local function drop_attached_node(p)
|
||||
if def and def.preserve_metadata then
|
||||
local oldmeta = core.get_meta(p):to_table().fields
|
||||
-- Copy pos and node because the callback can modify them.
|
||||
local pos_copy = vector.new(p)
|
||||
local pos_copy = vector.copy(p)
|
||||
local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
|
||||
local drop_stacks = {}
|
||||
for k, v in pairs(drops) do
|
||||
@@ -459,15 +466,39 @@ local function drop_attached_node(p)
|
||||
end
|
||||
end
|
||||
|
||||
function builtin_shared.check_attached_node(p, n)
|
||||
function builtin_shared.check_attached_node(p, n, group_rating)
|
||||
local def = core.registered_nodes[n.name]
|
||||
local d = vector.new()
|
||||
if def.paramtype2 == "wallmounted" or
|
||||
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
|
||||
def.paramtype2 == "colorwallmounted" then
|
||||
-- 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.
|
||||
-- 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)
|
||||
else
|
||||
d.y = -1
|
||||
@@ -498,22 +529,23 @@ function core.check_single_for_falling(p)
|
||||
if same and d_bottom.paramtype2 == "leveled" and
|
||||
core.get_node_level(p_bottom) <
|
||||
core.get_node_max_level(p_bottom) then
|
||||
convert_to_falling_node(p, n)
|
||||
return true
|
||||
local success, _ = convert_to_falling_node(p, n)
|
||||
return success
|
||||
end
|
||||
-- Otherwise only if the bottom node is considered "fall through"
|
||||
if not same and
|
||||
(not d_bottom.walkable or d_bottom.buildable_to) and
|
||||
(core.get_item_group(n.name, "float") == 0 or
|
||||
d_bottom.liquidtype == "none") then
|
||||
convert_to_falling_node(p, n)
|
||||
return true
|
||||
local success, _ = convert_to_falling_node(p, n)
|
||||
return success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if core.get_item_group(n.name, "attached_node") ~= 0 then
|
||||
if not builtin_shared.check_attached_node(p, n) then
|
||||
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
|
||||
drop_attached_node(p)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -21,7 +21,14 @@ core.features = {
|
||||
use_texture_alpha_string_modes = true,
|
||||
degrotate_240_steps = true,
|
||||
abm_min_max_y = true,
|
||||
particlespawner_tweenable = true,
|
||||
dynamic_add_media_table = true,
|
||||
get_sky_as_table = true,
|
||||
get_light_data_buffer = true,
|
||||
mod_storage_on_disk = true,
|
||||
compress_zstd = true,
|
||||
sound_params_start_time = true,
|
||||
physics_overrides_v2 = true,
|
||||
}
|
||||
|
||||
function core.has_feature(arg)
|
||||
|
||||
@@ -33,7 +33,7 @@ local function get_relevant_tables(transient)
|
||||
end
|
||||
end
|
||||
|
||||
function core.forceload_block(pos, transient)
|
||||
function core.forceload_block(pos, transient, limit)
|
||||
-- set changed flag
|
||||
forceload_blocks_changed = true
|
||||
|
||||
@@ -46,7 +46,8 @@ function core.forceload_block(pos, transient)
|
||||
elseif other_table[hash] ~= nil then
|
||||
relevant_table[hash] = 1
|
||||
else
|
||||
if total_forceloaded >= (tonumber(core.settings:get("max_forceloaded_blocks")) or 16) then
|
||||
limit = limit or tonumber(core.settings:get("max_forceloaded_blocks")) or 16
|
||||
if limit >= 0 and total_forceloaded >= limit then
|
||||
return false
|
||||
end
|
||||
total_forceloaded = total_forceloaded+1
|
||||
|
||||
@@ -8,16 +8,20 @@ local gamepath = scriptpath .. "game".. DIR_DELIM
|
||||
local builtin_shared = {}
|
||||
|
||||
dofile(gamepath .. "constants.lua")
|
||||
assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared)
|
||||
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
|
||||
dofile(gamepath .. "register.lua")
|
||||
assert(loadfile(commonpath .. "register.lua"))(builtin_shared)
|
||||
assert(loadfile(gamepath .. "register.lua"))(builtin_shared)
|
||||
|
||||
if core.settings:get_bool("profiler.load") then
|
||||
profiler = dofile(scriptpath .. "profiler" .. DIR_DELIM .. "init.lua")
|
||||
end
|
||||
|
||||
dofile(commonpath .. "after.lua")
|
||||
dofile(commonpath .. "mod_storage.lua")
|
||||
dofile(gamepath .. "item_entity.lua")
|
||||
dofile(gamepath .. "deprecated.lua")
|
||||
dofile(gamepath .. "misc_s.lua")
|
||||
dofile(gamepath .. "misc.lua")
|
||||
dofile(gamepath .. "privileges.lua")
|
||||
dofile(gamepath .. "auth.lua")
|
||||
@@ -32,5 +36,8 @@ 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
|
||||
|
||||
@@ -5,8 +5,8 @@ local builtin_shared = ...
|
||||
local function copy_pointed_thing(pointed_thing)
|
||||
return {
|
||||
type = pointed_thing.type,
|
||||
above = vector.new(pointed_thing.above),
|
||||
under = vector.new(pointed_thing.under),
|
||||
above = pointed_thing.above and vector.copy(pointed_thing.above),
|
||||
under = pointed_thing.under and vector.copy(pointed_thing.under),
|
||||
ref = pointed_thing.ref,
|
||||
}
|
||||
end
|
||||
@@ -15,15 +15,6 @@ end
|
||||
-- Item definition helpers
|
||||
--
|
||||
|
||||
function core.inventorycube(img1, img2, img3)
|
||||
img2 = img2 or img1
|
||||
img3 = img3 or img1
|
||||
return "[inventorycube"
|
||||
.. "{" .. img1:gsub("%^", "&")
|
||||
.. "{" .. img2:gsub("%^", "&")
|
||||
.. "{" .. img3:gsub("%^", "&")
|
||||
end
|
||||
|
||||
function core.get_pointed_thing_position(pointed_thing, above)
|
||||
if pointed_thing.type == "node" then
|
||||
if above then
|
||||
@@ -37,144 +28,6 @@ function core.get_pointed_thing_position(pointed_thing, above)
|
||||
end
|
||||
end
|
||||
|
||||
function core.dir_to_facedir(dir, is6d)
|
||||
--account for y if requested
|
||||
if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
|
||||
|
||||
--from above
|
||||
if dir.y < 0 then
|
||||
if math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 19
|
||||
else
|
||||
return 13
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 10
|
||||
else
|
||||
return 4
|
||||
end
|
||||
end
|
||||
|
||||
--from below
|
||||
else
|
||||
if math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 15
|
||||
else
|
||||
return 17
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 6
|
||||
else
|
||||
return 8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--otherwise, place horizontally
|
||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 3
|
||||
else
|
||||
return 1
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 2
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Table of possible dirs
|
||||
local facedir_to_dir = {
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new( 0, 0, -1),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, -1, 0),
|
||||
vector.new( 0, 1, 0),
|
||||
}
|
||||
-- Mapping from facedir value to index in facedir_to_dir.
|
||||
local facedir_to_dir_map = {
|
||||
[0]=1, 2, 3, 4,
|
||||
5, 2, 6, 4,
|
||||
6, 2, 5, 4,
|
||||
1, 5, 3, 6,
|
||||
1, 6, 3, 5,
|
||||
1, 4, 3, 2,
|
||||
}
|
||||
function core.facedir_to_dir(facedir)
|
||||
return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
|
||||
end
|
||||
|
||||
function core.dir_to_wallmounted(dir)
|
||||
if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
|
||||
if dir.y < 0 then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 3
|
||||
else
|
||||
return 2
|
||||
end
|
||||
else
|
||||
if dir.z < 0 then
|
||||
return 5
|
||||
else
|
||||
return 4
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- table of dirs in wallmounted order
|
||||
local wallmounted_to_dir = {
|
||||
[0] = vector.new( 0, 1, 0),
|
||||
vector.new( 0, -1, 0),
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 0, 0, -1),
|
||||
}
|
||||
function core.wallmounted_to_dir(wallmounted)
|
||||
return wallmounted_to_dir[wallmounted % 8]
|
||||
end
|
||||
|
||||
function core.dir_to_yaw(dir)
|
||||
return -math.atan2(dir.x, dir.z)
|
||||
end
|
||||
|
||||
function core.yaw_to_dir(yaw)
|
||||
return vector.new(-math.sin(yaw), 0, math.cos(yaw))
|
||||
end
|
||||
|
||||
function core.is_colored_paramtype(ptype)
|
||||
return (ptype == "color") or (ptype == "colorfacedir") or
|
||||
(ptype == "colorwallmounted") or (ptype == "colordegrotate")
|
||||
end
|
||||
|
||||
function core.strip_param2_color(param2, paramtype2)
|
||||
if not core.is_colored_paramtype(paramtype2) then
|
||||
return nil
|
||||
end
|
||||
if paramtype2 == "colorfacedir" then
|
||||
param2 = math.floor(param2 / 32) * 32
|
||||
elseif paramtype2 == "colorwallmounted" then
|
||||
param2 = math.floor(param2 / 8) * 8
|
||||
elseif paramtype2 == "colordegrotate" then
|
||||
param2 = math.floor(param2 / 32) * 32
|
||||
end
|
||||
-- paramtype2 == "color" requires no modification.
|
||||
return param2
|
||||
end
|
||||
|
||||
local function has_all_groups(tbl, required_groups)
|
||||
if type(required_groups) == "string" then
|
||||
return (tbl[required_groups] or 0) ~= 0
|
||||
@@ -323,12 +176,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
end
|
||||
|
||||
-- Place above pointed node
|
||||
local place_to = vector.new(above)
|
||||
local place_to = vector.copy(above)
|
||||
|
||||
-- If node under is buildable_to, place into it instead (eg. snow)
|
||||
if olddef_under.buildable_to then
|
||||
log("info", "node under is buildable to")
|
||||
place_to = vector.new(under)
|
||||
place_to = vector.copy(under)
|
||||
end
|
||||
|
||||
if core.is_protected(place_to, playername) then
|
||||
@@ -352,7 +205,9 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
newnode.param2 = core.dir_to_wallmounted(dir)
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir") and not param2 then
|
||||
def.paramtype2 == "colorfacedir" or
|
||||
def.paramtype2 == "4dir" or
|
||||
def.paramtype2 == "color4dir") and not param2 then
|
||||
local placer_pos = placer and placer:get_pos()
|
||||
if placer_pos then
|
||||
local dir = vector.subtract(above, placer_pos)
|
||||
@@ -372,6 +227,8 @@ 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
|
||||
@@ -383,10 +240,11 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
end
|
||||
|
||||
-- Check if the node is attached and if it can be placed there
|
||||
if core.get_item_group(def.name, "attached_node") ~= 0 and
|
||||
not builtin_shared.check_attached_node(place_to, newnode) then
|
||||
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
|
||||
log("action", "attached node " .. def.name ..
|
||||
" can not be placed at " .. core.pos_to_string(place_to))
|
||||
" cannot be placed at " .. core.pos_to_string(place_to))
|
||||
return itemstack, nil
|
||||
end
|
||||
|
||||
@@ -409,7 +267,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
-- Run callback
|
||||
if def.after_place_node and not prevent_after_place then
|
||||
-- Deepcopy place_to and pointed_thing because callback can modify it
|
||||
local place_to_copy = vector.new(place_to)
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if def.after_place_node(place_to_copy, placer, itemstack,
|
||||
pointed_thing_copy) then
|
||||
@@ -420,7 +278,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(core.registered_on_placenodes) do
|
||||
-- Deepcopy pos, node and pointed_thing because callback can modify them
|
||||
local place_to_copy = vector.new(place_to)
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
|
||||
local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
@@ -492,8 +350,26 @@ 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 pairs(core.registered_on_item_eats) do
|
||||
for _, callback in ipairs(core.registered_on_item_eats) do
|
||||
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
||||
if result then
|
||||
return result
|
||||
@@ -514,22 +390,20 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
|
||||
|
||||
-- Changing hp might kill the player causing mods to do who-knows-what to the
|
||||
-- inventory, so do this before set_hp().
|
||||
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
|
||||
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)
|
||||
end
|
||||
end
|
||||
user:set_wielded_item(itemstack)
|
||||
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)
|
||||
|
||||
@@ -548,7 +422,7 @@ function core.node_punch(pos, node, puncher, pointed_thing)
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(core.registered_on_punchnodes) do
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
|
||||
callback(pos_copy, node_copy, puncher, pointed_thing_copy)
|
||||
@@ -589,7 +463,7 @@ function core.node_dig(pos, node, digger)
|
||||
local def = core.registered_nodes[node.name]
|
||||
-- Copy pos because the callback could modify it
|
||||
if def and (not def.diggable or
|
||||
(def.can_dig and not def.can_dig(vector.new(pos), digger))) then
|
||||
(def.can_dig and not def.can_dig(vector.copy(pos), digger))) then
|
||||
log("info", diggername .. " tried to dig "
|
||||
.. node.name .. " which is not diggable "
|
||||
.. core.pos_to_string(pos))
|
||||
@@ -636,7 +510,7 @@ function core.node_dig(pos, node, digger)
|
||||
if def and def.preserve_metadata then
|
||||
local oldmeta = core.get_meta(pos):to_table().fields
|
||||
-- Copy pos and node because the callback can modify them.
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
local drop_stacks = {}
|
||||
for k, v in pairs(drops) do
|
||||
@@ -668,7 +542,7 @@ function core.node_dig(pos, node, digger)
|
||||
-- Run callback
|
||||
if def and def.after_dig_node then
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
|
||||
end
|
||||
@@ -679,7 +553,7 @@ function core.node_dig(pos, node, digger)
|
||||
core.set_last_run_mod(origin.mod)
|
||||
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = vector.new(pos)
|
||||
local pos_copy = vector.copy(pos)
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
callback(pos_copy, node_copy, digger)
|
||||
end
|
||||
@@ -732,6 +606,7 @@ 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,
|
||||
|
||||
@@ -744,13 +619,8 @@ core.nodedef_default = {
|
||||
-- Node properties
|
||||
drawtype = "normal",
|
||||
visual_scale = 1.0,
|
||||
-- Don't define these because otherwise the old tile_images and
|
||||
-- special_materials wouldn't be read
|
||||
--tiles ={""},
|
||||
--special_tiles = {
|
||||
-- {name="", backface_culling=true},
|
||||
-- {name="", backface_culling=true},
|
||||
--},
|
||||
tiles = nil,
|
||||
special_tiles = nil,
|
||||
post_effect_color = {a=0, r=0, g=0, b=0},
|
||||
paramtype = "none",
|
||||
paramtype2 = "none",
|
||||
@@ -789,6 +659,7 @@ 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,
|
||||
}
|
||||
@@ -809,6 +680,7 @@ 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,
|
||||
}
|
||||
|
||||
@@ -825,8 +697,9 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
|
||||
tool_capabilities = nil,
|
||||
|
||||
-- Interaction callbacks
|
||||
on_place = redef_wrapper(core, 'item_place'),
|
||||
on_place = redef_wrapper(core, 'item_place'), -- 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,
|
||||
}
|
||||
|
||||
@@ -58,17 +58,22 @@ core.register_entity(":__builtin:item", {
|
||||
local glow = def and def.light_source and
|
||||
math.floor(def.light_source / 2 + 0.5)
|
||||
|
||||
local size_bias = 1e-3 * math.random() -- small random bias to counter Z-fighting
|
||||
local c = {-size, -size, -size, size, size, size}
|
||||
self.object:set_properties({
|
||||
is_visible = true,
|
||||
visual = "wielditem",
|
||||
textures = {itemname},
|
||||
visual_size = {x = size, y = size},
|
||||
collisionbox = {-size, -size, -size, size, size, size},
|
||||
visual_size = {x = size + size_bias, y = size + size_bias},
|
||||
collisionbox = c,
|
||||
automatic_rotate = math.pi * 0.5 * 0.2 / size,
|
||||
wield_item = self.itemstring,
|
||||
glow = glow,
|
||||
infotext = stack:get_description(),
|
||||
})
|
||||
|
||||
-- cache for usage in on_step
|
||||
self._collisionbox = c
|
||||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
@@ -93,12 +98,13 @@ 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
|
||||
-- Can not merge with itself
|
||||
-- Cannot merge with itself
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -108,7 +114,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
|
||||
-- Can not merge different or full stack
|
||||
-- Cannot merge different or full stack
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -163,7 +169,7 @@ core.register_entity(":__builtin:item", {
|
||||
local pos = self.object:get_pos()
|
||||
local node = core.get_node_or_nil({
|
||||
x = pos.x,
|
||||
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
|
||||
y = pos.y + self._collisionbox[2] - 0.05,
|
||||
z = pos.z
|
||||
})
|
||||
-- Delete in 'ignore' nodes
|
||||
@@ -173,10 +179,15 @@ core.register_entity(":__builtin:item", {
|
||||
return
|
||||
end
|
||||
|
||||
-- Prevent assert when item_entity is attached
|
||||
if moveresult == nil and self.object:get_attach() then
|
||||
return
|
||||
end
|
||||
|
||||
if self.force_out then
|
||||
-- This code runs after the entity got a push from the is_stuck code.
|
||||
-- It makes sure the entity is entirely outside the solid node
|
||||
local c = self.object:get_properties().collisionbox
|
||||
local c = self._collisionbox
|
||||
local s = self.force_out_start
|
||||
local f = self.force_out
|
||||
local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or
|
||||
@@ -313,16 +324,29 @@ core.register_entity(":__builtin:item", {
|
||||
end
|
||||
end,
|
||||
|
||||
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
|
||||
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)
|
||||
end
|
||||
self.itemstring = ""
|
||||
self.object:remove()
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- can be overriden by mods
|
||||
-- can be overridden 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
|
||||
|
||||
@@ -121,54 +121,7 @@ function core.get_player_radius_area(player_name, radius)
|
||||
end
|
||||
|
||||
|
||||
function core.hash_node_position(pos)
|
||||
return (pos.z + 32768) * 65536 * 65536
|
||||
+ (pos.y + 32768) * 65536
|
||||
+ pos.x + 32768
|
||||
end
|
||||
|
||||
|
||||
function core.get_position_from_hash(hash)
|
||||
local x = (hash % 65536) - 32768
|
||||
hash = math.floor(hash / 65536)
|
||||
local y = (hash % 65536) - 32768
|
||||
hash = math.floor(hash / 65536)
|
||||
local z = (hash % 65536) - 32768
|
||||
return vector.new(x, y, z)
|
||||
end
|
||||
|
||||
|
||||
function core.get_item_group(name, group)
|
||||
if not core.registered_items[name] or not
|
||||
core.registered_items[name].groups[group] then
|
||||
return 0
|
||||
end
|
||||
return core.registered_items[name].groups[group]
|
||||
end
|
||||
|
||||
|
||||
function core.get_node_group(name, group)
|
||||
core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
|
||||
return core.get_item_group(name, group)
|
||||
end
|
||||
|
||||
|
||||
function core.setting_get_pos(name)
|
||||
local value = core.settings:get(name)
|
||||
if not value then
|
||||
return nil
|
||||
end
|
||||
return core.string_to_pos(value)
|
||||
end
|
||||
|
||||
|
||||
-- See l_env.cpp for the other functions
|
||||
function core.get_artificial_light(param1)
|
||||
return math.floor(param1 / 16)
|
||||
end
|
||||
|
||||
|
||||
-- To be overriden by protection mods
|
||||
-- To be overridden by protection mods
|
||||
|
||||
function core.is_protected(pos, name)
|
||||
return false
|
||||
@@ -284,40 +237,35 @@ end
|
||||
core.dynamic_media_callbacks = {}
|
||||
|
||||
|
||||
-- PNG encoder safety wrapper
|
||||
-- Transfer of certain globals into async environment
|
||||
-- see builtin/async/game.lua for the other side
|
||||
|
||||
local o_encode_png = core.encode_png
|
||||
function core.encode_png(width, height, data, compression)
|
||||
if type(width) ~= "number" then
|
||||
error("Incorrect type for 'width', expected number, got " .. type(width))
|
||||
local function copy_filtering(t, seen)
|
||||
if type(t) == "userdata" or type(t) == "function" then
|
||||
return true -- don't use nil so presence can still be detected
|
||||
elseif type(t) ~= "table" then
|
||||
return t
|
||||
end
|
||||
if type(height) ~= "number" then
|
||||
error("Incorrect type for 'height', expected number, got " .. type(height))
|
||||
local n = {}
|
||||
seen = seen or {}
|
||||
seen[t] = n
|
||||
for k, v in pairs(t) do
|
||||
local k_ = seen[k] or copy_filtering(k, seen)
|
||||
local v_ = seen[v] or copy_filtering(v, seen)
|
||||
n[k_] = v_
|
||||
end
|
||||
|
||||
local expected_byte_count = width * height * 4
|
||||
|
||||
if type(data) ~= "table" and type(data) ~= "string" then
|
||||
error("Incorrect type for 'data', expected table or string, got " .. type(data))
|
||||
end
|
||||
|
||||
local data_length = type(data) == "table" and #data * 4 or string.len(data)
|
||||
|
||||
if data_length ~= expected_byte_count then
|
||||
error(string.format(
|
||||
"Incorrect length of 'data', width and height imply %d bytes but %d were provided",
|
||||
expected_byte_count,
|
||||
data_length
|
||||
))
|
||||
end
|
||||
|
||||
if type(data) == "table" then
|
||||
local dataBuf = {}
|
||||
for i = 1, #data do
|
||||
dataBuf[i] = core.colorspec_to_bytes(data[i])
|
||||
end
|
||||
data = table.concat(dataBuf)
|
||||
end
|
||||
|
||||
return o_encode_png(width, height, data, compression or 6)
|
||||
return n
|
||||
end
|
||||
|
||||
function core.get_globals_to_transfer()
|
||||
local all = {
|
||||
registered_items = copy_filtering(core.registered_items),
|
||||
registered_aliases = core.registered_aliases,
|
||||
|
||||
nodedef_default = copy_filtering(core.nodedef_default),
|
||||
craftitemdef_default = copy_filtering(core.craftitemdef_default),
|
||||
tooldef_default = copy_filtering(core.tooldef_default),
|
||||
noneitemdef_default = copy_filtering(core.noneitemdef_default),
|
||||
}
|
||||
return all
|
||||
end
|
||||
|
||||
92
builtin/game/misc_s.lua
Normal file
92
builtin/game/misc_s.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
-- 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
|
||||
@@ -1,5 +1,6 @@
|
||||
-- Minetest: builtin/misc_register.lua
|
||||
-- Minetest: builtin/register.lua
|
||||
|
||||
local builtin_shared = ...
|
||||
local S = core.get_translator("__builtin")
|
||||
|
||||
--
|
||||
@@ -79,8 +80,22 @@ 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 "??"
|
||||
@@ -89,6 +104,7 @@ 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 "??"
|
||||
@@ -303,14 +319,16 @@ end
|
||||
|
||||
function core.on_craft(itemstack, player, old_craft_list, craft_inv)
|
||||
for _, func in ipairs(core.registered_on_crafts) do
|
||||
itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
|
||||
-- cast to ItemStack since func() could return a string
|
||||
itemstack = ItemStack(func(itemstack, player, old_craft_list, craft_inv) or itemstack)
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
function core.craft_predict(itemstack, player, old_craft_list, craft_inv)
|
||||
for _, func in ipairs(core.registered_craft_predicts) do
|
||||
itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
|
||||
-- cast to ItemStack since func() could return a string
|
||||
itemstack = ItemStack(func(itemstack, player, old_craft_list, craft_inv) or itemstack)
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
@@ -403,55 +421,6 @@ 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
|
||||
|
||||
function core.run_callbacks(callbacks, mode, ...)
|
||||
assert(type(callbacks) == "table")
|
||||
local cb_len = #callbacks
|
||||
if cb_len == 0 then
|
||||
if mode == 2 or mode == 3 then
|
||||
return true
|
||||
elseif mode == 4 or mode == 5 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
local ret = nil
|
||||
for i = 1, cb_len do
|
||||
local origin = core.callback_origins[callbacks[i]]
|
||||
core.set_last_run_mod(origin.mod)
|
||||
local cb_ret = callbacks[i](...)
|
||||
|
||||
if mode == 0 and i == 1 then
|
||||
ret = cb_ret
|
||||
elseif mode == 1 and i == cb_len then
|
||||
ret = cb_ret
|
||||
elseif mode == 2 then
|
||||
if not cb_ret or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 3 then
|
||||
if cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
ret = cb_ret
|
||||
elseif mode == 4 then
|
||||
if (cb_ret and not ret) or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 5 and cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function core.run_priv_callbacks(name, priv, caller, method)
|
||||
local def = core.registered_privileges[priv]
|
||||
if not def or not def["on_" .. method] or
|
||||
@@ -468,34 +437,6 @@ end
|
||||
-- Callback registration
|
||||
--
|
||||
|
||||
local function make_registration()
|
||||
local t = {}
|
||||
local registerfunc = function(func)
|
||||
t[#t + 1] = func
|
||||
core.callback_origins[func] = {
|
||||
mod = core.get_current_modname() or "??",
|
||||
name = debug.getinfo(1, "n").name or "??"
|
||||
}
|
||||
--local origin = core.callback_origins[func]
|
||||
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
|
||||
end
|
||||
return t, registerfunc
|
||||
end
|
||||
|
||||
local function make_registration_reverse()
|
||||
local t = {}
|
||||
local registerfunc = function(func)
|
||||
table.insert(t, 1, func)
|
||||
core.callback_origins[func] = {
|
||||
mod = core.get_current_modname() or "??",
|
||||
name = debug.getinfo(1, "n").name or "??"
|
||||
}
|
||||
--local origin = core.callback_origins[func]
|
||||
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
|
||||
end
|
||||
return t, registerfunc
|
||||
end
|
||||
|
||||
local function make_registration_wrap(reg_fn_name, clear_fn_name)
|
||||
local list = {}
|
||||
|
||||
@@ -583,6 +524,9 @@ core.registered_decorations = make_registration_wrap("register_decoration", "cle
|
||||
core.unregister_biome = make_wrap_deregistration(core.register_biome,
|
||||
core.clear_registered_biomes, core.registered_biomes)
|
||||
|
||||
local make_registration = builtin_shared.make_registration
|
||||
local make_registration_reverse = builtin_shared.make_registration_reverse
|
||||
|
||||
core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
|
||||
core.registered_on_chatcommands, core.register_on_chatcommand = make_registration()
|
||||
core.registered_globalsteps, core.register_globalstep = make_registration()
|
||||
@@ -605,6 +549,7 @@ 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()
|
||||
@@ -615,6 +560,17 @@ core.registered_on_player_inventory_actions, core.register_on_player_inventory_a
|
||||
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()
|
||||
|
||||
@@ -138,13 +138,15 @@ local function player_event_handler(player,eventname)
|
||||
end
|
||||
|
||||
function core.hud_replace_builtin(hud_name, definition)
|
||||
|
||||
if type(definition) ~= "table" or
|
||||
definition.hud_elem_type ~= "statbar" then
|
||||
return false
|
||||
end
|
||||
|
||||
definition = table.copy(definition)
|
||||
|
||||
if hud_name == "health" then
|
||||
definition.item = definition.item or definition.number or core.PLAYER_MAX_HP_DEFAULT
|
||||
bar_definitions.hp = definition
|
||||
|
||||
for name, ids in pairs(hud_ids) do
|
||||
@@ -159,6 +161,7 @@ function core.hud_replace_builtin(hud_name, definition)
|
||||
end
|
||||
|
||||
if hud_name == "breath" then
|
||||
definition.item = definition.item or definition.number or core.PLAYER_MAX_BREATH_DEFAULT
|
||||
bar_definitions.breath = definition
|
||||
|
||||
for name, ids in pairs(hud_ids) do
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
local math_floor = math.floor
|
||||
local vector_new = vector.new
|
||||
|
||||
VoxelArea = {
|
||||
MinEdge = vector.new(1, 1, 1),
|
||||
MaxEdge = vector.new(0, 0, 0),
|
||||
MinEdge = vector_new(1, 1, 1),
|
||||
MaxEdge = vector_new(0, 0, 0),
|
||||
ystride = 0,
|
||||
zstride = 0,
|
||||
}
|
||||
|
||||
function VoxelArea:new(o)
|
||||
local class_metatable = {}
|
||||
setmetatable(VoxelArea, class_metatable)
|
||||
|
||||
local function new(self, o)
|
||||
o = o or {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
@@ -17,9 +23,15 @@ function VoxelArea:new(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(
|
||||
return vector_new(
|
||||
MaxEdge.x - MinEdge.x + 1,
|
||||
MaxEdge.y - MinEdge.y + 1,
|
||||
MaxEdge.z - MinEdge.z + 1
|
||||
@@ -36,7 +48,7 @@ function VoxelArea:index(x, y, z)
|
||||
local i = (z - MinEdge.z) * self.zstride +
|
||||
(y - MinEdge.y) * self.ystride +
|
||||
(x - MinEdge.x) + 1
|
||||
return math.floor(i)
|
||||
return math_floor(i)
|
||||
end
|
||||
|
||||
function VoxelArea:indexp(p)
|
||||
@@ -44,24 +56,23 @@ function VoxelArea:indexp(p)
|
||||
local i = (p.z - MinEdge.z) * self.zstride +
|
||||
(p.y - MinEdge.y) * self.ystride +
|
||||
(p.x - MinEdge.x) + 1
|
||||
return math.floor(i)
|
||||
return math_floor(i)
|
||||
end
|
||||
|
||||
function VoxelArea:position(i)
|
||||
local p = {}
|
||||
local MinEdge = self.MinEdge
|
||||
|
||||
i = i - 1
|
||||
|
||||
p.z = math.floor(i / self.zstride) + MinEdge.z
|
||||
local z = math_floor(i / self.zstride) + MinEdge.z
|
||||
i = i % self.zstride
|
||||
|
||||
p.y = math.floor(i / self.ystride) + MinEdge.y
|
||||
local y = math_floor(i / self.ystride) + MinEdge.y
|
||||
i = i % self.ystride
|
||||
|
||||
p.x = math.floor(i) + MinEdge.x
|
||||
local x = math_floor(i) + MinEdge.x
|
||||
|
||||
return p
|
||||
return vector_new(x, y, z)
|
||||
end
|
||||
|
||||
function VoxelArea:contains(x, y, z)
|
||||
|
||||
@@ -6,19 +6,25 @@
|
||||
--
|
||||
|
||||
-- Initialize some very basic things
|
||||
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(...)
|
||||
function core.error_handler(err, level)
|
||||
return debug.traceback(tostring(err), level)
|
||||
end
|
||||
do
|
||||
local function concat_args(...)
|
||||
local n, t = select("#", ...), {...}
|
||||
for i = 1, n do
|
||||
t[i] = tostring(t[i])
|
||||
end
|
||||
core_print(table.concat(t, "\t"))
|
||||
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
|
||||
end
|
||||
core.print = nil -- don't pollute our namespace
|
||||
end
|
||||
math.randomseed(os.time())
|
||||
minetest = core
|
||||
@@ -56,8 +62,10 @@ elseif INIT == "mainmenu" then
|
||||
if not custom_loaded then
|
||||
dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua")
|
||||
end
|
||||
elseif INIT == "async" then
|
||||
dofile(asyncpath .. "init.lua")
|
||||
elseif INIT == "async" then
|
||||
dofile(asyncpath .. "mainmenu.lua")
|
||||
elseif INIT == "async_game" then
|
||||
dofile(asyncpath .. "game.lua")
|
||||
elseif INIT == "client" then
|
||||
dofile(clientpath .. "init.lua")
|
||||
else
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
# textdomain: __builtin
|
||||
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
|
||||
Empty command.=Leerer Befehl.
|
||||
Invalid command: @1=Ungültiger Befehl: @1
|
||||
Invalid command usage.=Ungültige Befehlsverwendung.
|
||||
@@ -43,7 +61,7 @@ Invalid parameters (see /help revoke).=Ungültige Parameter (siehe „/help revo
|
||||
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=Passwort von Spieler setzen
|
||||
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.
|
||||
@@ -72,6 +90,7 @@ Teleport to position or player=Zu Position oder Spieler teleportieren
|
||||
You don't have permission to teleport other players (missing privilege: @1).=Sie haben nicht die Erlaubnis, andere Spieler zu teleportieren (fehlendes Privileg: @1).
|
||||
([-n] <name> <value>) | <name>=([-n] <Name> <Wert>) | <Name>
|
||||
Set or read server configuration setting=Serverkonfigurationseinstellung setzen oder lesen
|
||||
Failed. Cannot modify secure settings. Edit the settings file manually.=Fehlgeschlagen. Sicherheitseinstellungen können nicht modifiziert werden. Bearbeiten Sie die Einstellungsdatei manuell.
|
||||
Failed. Use '/set -n <name> <value>' to create a new setting.=Fehlgeschlagen. Benutzen Sie „/set -n <Name> <Wert>“, um eine neue Einstellung zu erstellen.
|
||||
@1 @= @2=@1 @= @2
|
||||
<not set>=<nicht gesetzt>
|
||||
@@ -151,6 +170,7 @@ Server shutting down (operator request).=Server wird heruntergefahren (Betreiber
|
||||
Ban the IP of a player or show the ban list=Die IP eines Spielers verbannen oder die Bannliste anzeigen
|
||||
The ban list is empty.=Die Bannliste ist leer.
|
||||
Ban list: @1=Bannliste: @1
|
||||
You cannot ban players in singleplayer!=Im Einzelspielermodus können Sie keine Spieler verbannen!
|
||||
Player is not online.=Spieler ist nicht online.
|
||||
Failed to ban player.=Konnte Spieler nicht verbannen.
|
||||
Banned @1.=@1 verbannt.
|
||||
@@ -187,30 +207,6 @@ 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).
|
||||
@@ -232,12 +228,17 @@ Can use fly mode=Kann den Flugmodus benutzen
|
||||
Can use fast mode=Kann den Schnellmodus benutzen
|
||||
Can fly through solid nodes using noclip mode=Kann durch feste Blöcke mit dem Geistmodus fliegen
|
||||
Can use the rollback functionality=Kann die Rollback-Funktionalität benutzen
|
||||
Can view more debug info that might give a gameplay advantage=Kann zusätzliche Debuginformationen betrachten, welche einen spielerischen Vorteil geben könnten
|
||||
Can enable wireframe=Kann Drahtmodell aktivieren
|
||||
Unknown Item=Unbekannter Gegenstand
|
||||
Air=Luft
|
||||
Ignore=Ignorieren
|
||||
You can't place 'ignore' nodes!=Sie können keine „ignore“-Blöcke platzieren!
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<Filter>] | dump [<Filter>] | save [<Format> [<Filter>]] | reset
|
||||
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).
|
||||
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“.
|
||||
|
||||
246
builtin/locale/__builtin.id.tr
Normal file
246
builtin/locale/__builtin.id.tr
Normal file
@@ -0,0 +1,246 @@
|
||||
# textdomain: __builtin
|
||||
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
|
||||
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
|
||||
@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'!
|
||||
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).
|
||||
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
|
||||
@@ -1,77 +1,97 @@
|
||||
# textdomain: __builtin
|
||||
# note for transaltors: sono state seguite le norme di https://italianoinclusivo.it/ a parte per il suffisso -tore -trice, con l'intento di trasformarlo in un epiceno
|
||||
Invalid parameters (see /help @1).=Parametri non validi (vedi /help @1)
|
||||
Too many arguments, try using just /help <command>=Troppi argomenti, prova a usare /help <comando>
|
||||
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]=[all | privs | <comando>] [-t]
|
||||
Get help for commands or list privileges (-t: output in chat)=Richiama la finestra d'aiuto dei comandi o dei privilegi (-t: mostra 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 clic 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
|
||||
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=
|
||||
(@1 s)= (@1 s)
|
||||
Command execution took @1 s=L'esecuzione del comando ha richiesto @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.
|
||||
Unable to get position of player @1.=Impossibile ottenere la posizione dellə 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
|
||||
Show the name of the server owner=Mostra il nome di chi possiede il 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.=
|
||||
There's no administrator named in the config file.=Non c'è nessun*amministratore nel file di configurazione.
|
||||
@1 does not have any privileges.=@1 non ha nessun privilegio
|
||||
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.
|
||||
Show privileges of yourself or another player=Mostra i privilegi propri o di un*altrə giocatore
|
||||
Player @1 does not exist.=Lə 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
|
||||
Return list of all online players with privilege=Ritorna una lista di tuttɜ lɜ giocatori connessɜ 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
|
||||
No online player has the "@1" privilege.=Nessunə giocatore ha il privilegio "@1".
|
||||
Players online with the "@1" privilege: @2=Giocatori connessɜ 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=
|
||||
Your privileges are insufficient. '@1' only allows you to grant: @2=I tuoi privilegi sono insufficienti. '@1' ti permette soltanto di elargire: @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
|
||||
<name> (<privilege> [, <privilege2> [<...>]] | all)=<nome> (<privilegio> [, <privilegio2> [<...>]] | all)
|
||||
Give privileges to player=Dà privilegi allə giocatore
|
||||
Invalid parameters (see /help grant).=Parametri non validi (vedi /help grant).
|
||||
<privilege> [, <privilege2> [<...>]] | all=
|
||||
<privilege> [, <privilege2> [<...>]] | all=<privilegio> [, <privilegio2> [<...>]] | 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.=
|
||||
Your privileges are insufficient. '@1' only allows you to revoke: @2=I tuoi privilegi sono insufficienti. '@1' ti permette soltanto di revocare: @2
|
||||
Note: Cannot revoke in singleplayer: @1=N.B.: Non si può revocare in locale: @1
|
||||
Note: Cannot revoke from admin: @1=N.B.: Non si può revocare a un*amministratore: @1
|
||||
No privileges were revoked.=Nessun privilegio è stato revocato.
|
||||
@1 revoked privileges from you: @2=@1 ti ha revocato i seguenti privilegi: @2
|
||||
Remove privileges from player=Rimuove privilegi dal giocatore
|
||||
Remove privileges from player=Rimuove privilegi dallə 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=Imposta la password del giocatore
|
||||
Set player's password (sent unencrypted, thus insecure)=Imposta la password dellə giocatore (non crittografata, ergo insicura)
|
||||
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.
|
||||
Password of player "@1" cleared.=Password dellə 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.
|
||||
Password of player "@1" set.=Password dellə 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
|
||||
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.
|
||||
No such player "@1" to remove.=Non è presente nessunə giocatore "@1" da rimuovere.
|
||||
Player "@1" is connected, cannot remove.=Lə giocatore "@1" è connessə, non può essere rimossə.
|
||||
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!
|
||||
Cannot get player with name @1.=Impossibile trovare lə giocatore chiamato @1.
|
||||
Cannot teleport, @1 is attached to an object!=Impossibile teletrasportare, @1 è attaccatə 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
|
||||
One does not teleport to oneself.=Non ci si può teletrasportare su se stessɜ.
|
||||
Cannot get teleportee with name @1.=Impossibile trovare lə giocatore chiamatə @1 per il teletrasporto
|
||||
Cannot get target player with name @1.=Impossibile trovare lə 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).
|
||||
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 altrɜ 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.=Errore. Non è possibile modificare le impostazioni di sicurezza. Modifica manualmente il file d'impostazioni.
|
||||
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>
|
||||
@@ -88,11 +108,11 @@ Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in
|
||||
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.=
|
||||
No mods installed.=Nessuna mod installata.
|
||||
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 is not a known player.=@1 non è unə giocatore conosciutə.
|
||||
@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.
|
||||
@@ -100,7 +120,7 @@ Giving 'ignore' is not allowed.=Non è permesso dare 'ignore'.
|
||||
@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
|
||||
Give item to player=Dà oggetti allɜ giocatori
|
||||
Name and ItemString required.=Richiesti nome e NomeOggetto.
|
||||
<ItemString> [<count> [<wear>]]=<NomeOggetto> [<quantità> [<usura>]]
|
||||
Give item to yourself=Dà oggetti a te stessǝ
|
||||
@@ -108,29 +128,29 @@ 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.
|
||||
Unable to spawn entity, player is nil.=Impossibile generare l'entità, lə 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 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.
|
||||
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'ultimə 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.
|
||||
Nobody has touched the specified location in @1 seconds.=Nessunə 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
|
||||
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 player '@1' since @2 seconds.=Riavvolge le azioni dellə 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)
|
||||
(log is too long to show)=(lo storico è 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
|
||||
@@ -139,120 +159,89 @@ This command was disabled by a mod or game.=Questo comando è stato disabilitato
|
||||
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).=
|
||||
Invalid time (must be between 0 and 24000).=Orario non valido (deve essere tra 0 e 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)=
|
||||
[<delay_in_seconds> | -1] [-r] [<message>]=[<ritardo_in_secondi> | -1] [-r] [<messaggio>]
|
||||
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Arresta il server (-1 cancella un arresto programmato, -r permette allɜ giocatori di riconnettersi)
|
||||
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
|
||||
Player is not online.=Il giocatore non è connesso.
|
||||
Failed to ban player.=Errore nel bandire il giocatore.
|
||||
Ban the IP of a player or show the ban list=Bandisce l'IP dellə giocatore o mostra la lista di quellɜ banditɜ
|
||||
The ban list is empty.=La lista banditɜ è vuota.
|
||||
Ban list: @1=Lista banditɜ: @1
|
||||
You cannot ban players in singleplayer!=Non puoi bandire giocatori in locale!
|
||||
Player is not online.=Lə giocatore non è connessə.
|
||||
Failed to ban player.=Errore nel bandire lə 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
|
||||
Remove IP ban belonging to a player/IP=Perdona l'IP appartenente a unə giocatore/IP
|
||||
Failed to unban player/IP.=Errore nel perdonare lə 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.
|
||||
Kick a player=Caccia unə giocatore
|
||||
Failed to kick player @1.=Errore nel cacciare lə 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)
|
||||
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 disconnettere. (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
|
||||
Send a direct message to a player=Invia un messaggio privato a unə giocatore
|
||||
Invalid usage, see /help msg.=Uso incorretto, vedi /help msg
|
||||
The player @1 is not online.=Il giocatore @1 non è connesso.
|
||||
The player @1 is not online.=Lə giocatore @1 non è connessə.
|
||||
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).
|
||||
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 è ignoto.
|
||||
Clear the inventory of yourself or another player=Svuota l'inventario tuo o di un*altrə 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*altrə 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.
|
||||
Player must be online to clear inventory!=Lə giocatore deve essere connessə per svuotarne l'inventario!
|
||||
Players can't be killed, damage has been disabled.=Lɜ giocatori non possono essere uccisɜ, il danno è disabilitato.
|
||||
Player @1 is not online.=Lə 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).=
|
||||
Kill player or yourself=Uccide unə giocatore o te stessǝ
|
||||
@1 joined the game.=@1 si è connessə.
|
||||
@1 left the game.=@1 si è disconnessə.
|
||||
@1 left the game (timed out).=@1 si è disconnessə (connessione scaduta).
|
||||
(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 basic privileges (@1)=Si possono modificare i privilegi di base (@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 teleport other players=Si possono teletrasportare lɜ altrɜ 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 ban and unban players=Si possono bandire e perdonare lɜ giocatori
|
||||
Can kick players=Si possono cacciare lɜ 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 view more debug info that might give a gameplay advantage=
|
||||
Can enable wireframe=
|
||||
Can enable wireframe=Si può abilitare la vista reticolata
|
||||
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 #####
|
||||
|
||||
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'
|
||||
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 registro 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).
|
||||
Values below show absolute/relative times spend per server step by the instrumented function.=I valori sottostanti mostrano i tempi assoluti/relativi impiegati su ogni singolo step dalla funzione analizzata
|
||||
A total of @1 sample(s) were taken.=Son stati ottenuti campioni per un totale di @1.
|
||||
The output is limited to '@1'.=L'output è limitato a '@1'.
|
||||
Saving of profile failed: @1=Errore nel salvare il profilo: @1
|
||||
Profile saved to @1=Profilo salvato in @1
|
||||
|
||||
246
builtin/locale/__builtin.ms.tr
Normal file
246
builtin/locale/__builtin.ms.tr
Normal file
@@ -0,0 +1,246 @@
|
||||
# textdomain: __builtin
|
||||
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
|
||||
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
|
||||
@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'!
|
||||
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).
|
||||
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
|
||||
246
builtin/locale/__builtin.ru.tr
Normal file
246
builtin/locale/__builtin.ru.tr
Normal file
@@ -0,0 +1,246 @@
|
||||
# textdomain: __builtin
|
||||
Invalid parameters (see /help @1).=Недопустимые параметры (см. /help @1).
|
||||
Too many arguments, try using just /help <command>=Слишком много аргументов, попробуйте использовать просто /help <команда>
|
||||
Available commands: @1=Доступные команды: @1
|
||||
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Используйте '/help <команда>', чтобы получить дополнительную информацию, или '/help all', чтобы перечислить все.
|
||||
Available commands:=Доступные команды:
|
||||
Command not available: @1=Команда недоступна: @1
|
||||
[all | privs | <cmd>] [-t]=[all | privs | <команда>] [-t]
|
||||
Get help for commands or list privileges (-t: output in chat)=Получить справку по командам или списку привилегий (-t: вывод в чате)
|
||||
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=Команда: @1 @2
|
||||
Available commands: (see also: /help <cmd>)=Доступные команды: (смотрите также: /help <команда>)
|
||||
Close=Закрыть
|
||||
Privilege=Привилегия
|
||||
Description=Описание
|
||||
Empty command.=Пустая команда.
|
||||
Invalid command: @1=Недопустимая команда: @1
|
||||
Invalid command usage.=Недопустимое использование команды.
|
||||
(@1 s)= (@1 с)
|
||||
Command execution took @1 s=Выполнение команды заняло @1 с
|
||||
You don't have permission to run this command (missing privileges: @1).=У вас нет разрешения на запуск этой команды (отсутствуют привилегии: @1).
|
||||
Unable to get position of player @1.=Не удалось получить позицию игрока @1.
|
||||
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Неправильный формат области. Ожидаемое: (x1,y1,z1) (x2,y2,z2)
|
||||
<action>=<действие>
|
||||
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Показать действие в чате (например, '/me заказывает пиццу' отображает '<имя игрока> заказывает пиццу')
|
||||
Show the name of the server owner=Показать имя владельца сервера
|
||||
The administrator of this server is @1.=Администратором этого сервера является @1.
|
||||
There's no administrator named in the config file.=В конфигурационном файле нет имени администратора.
|
||||
@1 does not have any privileges.=@1 не имеет никаких привилегий.
|
||||
Privileges of @1: @2=Привилегии @1: @2
|
||||
[<name>]=[<имя>]
|
||||
Show privileges of yourself or another player=Показать ваши привилегии или привилегии другого игрока
|
||||
Player @1 does not exist.=Игрок @1 не существует.
|
||||
<privilege>=<привилегия>
|
||||
Return list of all online players with privilege=Возвращает список всех онлайн-игроков, имеющих указанную привилегию
|
||||
Invalid parameters (see /help haspriv).=Недопустимые параметры (см. /help haspriv).
|
||||
Unknown privilege!=Неизвестная привилегия!
|
||||
No online player has the "@1" privilege.=Ни один онлайн-игрок не имеет привилегии "@1".
|
||||
Players online with the "@1" privilege: @2=Игроки онлайн с привилегией "@1": @2
|
||||
Your privileges are insufficient.=Ваших привилегий недостаточно.
|
||||
Your privileges are insufficient. '@1' only allows you to grant: @2=Ваших привилегий недостаточно. '@1' позволяет вам предоставлять только: @2
|
||||
Unknown privilege: @1=Неизвестная привилегия: @1
|
||||
@1 granted you privileges: @2=@1 предоставил(а) вам привилегии: @2
|
||||
<name> (<privilege> [, <privilege2> [<...>]] | all)=<имя> (<привилегия> [, <привилегия2> [<...>]] | all)
|
||||
Give privileges to player=Предоставить привилегии игроку
|
||||
Invalid parameters (see /help grant).=Недопустимые параметры (см. /help grant).
|
||||
<privilege> [, <privilege2> [<...>]] | all=<привилегия> [, <привилегия2> [<...>]] | all
|
||||
Grant privileges to yourself=Предоставить привилегии себе
|
||||
Invalid parameters (see /help grantme).=Недопустимые параметры (см. /help grantme).
|
||||
Your privileges are insufficient. '@1' only allows you to revoke: @2=Ваших привилегий недостаточно. '@1' позволяет вам отозвать только: @2
|
||||
Note: Cannot revoke in singleplayer: @1=Примечание: Невозможно отозвать в одиночной игре: @1
|
||||
Note: Cannot revoke from admin: @1=Примечание: Невозможно отозвать у администратора: @1
|
||||
No privileges were revoked.=Никакие привилегии не были отозваны.
|
||||
@1 revoked privileges from you: @2=@1 отозвал у вас привилегии: @2
|
||||
Remove privileges from player=Отозвать привилегии у игрока
|
||||
Invalid parameters (see /help revoke).=Недопустимые параметры (см. /help revoke).
|
||||
Revoke privileges from yourself=Отозвать привилегии у себя
|
||||
Invalid parameters (see /help revokeme).=Недопустимые параметры (см. /help revokeme).
|
||||
<name> <password>=<имя> <пароль>
|
||||
Set player's password (sent unencrypted, thus insecure)=Установить пароль игрока (отправка в незашифрованном виде, следовательно, небезопасно)
|
||||
Name field required.=Обязательное поле "Имя".
|
||||
Your password was cleared by @1.=@1 сбросил ваш пароль.
|
||||
Password of player "@1" cleared.=Пароль игрока "@1" сброшен.
|
||||
Your password was set by @1.=@1 установил вам пароль.
|
||||
Password of player "@1" set.=Установлен пароль игрока "@1".
|
||||
<name>=<имя>
|
||||
Set empty password for a player=Установить пустой пароль для игрока
|
||||
Reload authentication data=Перезагрузить аутентификационные данные
|
||||
Done.=Готово.
|
||||
Failed.=Не удалось.
|
||||
Remove a player's data=Удалить данные игрока
|
||||
Player "@1" removed.=Игрок "@1" удален.
|
||||
No such player "@1" to remove.=Нет такого игрока "@1", которого можно было бы удалить.
|
||||
Player "@1" is connected, cannot remove.=Игрок "@1" сейчас в игре, удалить его невозможно.
|
||||
Unhandled remove_player return code @1.=Необработанный код возврата remove_player @1.
|
||||
Cannot teleport out of map bounds!=Невозможно телепортироваться за пределы карты!
|
||||
Cannot get player with name @1.=Не удается получить игрока с именем @1.
|
||||
Cannot teleport, @1 is attached to an object!=Невозможно телепортироваться, @1 привязан к объекту!
|
||||
Teleporting @1 to @2.=Телепортация @1 в @2.
|
||||
One does not teleport to oneself.=Игрок не может телепортироваться к самому себе.
|
||||
Cannot get teleportee with name @1.=Не удается найти телепортирующегося с именем @1.
|
||||
Cannot get target player with name @1.=Не удается найти целевого игрока с именем @1.
|
||||
Teleporting @1 to @2 at @3.=Телепортация @1 к @2 в @3.
|
||||
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <имя_к_кому> | <имя> <X>,<Y>,<Z> | <имя> <имя_к_кому>
|
||||
Teleport to position or player=Телепортироваться на позицию или к игроку
|
||||
You don't have permission to teleport other players (missing privilege: @1).=У вас нет разрешения на телепортацию других игроков (отсутствует привилегия: @1).
|
||||
([-n] <name> <value>) | <name>=([-n] <имя> <значение>) | <имя>
|
||||
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.=Ошибка. Используйте '/set -n <имя> <значение>', чтобы создать новую настройку.
|
||||
@1 @= @2=@1 @= @2
|
||||
<not set>=<не задано>
|
||||
Invalid parameters (see /help set).=Недопустимые параметры (см. /help set).
|
||||
Finished emerging @1 blocks in @2ms.=Загрузка/генерация @1 map-блоков завершена за @2мс.
|
||||
emergeblocks update: @1/@2 blocks emerged (@3%)=обновление emergeblocks: загружено/сгенерировано: @1 из @2 блоков (@3%)
|
||||
(here [<radius>]) | (<pos1> <pos2>)=(here [<радиус>]) | (<позиция1> <позиция2>)
|
||||
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Загрузить (или сгенерировать, если не существуют) map-блоки, содержащиеся в области с позиции1 по позицию2 (<позиция1> и <позиция2> должны быть в круглых скобках)
|
||||
Started emerge of area ranging from @1 to @2.=Загрузка/генерация области в диапазоне от @1 до @2 начата.
|
||||
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Удалить map-блоки, содержащиеся в области с позиции1 по позицию2 (<позиция1> и <позиция2> должны быть заключены в круглые скобки)
|
||||
Successfully cleared area ranging from @1 to @2.=Успешно очищена область в диапазоне от @1 до @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)=Сбрасывает освещение в области с позиции1 по позицию2 (<позиция1> и <позиция2> должны быть заключены в круглые скобки)
|
||||
Successfully reset light in the area ranging from @1 to @2.=Успешно сброшено освещение в диапазоне от @1 до @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.=Выдача 'ignore' не допускается.
|
||||
@1 is not a known player.=Неизвестный игрок: @1.
|
||||
@1 partially added to inventory.=@1 частично добавлен в инвентарь.
|
||||
@1 could not be added to inventory.=@1 не удалось добавить в инвентарь.
|
||||
@1 added to inventory.=@1 добавлено в инвентарь.
|
||||
@1 partially added to inventory of @2.=@1 частично добавлен в инвентарь @2.
|
||||
@1 could not be added to inventory of @2.=@1 не удалось добавить в инвентарь @2.
|
||||
@1 added to inventory of @2.=@1 добавлен в инвентарь @2.
|
||||
<name> <ItemString> [<count> [<wear>]]=<имя> <ItemString> [<количество> [<износ>]]
|
||||
Give item to player=Добавить предмет в инвентарь игрока
|
||||
Name and ItemString required.=Требуется <имя> игрока и название предмета <ItemString>.
|
||||
<ItemString> [<count> [<wear>]]=<ItemString> [<количество> [<износ>]]
|
||||
Give item to yourself=Добавить предмет в свой инвентарь
|
||||
ItemString required.=Требуется название предмета <ItemString>.
|
||||
<EntityName> [<X>,<Y>,<Z>]=<EntityName> [<X>,<Y>,<Z>]
|
||||
Spawn entity at given (or your) position=Спаун сущности (моб/стрела/...) в заданной (или вашей) позиции
|
||||
EntityName required.=Требуется имя сущности <EntityName>.
|
||||
Unable to spawn entity, player is nil.=Не удается создать сущность, игрок не найден.
|
||||
Cannot spawn an unknown entity.=Не удается создать неизвестную сущность.
|
||||
Invalid parameters (@1).=Недопустимые параметры (@1).
|
||||
@1 spawned.=Сущность @1 создана.
|
||||
@1 failed to spawn.=Сущность @1 не удалось создать.
|
||||
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=Проверить кто в последний раз прикасался к ноде или нодам рядом в течение времени, указанного в <секундах>. По умолчанию: диапазон @= 0, секунды @= 86400 @= 24 часа, ограничение @= 5. Установите <секунды> в значение inf для отключения ограничения по времени
|
||||
Rollback functions are disabled.=Функции отката отключены.
|
||||
That limit is too high!=Это <ограничение> слишком велико!
|
||||
Checking @1 ...=Проверка @1 ...
|
||||
Nobody has touched the specified location in @1 seconds.=Никто не прикасался к указанному местоположению в течение @1 секунд(ы).
|
||||
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 секунд назад.
|
||||
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Ударьте по ноде (диапазон@=@1, секунды@=@2, предел @=@3).
|
||||
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<имя> [<секунды>]) | (:<игрок> [<секунды>])
|
||||
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Отменяет действия игрока. Значение по умолчанию для <секунд> равно 60. Установите <секунды> в значение inf для отключения ограничения по времени
|
||||
Invalid parameters. See /help rollback and /help rollback_check.=Недопустимые параметры. Смотрите /help rollback и /help rollback_check.
|
||||
Reverting actions of player '@1' since @2 seconds.=Отмена действия игрока "@1" за последние @2 секунд(у/ы).
|
||||
Reverting actions of @1 since @2 seconds.=Отмена действий @1 за последние @2 секунд(у/ы).
|
||||
(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>]=[<0..23>:<0..59> | <0..24000>]
|
||||
Show or set time of day=Показать или установить время суток
|
||||
Current time is @1:@2.=Текущее время @1:@2.
|
||||
You don't have permission to run this command (missing privilege: @1).=У вас нет разрешения на выполнение этой команды (отсутствует привилегия: @1).
|
||||
Invalid time (must be between 0 and 24000).=Недопустимое время (должно быть в диапазоне от 0 до 24000).
|
||||
Time of day changed.=Время суток изменено.
|
||||
Invalid hour (must be between 0 and 23 inclusive).=Недопустимый час (должно быть от 0 до 23 включительно).
|
||||
Invalid minute (must be between 0 and 59 inclusive).=Недопустимая минута (должно быть от 0 до 59 включительно).
|
||||
Show day count since world creation=Показать количество дней с момента сотворения мира
|
||||
Current day is @1.=Текущий день: @1.
|
||||
[<delay_in_seconds> | -1] [-r] [<message>]=[<задержка_в_секундах> | -1] [-r] [<сообщение>]
|
||||
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Завершение работы сервера (-1 отменяет отложенное завершение работы, -r позволяет игрокам повторно подключиться)
|
||||
Server shutting down (operator request).=Завершение работы сервера (по запросу администрации).
|
||||
Ban the IP of a player or show the ban list=Заблокировать IP игрока или показать список заблокированных
|
||||
The ban list is empty.=Список заблокированных пуст.
|
||||
Ban list: @1=Список заблокированных: @1
|
||||
You cannot ban players in singleplayer!=Вы не можете забанить игроков в одиночной игре!
|
||||
Player is not online.=Игрок не подключен к Сети.
|
||||
Failed to ban player.=Не удалось забанить игрока.
|
||||
Banned @1.=@1 забанен.
|
||||
<name> | <IP_address>=<имя> | <IP_адрес>
|
||||
Remove IP ban belonging to a player/IP=Удалить IP-бан, принадлежащий игроку/IP-адресу
|
||||
Failed to unban player/IP.=Не удалось разбанить игрока/IP.
|
||||
Unbanned @1.=@1 разбанен.
|
||||
<name> [<reason>]=<имя> [<причина>]
|
||||
Kick a player=Кикнуть игрока
|
||||
Failed to kick player @1.=Не удалось кикнуть @1.
|
||||
Kicked @1.=@1 кикнут.
|
||||
[full | quick]=[full | quick]
|
||||
Clear all objects in world=Удалить ВСЕ объекты/сущности во ВСЁМ мире
|
||||
Invalid usage, see /help clearobjects.=Недопустимое использование, см. /help clearobjects.
|
||||
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Удаление всех объектов. Это может занять много времени. Ваш клиент может отключиться из-за тайм-аута. (запущено: @1)
|
||||
Cleared all objects.=Все объекты удалены.
|
||||
<name> <message>=<имя> <сообщение>
|
||||
Send a direct message to a player=Отправить прямое сообщение игроку
|
||||
Invalid usage, see /help msg.=Недопустимое использование, см. /help msg.
|
||||
The player @1 is not online.=Игрок @1 не находится в игре.
|
||||
DM from @1: @2=DM от @1: @2
|
||||
Message sent.=Сообщение отправлено.
|
||||
Get the last login time of a player or yourself=Вывести время последнего входа игрока или своё
|
||||
@1's last login time was @2.=Время последнего входа @1: @2.
|
||||
@1's last login time is unknown.=Время последнего входа @1 неизвестно.
|
||||
Clear the inventory of yourself or another player=Очистить свой инвентарь или инвентарь другого игрока
|
||||
You don't have permission to clear another player's inventory (missing privilege: @1).=У вас нет разрешения на очистку инвентаря другого игрока (отсутствует привилегия: @1).
|
||||
@1 cleared your inventory.=@1 очистил ваш инвентарь.
|
||||
Cleared @1's inventory.=Инвентарь @1 очищен.
|
||||
Player must be online to clear inventory!=Игрок должен быть онлайн, чтобы очистить его инвентарь!
|
||||
Players can't be killed, damage has been disabled.=Игроки не могут быть убиты, урон отключен.
|
||||
Player @1 is not online.=Игрок @1 не находится в игре.
|
||||
You are already dead.=Ты уже мертв.
|
||||
@1 is already dead.=@1 уже мертв.
|
||||
@1 has been killed.=@1 был убит.
|
||||
Kill player or yourself=Убить игрока или себя
|
||||
@1 joined the game.=@1 присоединился к игре.
|
||||
@1 left the game.=@1 вышел из игры.
|
||||
@1 left the game (timed out).=@1 вышел из игры (тайм-аут).
|
||||
(no description)=(без описания)
|
||||
Can interact with things and modify the world=Может взаимодействовать (с объектами/игроками/...) и изменять мир
|
||||
Can speak in chat=Может общаться в чате
|
||||
Can modify basic privileges (@1)=Может изменять базовые привилегии (@1)
|
||||
Can modify privileges=Может изменять привилегии
|
||||
Can teleport self=Может самостоятельно телепортироваться
|
||||
Can teleport other players=Может телепортировать других игроков
|
||||
Can set the time of day using /time=Может устанавливать время суток с помощью /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=Может использовать /give и /giveme
|
||||
Can use /setpassword and /clearpassword=Может использовать /setpassword и /clearpassword
|
||||
Can use fly mode=Может использовать режим полета
|
||||
Can use fast mode=Может использовать быстрый режим (ускорение)
|
||||
Can fly through solid nodes using noclip mode=Может пролетать сквозь ноды, используя режим noclip
|
||||
Can use the rollback functionality=Может использовать функцию отката
|
||||
Can enable wireframe=Может использовать режим каркаса в отладке
|
||||
Unknown Item=Неизвестный предмет
|
||||
Air=Воздух
|
||||
Ignore=Игнорируемая встроенная нода (":ignore")
|
||||
You can't place 'ignore' nodes!=Вы не можете установить ноду 'ignore'!
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset
|
||||
Handle the profiler and profiling data=Работа с профайлером и данными профилирования
|
||||
Statistics written to action log.=Статистика записывается в журнал действий.
|
||||
Statistics were reset.=Статистика была сброшена.
|
||||
Usage: @1=Использование: @1
|
||||
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Формат может быть одним из txt, csv, lua, json, json_pretty (структуры могут быть изменены).
|
||||
Values below show absolute/relative times spend per server step by the instrumented function.=Приведенные ниже значения показывают абсолютное/относительное время, затрачиваемое функцией на каждый шаг сервера.
|
||||
A total of @1 sample(s) were taken.=Всего было взято @1 образец(ов).
|
||||
The output is limited to '@1'.=Вывод ограничен значением '@1'.
|
||||
Saving of profile failed: @1=Не удалось сохранить данные профилирования: @1
|
||||
Profile saved to @1=Данные профилирования сохранены в @1
|
||||
@@ -1,4 +1,22 @@
|
||||
# textdomain: __builtin
|
||||
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=
|
||||
Empty command.=
|
||||
Invalid command: @1=
|
||||
Invalid command usage.=
|
||||
@@ -43,7 +61,7 @@ Invalid parameters (see /help revoke).=
|
||||
Revoke privileges from yourself=
|
||||
Invalid parameters (see /help revokeme).=
|
||||
<name> <password>=
|
||||
Set player's password=
|
||||
Set player's password (sent unencrypted, thus insecure)=
|
||||
Name field required.=
|
||||
Your password was cleared by @1.=
|
||||
Password of player "@1" cleared.=
|
||||
@@ -72,6 +90,7 @@ Teleport to position or player=
|
||||
You don't have permission to teleport other players (missing privilege: @1).=
|
||||
([-n] <name> <value>) | <name>=
|
||||
Set or read server configuration setting=
|
||||
Failed. Cannot modify secure settings. Edit the settings file manually.=
|
||||
Failed. Use '/set -n <name> <value>' to create a new setting.=
|
||||
@1 @= @2=
|
||||
<not set>=
|
||||
@@ -151,6 +170,7 @@ Server shutting down (operator request).=
|
||||
Ban the IP of a player or show the ban list=
|
||||
The ban list is empty.=
|
||||
Ban list: @1=
|
||||
You cannot ban players in singleplayer!=
|
||||
Player is not online.=
|
||||
Failed to ban player.=
|
||||
Banned @1.=
|
||||
@@ -187,30 +207,6 @@ 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).=
|
||||
@@ -232,12 +228,17 @@ Can use fly mode=
|
||||
Can use fast mode=
|
||||
Can fly through solid nodes using noclip mode=
|
||||
Can use the rollback functionality=
|
||||
Can view more debug info that might give a gameplay advantage=
|
||||
Can enable wireframe=
|
||||
Unknown Item=
|
||||
Air=
|
||||
Ignore=
|
||||
You can't place 'ignore' nodes!=
|
||||
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).=
|
||||
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'.=
|
||||
|
||||
@@ -45,6 +45,27 @@ 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]
|
||||
end
|
||||
|
||||
function render_serverlist_row(spec)
|
||||
local text = ""
|
||||
if spec.name then
|
||||
@@ -125,18 +146,12 @@ os.tmpname = function()
|
||||
end
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function menu_render_worldlist(show_gameid)
|
||||
function menu_render_worldlist()
|
||||
local retval = {}
|
||||
local current_worldlist = menudata.worldlist:get_list()
|
||||
|
||||
local row
|
||||
for i, v in ipairs(current_worldlist) do
|
||||
row = v.name
|
||||
if show_gameid == nil or show_gameid == true then
|
||||
row = row .. " [" .. v.gameid .. "]"
|
||||
end
|
||||
retval[#retval+1] = core.formspec_escape(row)
|
||||
|
||||
retval[#retval+1] = core.formspec_escape(v.name)
|
||||
end
|
||||
|
||||
return table.concat(retval, ",")
|
||||
@@ -242,3 +257,11 @@ function menu_worldmt_legacy(selected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function confirmation_formspec(message, confirm_id, confirm_label, cancel_id, cancel_label)
|
||||
return "size[10,2.5,true]" ..
|
||||
"label[0.5,0.5;" .. message .. "]" ..
|
||||
"style[" .. confirm_id .. ";bgcolor=red]" ..
|
||||
"button[0.5,1.5;2.5,0.5;" .. confirm_id .. ";" .. confirm_label .. "]" ..
|
||||
"button[7.0,1.5;2.5,0.5;" .. cancel_id .. ";" .. cancel_label .. "]"
|
||||
end
|
||||
|
||||
@@ -23,9 +23,18 @@ if not core.get_http_api then
|
||||
return
|
||||
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 = {
|
||||
loading = false,
|
||||
load_ok = false,
|
||||
load_error = false,
|
||||
|
||||
-- Unordered preserves the original order of the ContentDB API,
|
||||
-- before the package list is ordered based on installed state.
|
||||
packages = {},
|
||||
packages_full = {},
|
||||
packages_full_unordered = {},
|
||||
aliases = {},
|
||||
}
|
||||
|
||||
local http = core.get_http_api()
|
||||
|
||||
@@ -47,6 +56,9 @@ local filter_types_titles = {
|
||||
fgettext("Texture packs"),
|
||||
}
|
||||
|
||||
-- Automatic package installation
|
||||
local auto_install_spec = nil
|
||||
|
||||
local number_downloading = 0
|
||||
local download_queue = {}
|
||||
|
||||
@@ -62,15 +74,6 @@ 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(
|
||||
@@ -89,7 +92,7 @@ local function download_and_extract(param)
|
||||
if filename == "" or not core.download_file(param.url, filename) then
|
||||
core.log("error", "Downloading " .. dump(param.url) .. " failed")
|
||||
return {
|
||||
msg = fgettext("Failed to download $1", package.name)
|
||||
msg = fgettext_ne("Failed to download \"$1\"", package.title)
|
||||
}
|
||||
end
|
||||
|
||||
@@ -105,7 +108,7 @@ local function download_and_extract(param)
|
||||
os.remove(filename)
|
||||
if not tempfolder then
|
||||
return {
|
||||
msg = fgettext("Install: Unsupported file type or broken archive"),
|
||||
msg = fgettext_ne("Failed to extract \"$1\" (unsupported file type or broken archive)", package.title),
|
||||
}
|
||||
end
|
||||
|
||||
@@ -129,7 +132,7 @@ local function start_install(package, reason)
|
||||
local path, msg = pkgmgr.install_dir(package.type, result.path, package.name, package.path)
|
||||
core.delete_dir(result.path)
|
||||
if not path then
|
||||
gamedata.errormessage = msg
|
||||
gamedata.errormessage = fgettext_ne("Error installing \"$1\": $2", package.title, msg)
|
||||
else
|
||||
core.log("action", "Installed package to " .. path)
|
||||
|
||||
@@ -151,11 +154,9 @@ local function start_install(package, reason)
|
||||
|
||||
if conf_path then
|
||||
local conf = Settings(conf_path)
|
||||
if name_is_title then
|
||||
conf:set("name", package.title)
|
||||
else
|
||||
conf:set("title", package.title)
|
||||
conf:set("name", package.name)
|
||||
conf:set("title", package.title)
|
||||
if not name_is_title then
|
||||
conf:set("name", package.name)
|
||||
end
|
||||
if not conf:get("description") then
|
||||
conf:set("description", package.short_description)
|
||||
@@ -186,14 +187,18 @@ local function start_install(package, reason)
|
||||
|
||||
if not core.handle_async(download_and_extract, params, callback) then
|
||||
core.log("error", "ERROR: async event failed")
|
||||
gamedata.errormessage = fgettext("Failed to download $1", package.name)
|
||||
gamedata.errormessage = fgettext_ne("Failed to download $1", package.name)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local function queue_download(package, reason)
|
||||
if package.queued or package.downloading then
|
||||
return
|
||||
end
|
||||
|
||||
local max_concurrent_downloads = tonumber(core.settings:get("contentdb_max_concurrent_downloads"))
|
||||
if number_downloading < max_concurrent_downloads then
|
||||
if number_downloading < math.max(max_concurrent_downloads, 1) then
|
||||
start_install(package, reason)
|
||||
else
|
||||
table.insert(download_queue, { package = package, reason = reason })
|
||||
@@ -202,6 +207,9 @@ local function queue_download(package, reason)
|
||||
end
|
||||
|
||||
local function get_raw_dependencies(package)
|
||||
if package.type ~= "mod" then
|
||||
return {}
|
||||
end
|
||||
if package.raw_deps then
|
||||
return package.raw_deps
|
||||
end
|
||||
@@ -209,7 +217,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.url_part, core.get_max_supp_proto(), core.urlencode(version.string))
|
||||
|
||||
local response = http.fetch_sync({ url = url })
|
||||
if not response.succeeded then
|
||||
@@ -280,7 +288,7 @@ local function resolve_dependencies_2(raw_deps, installed_mods, out)
|
||||
end
|
||||
end
|
||||
|
||||
-- Otherwise, find the first mod that fulfils it
|
||||
-- Otherwise, find the first mod that fulfills it
|
||||
if fallback then
|
||||
return {
|
||||
is_optional = dep.is_optional,
|
||||
@@ -348,22 +356,21 @@ 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
|
||||
|
||||
@@ -410,7 +417,7 @@ function install_dialog.get_formspec()
|
||||
"container[0.375,0.70]",
|
||||
|
||||
"label[0,0.25;", fgettext("Base Game:"), "]",
|
||||
"dropdown[2,0;4.25,0.5;gameid;", table.concat(games, ","), ";", selected_game_idx, "]",
|
||||
"dropdown[2,0;4.25,0.5;selected_game;", table.concat(game_list, ","), ";", selected_game_idx, "]",
|
||||
|
||||
"label[0,0.8;", fgettext("Dependencies:"), "]",
|
||||
|
||||
@@ -432,7 +439,7 @@ function install_dialog.get_formspec()
|
||||
"container_end[]",
|
||||
}
|
||||
|
||||
return table.concat(formspec, "")
|
||||
return table.concat(formspec)
|
||||
end
|
||||
|
||||
function install_dialog.handle_submit(this, fields)
|
||||
@@ -461,9 +468,9 @@ function install_dialog.handle_submit(this, fields)
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.gameid then
|
||||
if fields.selected_game then
|
||||
for _, game in pairs(pkgmgr.games) do
|
||||
if game.name == fields.gameid then
|
||||
if game.title == fields.selected_game then
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
break
|
||||
end
|
||||
@@ -490,12 +497,10 @@ local confirm_overwrite = {}
|
||||
function confirm_overwrite.get_formspec()
|
||||
local package = confirm_overwrite.package
|
||||
|
||||
return "size[11.5,4.5,true]" ..
|
||||
"label[2,2;" ..
|
||||
fgettext("\"$1\" already exists. Would you like to overwrite it?", package.name) .. "]"..
|
||||
"style[install;bgcolor=red]" ..
|
||||
"button[3.25,3.5;2.5,0.5;install;" .. fgettext("Overwrite") .. "]" ..
|
||||
"button[5.75,3.5;2.5,0.5;cancel;" .. fgettext("Cancel") .. "]"
|
||||
return confirmation_formspec(
|
||||
fgettext("\"$1\" already exists. Would you like to overwrite it?", package.name),
|
||||
'install', fgettext("Overwrite"),
|
||||
'cancel', fgettext("Cancel"))
|
||||
end
|
||||
|
||||
function confirm_overwrite.handle_submit(this, fields)
|
||||
@@ -525,6 +530,50 @@ function confirm_overwrite.create(package, callback)
|
||||
nil)
|
||||
end
|
||||
|
||||
local function install_or_update_package(this, package)
|
||||
local install_parent
|
||||
if package.type == "mod" then
|
||||
install_parent = core.get_modpath()
|
||||
elseif package.type == "game" then
|
||||
install_parent = core.get_gamepath()
|
||||
elseif package.type == "txp" then
|
||||
install_parent = core.get_texturepath()
|
||||
else
|
||||
error("Unknown package type: " .. package.type)
|
||||
end
|
||||
|
||||
if package.queued or package.downloading then
|
||||
return
|
||||
end
|
||||
|
||||
local function on_confirm()
|
||||
local deps = get_raw_dependencies(package)
|
||||
if deps and has_hard_deps(deps) then
|
||||
local dlg = install_dialog.create(package, deps)
|
||||
dlg:set_parent(this)
|
||||
this:hide()
|
||||
dlg:show()
|
||||
else
|
||||
queue_download(package, package.path and REASON_UPDATE or REASON_NEW)
|
||||
end
|
||||
end
|
||||
|
||||
if package.type == "mod" and #pkgmgr.games == 0 then
|
||||
local dlg = messagebox("install_game",
|
||||
fgettext("You need to install a game before you can install a mod"))
|
||||
dlg:set_parent(this)
|
||||
this:hide()
|
||||
dlg:show()
|
||||
elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
|
||||
local dlg = confirm_overwrite.create(package, on_confirm)
|
||||
dlg:set_parent(this)
|
||||
this:hide()
|
||||
dlg:show()
|
||||
else
|
||||
on_confirm()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_file_extension(path)
|
||||
local parts = path:split(".")
|
||||
@@ -579,29 +628,33 @@ local function get_screenshot(package)
|
||||
return defaulttexturedir .. "loading_screenshot.png"
|
||||
end
|
||||
|
||||
function store.load()
|
||||
local function fetch_pkgs()
|
||||
local version = core.get_version()
|
||||
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=" .. core.urlencode(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=" .. core.urlencode(item)
|
||||
end
|
||||
end
|
||||
|
||||
local http = core.get_http_api()
|
||||
local response = http.fetch_sync({ url = url })
|
||||
if not response.succeeded then
|
||||
return
|
||||
end
|
||||
|
||||
store.packages_full = core.parse_json(response.data) or {}
|
||||
store.aliases = {}
|
||||
local packages = core.parse_json(response.data)
|
||||
if not packages or #packages == 0 then
|
||||
return
|
||||
end
|
||||
local aliases = {}
|
||||
|
||||
for _, package in pairs(store.packages_full) do
|
||||
for _, package in pairs(packages) do
|
||||
local name_len = #package.name
|
||||
-- This must match what store.update_paths() does!
|
||||
package.id = package.author:lower() .. "/"
|
||||
@@ -611,48 +664,137 @@ function store.load()
|
||||
package.id = package.id .. package.name
|
||||
end
|
||||
|
||||
package.url_part = urlencode(package.author) .. "/" .. urlencode(package.name)
|
||||
package.url_part = core.urlencode(package.author) .. "/" .. core.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
|
||||
aliases[alias:lower()] = package.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
store.packages_full_unordered = store.packages_full
|
||||
store.packages = store.packages_full
|
||||
store.loaded = true
|
||||
return { packages = packages, aliases = aliases }
|
||||
end
|
||||
|
||||
local function sort_and_filter_pkgs()
|
||||
store.update_paths()
|
||||
store.sort_packages()
|
||||
store.filter_packages(search_string)
|
||||
end
|
||||
|
||||
-- Resolves the package specification stored in auto_install_spec into an actual package.
|
||||
-- May only be called after the package list has been loaded successfully.
|
||||
local function resolve_auto_install_spec()
|
||||
assert(store.load_ok)
|
||||
|
||||
if not auto_install_spec then
|
||||
return nil
|
||||
end
|
||||
|
||||
local spec = store.aliases[auto_install_spec] or auto_install_spec
|
||||
local resolved = nil
|
||||
|
||||
for _, pkg in ipairs(store.packages_full_unordered) do
|
||||
if pkg.id == spec then
|
||||
resolved = pkg
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not resolved then
|
||||
gamedata.errormessage = fgettext("The package $1 was not found.", auto_install_spec)
|
||||
ui.update()
|
||||
|
||||
auto_install_spec = nil
|
||||
end
|
||||
|
||||
return resolved
|
||||
end
|
||||
|
||||
-- Installs the package specified by auto_install_spec.
|
||||
-- Only does something if:
|
||||
-- a. The package list has been loaded successfully.
|
||||
-- b. The store dialog is currently visible.
|
||||
local function do_auto_install()
|
||||
if not store.load_ok then
|
||||
return
|
||||
end
|
||||
|
||||
local pkg = resolve_auto_install_spec()
|
||||
if not pkg then
|
||||
return
|
||||
end
|
||||
|
||||
local store_dlg = ui.find_by_name("store")
|
||||
if not store_dlg or store_dlg.hidden then
|
||||
return
|
||||
end
|
||||
|
||||
install_or_update_package(store_dlg, pkg)
|
||||
auto_install_spec = nil
|
||||
end
|
||||
|
||||
function store.load()
|
||||
if store.load_ok then
|
||||
sort_and_filter_pkgs()
|
||||
return
|
||||
end
|
||||
if store.loading then
|
||||
return
|
||||
end
|
||||
store.loading = true
|
||||
core.handle_async(
|
||||
fetch_pkgs,
|
||||
nil,
|
||||
function(result)
|
||||
if result then
|
||||
store.load_ok = true
|
||||
store.load_error = false
|
||||
store.packages = result.packages
|
||||
store.packages_full = result.packages
|
||||
store.packages_full_unordered = result.packages
|
||||
store.aliases = result.aliases
|
||||
|
||||
sort_and_filter_pkgs()
|
||||
do_auto_install()
|
||||
else
|
||||
store.load_error = true
|
||||
end
|
||||
|
||||
store.loading = false
|
||||
ui.update()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function store.update_paths()
|
||||
local mod_hash = {}
|
||||
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
|
||||
local cdb_id = pkgmgr.get_contentdb_id(mod)
|
||||
if cdb_id then
|
||||
mod_hash[store.aliases[cdb_id] or cdb_id] = mod
|
||||
end
|
||||
end
|
||||
|
||||
local game_hash = {}
|
||||
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
|
||||
local cdb_id = pkgmgr.get_contentdb_id(game)
|
||||
if cdb_id then
|
||||
game_hash[store.aliases[cdb_id] or cdb_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
|
||||
local cdb_id = pkgmgr.get_contentdb_id(txp)
|
||||
if cdb_id then
|
||||
txp_hash[store.aliases[cdb_id] or cdb_id] = txp
|
||||
end
|
||||
end
|
||||
|
||||
@@ -671,6 +813,7 @@ function store.update_paths()
|
||||
package.installed_release = content.release or 0
|
||||
else
|
||||
package.path = nil
|
||||
package.installed_release = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -678,27 +821,40 @@ end
|
||||
function store.sort_packages()
|
||||
local ret = {}
|
||||
|
||||
local auto_install_pkg = resolve_auto_install_spec() -- can be nil
|
||||
|
||||
-- Add installed content
|
||||
for i=1, #store.packages_full_unordered do
|
||||
local package = store.packages_full_unordered[i]
|
||||
if package.path then
|
||||
ret[#ret + 1] = package
|
||||
for _, pkg in ipairs(store.packages_full_unordered) do
|
||||
if pkg.path and pkg ~= auto_install_pkg then
|
||||
ret[#ret + 1] = pkg
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort installed content by title
|
||||
-- Sort installed content first by "is there an update available?", then by title
|
||||
table.sort(ret, function(a, b)
|
||||
local a_updatable = a.installed_release < a.release
|
||||
local b_updatable = b.installed_release < b.release
|
||||
if a_updatable and not b_updatable then
|
||||
return true
|
||||
elseif b_updatable and not a_updatable then
|
||||
return false
|
||||
end
|
||||
|
||||
return a.title < b.title
|
||||
end)
|
||||
|
||||
-- Add uninstalled content
|
||||
for i=1, #store.packages_full_unordered do
|
||||
local package = store.packages_full_unordered[i]
|
||||
if not package.path then
|
||||
ret[#ret + 1] = package
|
||||
for _, pkg in ipairs(store.packages_full_unordered) do
|
||||
if not pkg.path and pkg ~= auto_install_pkg then
|
||||
ret[#ret + 1] = pkg
|
||||
end
|
||||
end
|
||||
|
||||
-- Put the package that will be auto-installed at the very top
|
||||
if auto_install_pkg then
|
||||
table.insert(ret, 1, auto_install_pkg)
|
||||
end
|
||||
|
||||
store.packages_full = ret
|
||||
end
|
||||
|
||||
@@ -737,7 +893,29 @@ function store.filter_packages(query)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_info_formspec(text)
|
||||
local H = 9.5
|
||||
return table.concat({
|
||||
"formspec_version[6]",
|
||||
"size[15.75,9.5]",
|
||||
TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
|
||||
|
||||
"label[4,4.35;", text, "]",
|
||||
"container[0,", H - 0.8 - 0.375, "]",
|
||||
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
|
||||
"container_end[]",
|
||||
})
|
||||
end
|
||||
|
||||
function store.get_formspec(dlgdata)
|
||||
if store.loading then
|
||||
return get_info_formspec(fgettext("Loading..."))
|
||||
end
|
||||
if store.load_error then
|
||||
return get_info_formspec(fgettext("No packages could be retrieved"))
|
||||
end
|
||||
assert(store.load_ok)
|
||||
|
||||
store.update_paths()
|
||||
|
||||
dlgdata.pagemax = math.max(math.ceil(#store.packages / num_per_page), 1)
|
||||
@@ -747,82 +925,70 @@ function store.get_formspec(dlgdata)
|
||||
|
||||
local W = 15.75
|
||||
local H = 9.5
|
||||
local formspec
|
||||
if #store.packages_full > 0 then
|
||||
formspec = {
|
||||
"formspec_version[3]",
|
||||
"size[15.75,9.5]",
|
||||
"position[0.5,0.55]",
|
||||
local formspec = {
|
||||
"formspec_version[6]",
|
||||
"size[15.75,9.5]",
|
||||
TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
|
||||
|
||||
"style[status,downloading,queued;border=false]",
|
||||
"style[status,downloading,queued;border=false]",
|
||||
|
||||
"container[0.375,0.375]",
|
||||
"field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
|
||||
"field_close_on_enter[search_string;false]",
|
||||
"image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
|
||||
"image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
|
||||
"dropdown[9.6,0;2.4,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
|
||||
"container_end[]",
|
||||
"container[0.375,0.375]",
|
||||
"field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
|
||||
"field_enter_after_edit[search_string;true]",
|
||||
"image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
|
||||
"image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
|
||||
"dropdown[9.175,0;2.7875,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
|
||||
"container_end[]",
|
||||
|
||||
-- Page nav buttons
|
||||
"container[0,", H - 0.8 - 0.375, "]",
|
||||
"button[0.375,0;4,0.8;back;", fgettext("Back to Main Menu"), "]",
|
||||
-- Page nav buttons
|
||||
"container[0,", H - 0.8 - 0.375, "]",
|
||||
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
|
||||
|
||||
"container[", W - 0.375 - 0.8*4 - 2, ",0]",
|
||||
"image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
|
||||
"image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
|
||||
"style[pagenum;border=false]",
|
||||
"button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
|
||||
"image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
|
||||
"image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
|
||||
"container_end[]",
|
||||
"container[", W - 0.375 - 0.8*4 - 2, ",0]",
|
||||
"image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
|
||||
"image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
|
||||
"style[pagenum;border=false]",
|
||||
"button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
|
||||
"image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
|
||||
"image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
|
||||
"container_end[]",
|
||||
|
||||
"container_end[]",
|
||||
}
|
||||
"container_end[]",
|
||||
}
|
||||
|
||||
if number_downloading > 0 then
|
||||
formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;downloading;"
|
||||
if #download_queue > 0 then
|
||||
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", number_downloading, #download_queue)
|
||||
else
|
||||
formspec[#formspec + 1] = fgettext("$1 downloading...", number_downloading)
|
||||
if number_downloading > 0 then
|
||||
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;downloading;"
|
||||
if #download_queue > 0 then
|
||||
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", number_downloading, #download_queue)
|
||||
else
|
||||
formspec[#formspec + 1] = fgettext("$1 downloading...", number_downloading)
|
||||
end
|
||||
formspec[#formspec + 1] = "]"
|
||||
else
|
||||
local num_avail_updates = 0
|
||||
for i=1, #store.packages_full do
|
||||
local package = store.packages_full[i]
|
||||
if package.path and package.installed_release < package.release and
|
||||
not (package.downloading or package.queued) then
|
||||
num_avail_updates = num_avail_updates + 1
|
||||
end
|
||||
end
|
||||
|
||||
if num_avail_updates == 0 then
|
||||
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;status;"
|
||||
formspec[#formspec + 1] = fgettext("No updates")
|
||||
formspec[#formspec + 1] = "]"
|
||||
else
|
||||
local num_avail_updates = 0
|
||||
for i=1, #store.packages_full do
|
||||
local package = store.packages_full[i]
|
||||
if package.path and package.installed_release < package.release and
|
||||
not (package.downloading or package.queued) then
|
||||
num_avail_updates = num_avail_updates + 1
|
||||
end
|
||||
end
|
||||
|
||||
if num_avail_updates == 0 then
|
||||
formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;status;"
|
||||
formspec[#formspec + 1] = fgettext("No updates")
|
||||
formspec[#formspec + 1] = "]"
|
||||
else
|
||||
formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;update_all;"
|
||||
formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
|
||||
formspec[#formspec + 1] = "]"
|
||||
end
|
||||
end
|
||||
|
||||
if #store.packages == 0 then
|
||||
formspec[#formspec + 1] = "label[4,3;"
|
||||
formspec[#formspec + 1] = fgettext("No results")
|
||||
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;update_all;"
|
||||
formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
|
||||
formspec[#formspec + 1] = "]"
|
||||
end
|
||||
else
|
||||
formspec = {
|
||||
"size[12,7]",
|
||||
"position[0.5,0.55]",
|
||||
"label[4,3;", fgettext("No packages could be retrieved"), "]",
|
||||
"container[0,", H - 0.8 - 0.375, "]",
|
||||
"button[0,0;4,0.8;back;", fgettext("Back to Main Menu"), "]",
|
||||
"container_end[]",
|
||||
}
|
||||
end
|
||||
|
||||
if #store.packages == 0 then
|
||||
formspec[#formspec + 1] = "label[4,4.75;"
|
||||
formspec[#formspec + 1] = fgettext("No results")
|
||||
formspec[#formspec + 1] = "]"
|
||||
end
|
||||
|
||||
-- download/queued tooltips always have the same message
|
||||
@@ -851,7 +1017,10 @@ function store.get_formspec(dlgdata)
|
||||
formspec[#formspec + 1] = "]"
|
||||
|
||||
-- buttons
|
||||
local left_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
|
||||
local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
|
||||
|
||||
local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
|
||||
local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
|
||||
formspec[#formspec + 1] = "container["
|
||||
formspec[#formspec + 1] = W - 0.375*2
|
||||
formspec[#formspec + 1] = ",0.1]"
|
||||
@@ -861,28 +1030,28 @@ function store.get_formspec(dlgdata)
|
||||
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
|
||||
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
|
||||
elseif package.queued then
|
||||
formspec[#formspec + 1] = left_base
|
||||
formspec[#formspec + 1] = second_base
|
||||
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
|
||||
elseif not package.path then
|
||||
local elem_name = "install_" .. i .. ";"
|
||||
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
|
||||
formspec[#formspec + 1] = left_base .. "cdb_add.png;" .. elem_name .. "]"
|
||||
formspec[#formspec + 1] = second_base .. "cdb_add.png;" .. elem_name .. "]"
|
||||
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors
|
||||
else
|
||||
if package.installed_release < package.release then
|
||||
|
||||
-- The install_ action also handles updating
|
||||
local elem_name = "install_" .. i .. ";"
|
||||
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
|
||||
formspec[#formspec + 1] = left_base .. "cdb_update.png;" .. elem_name .. "]"
|
||||
formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]"
|
||||
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
|
||||
else
|
||||
|
||||
local elem_name = "uninstall_" .. i .. ";"
|
||||
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
|
||||
formspec[#formspec + 1] = left_base .. "cdb_clear.png;" .. elem_name .. "]"
|
||||
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
|
||||
description_width = description_width - 0.7 - 0.15
|
||||
end
|
||||
|
||||
local elem_name = "uninstall_" .. i .. ";"
|
||||
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
|
||||
formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]"
|
||||
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
|
||||
end
|
||||
|
||||
local web_elem_name = "view_" .. i .. ";"
|
||||
@@ -893,7 +1062,6 @@ function store.get_formspec(dlgdata)
|
||||
formspec[#formspec + 1] = "container_end[]"
|
||||
|
||||
-- description
|
||||
local description_width = W - 0.375*5 - 0.85 - 2*0.7
|
||||
formspec[#formspec + 1] = "textarea[1.855,0.3;"
|
||||
formspec[#formspec + 1] = tostring(description_width)
|
||||
formspec[#formspec + 1] = ",0.8;;;"
|
||||
@@ -903,7 +1071,7 @@ function store.get_formspec(dlgdata)
|
||||
formspec[#formspec + 1] = "container_end[]"
|
||||
end
|
||||
|
||||
return table.concat(formspec, "")
|
||||
return table.concat(formspec)
|
||||
end
|
||||
|
||||
function store.handle_submit(this, fields)
|
||||
@@ -957,6 +1125,7 @@ function store.handle_submit(this, fields)
|
||||
local new_type = table.indexof(filter_types_titles, fields.type)
|
||||
if new_type ~= filter_type then
|
||||
filter_type = new_type
|
||||
cur_page = 1
|
||||
store.filter_packages(search_string)
|
||||
return true
|
||||
end
|
||||
@@ -980,39 +1149,7 @@ function store.handle_submit(this, fields)
|
||||
assert(package)
|
||||
|
||||
if fields["install_" .. i] then
|
||||
local install_parent
|
||||
if package.type == "mod" then
|
||||
install_parent = core.get_modpath()
|
||||
elseif package.type == "game" then
|
||||
install_parent = core.get_gamepath()
|
||||
elseif package.type == "txp" then
|
||||
install_parent = core.get_texturepath()
|
||||
else
|
||||
error("Unknown package type: " .. package.type)
|
||||
end
|
||||
|
||||
|
||||
local function on_confirm()
|
||||
local deps = get_raw_dependencies(package)
|
||||
if deps and has_hard_deps(deps) then
|
||||
local dlg = install_dialog.create(package, deps)
|
||||
dlg:set_parent(this)
|
||||
this:hide()
|
||||
dlg:show()
|
||||
else
|
||||
queue_download(package, package.path and REASON_UPDATE or REASON_NEW)
|
||||
end
|
||||
end
|
||||
|
||||
if not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
|
||||
local dlg = confirm_overwrite.create(package, on_confirm)
|
||||
dlg:set_parent(this)
|
||||
this:hide()
|
||||
dlg:show()
|
||||
else
|
||||
on_confirm()
|
||||
end
|
||||
|
||||
install_or_update_package(this, package)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -1036,17 +1173,30 @@ function store.handle_submit(this, fields)
|
||||
return false
|
||||
end
|
||||
|
||||
function create_store_dlg(type)
|
||||
if not store.loaded or #store.packages_full == 0 then
|
||||
store.load()
|
||||
function store.handle_events(event)
|
||||
if event == "DialogShow" then
|
||||
-- On mobile, don't show the "MINETEST" header behind the dialog.
|
||||
mm_game_theme.set_engine(TOUCHSCREEN_GUI)
|
||||
|
||||
-- If the store is already loaded, auto-install packages here.
|
||||
do_auto_install()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
store.update_paths()
|
||||
store.sort_packages()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Creates a ContentDB dialog.
|
||||
---
|
||||
--- @param type string | nil
|
||||
--- Sets initial package filter. "game", "mod", "txp" or nil (no filter).
|
||||
--- @param install_spec table | nil
|
||||
--- ContentDB ID of package as returned by pkgmgr.get_contentdb_id().
|
||||
--- Sets package to install or update automatically.
|
||||
function create_store_dlg(type, install_spec)
|
||||
search_string = ""
|
||||
cur_page = 1
|
||||
|
||||
if type then
|
||||
-- table.indexof does not work on tables that contain `nil`
|
||||
for i, v in pairs(filter_types_type) do
|
||||
@@ -1055,12 +1205,19 @@ function create_store_dlg(type)
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
filter_type = 1
|
||||
end
|
||||
|
||||
store.filter_packages(search_string)
|
||||
-- Keep the old auto_install_spec if the caller doesn't specify one.
|
||||
if install_spec then
|
||||
auto_install_spec = install_spec
|
||||
end
|
||||
|
||||
store.load()
|
||||
|
||||
return dialog_create("store",
|
||||
store.get_formspec,
|
||||
store.handle_submit,
|
||||
nil)
|
||||
store.handle_events)
|
||||
end
|
||||
22
builtin/mainmenu/content/init.lua
Normal file
22
builtin/mainmenu/content/init.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2023 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 path = core.get_mainmenu_path() .. DIR_DELIM .. "content"
|
||||
|
||||
dofile(path .. DIR_DELIM .. "pkgmgr.lua")
|
||||
dofile(path .. DIR_DELIM .. "update_detector.lua")
|
||||
dofile(path .. DIR_DELIM .. "dlg_contentstore.lua")
|
||||
@@ -78,54 +78,63 @@ local function load_texture_packs(txtpath, retval)
|
||||
|
||||
for _, item in ipairs(list) do
|
||||
if item ~= "base" then
|
||||
local name = item
|
||||
|
||||
local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
|
||||
if path == current_texture_path then
|
||||
name = fgettext("$1 (Enabled)", name)
|
||||
end
|
||||
|
||||
local conf = Settings(path .. "texture_pack.conf")
|
||||
local enabled = path == current_texture_path
|
||||
|
||||
local title = conf:get("title") or item
|
||||
|
||||
-- list_* is only used if non-nil, else the regular versions are used.
|
||||
retval[#retval + 1] = {
|
||||
name = item,
|
||||
title = title,
|
||||
list_name = enabled and fgettext("$1 (Enabled)", item) or nil,
|
||||
list_title = enabled and fgettext("$1 (Enabled)", title) or nil,
|
||||
author = conf:get("author"),
|
||||
release = tonumber(conf:get("release")) or 0,
|
||||
list_name = name,
|
||||
type = "txp",
|
||||
path = path,
|
||||
enabled = path == current_texture_path,
|
||||
enabled = enabled,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function get_mods(path,retval,modpack)
|
||||
--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)
|
||||
local mods = core.get_dir_list(path, true)
|
||||
|
||||
for _, name in ipairs(mods) do
|
||||
if name:sub(1, 1) ~= "." then
|
||||
local prefix = path .. DIR_DELIM .. name
|
||||
local mod_path = path .. DIR_DELIM .. name
|
||||
local mod_virtual_path = virtual_path .. "/" .. name
|
||||
local toadd = {
|
||||
dir_name = name,
|
||||
parent_dir = path,
|
||||
}
|
||||
retval[#retval + 1] = toadd
|
||||
listing[#listing + 1] = toadd
|
||||
|
||||
-- Get config file
|
||||
local mod_conf
|
||||
local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf")
|
||||
local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf")
|
||||
if modpack_conf then
|
||||
toadd.is_modpack = true
|
||||
modpack_conf:close()
|
||||
|
||||
mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table()
|
||||
mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table()
|
||||
if mod_conf.name then
|
||||
name = mod_conf.name
|
||||
toadd.is_name_explicit = true
|
||||
end
|
||||
else
|
||||
mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table()
|
||||
mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table()
|
||||
if mod_conf.name then
|
||||
name = mod_conf.name
|
||||
toadd.is_name_explicit = true
|
||||
@@ -134,14 +143,16 @@ function get_mods(path,retval,modpack)
|
||||
|
||||
-- Read from config
|
||||
toadd.name = name
|
||||
toadd.title = mod_conf.title
|
||||
toadd.author = mod_conf.author
|
||||
toadd.release = tonumber(mod_conf.release) or 0
|
||||
toadd.path = prefix
|
||||
toadd.path = mod_path
|
||||
toadd.virtual_path = mod_virtual_path
|
||||
toadd.type = "mod"
|
||||
|
||||
-- Check modpack.txt
|
||||
-- Note: modpack.conf is already checked above
|
||||
local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
|
||||
local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
|
||||
if modpackfile then
|
||||
modpackfile:close()
|
||||
toadd.is_modpack = true
|
||||
@@ -153,15 +164,20 @@ function get_mods(path,retval,modpack)
|
||||
elseif toadd.is_modpack then
|
||||
toadd.type = "modpack"
|
||||
toadd.is_modpack = true
|
||||
get_mods(prefix, retval, name)
|
||||
pkgmgr.get_mods(mod_path, mod_virtual_path, listing, 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()
|
||||
@@ -174,12 +190,29 @@ function pkgmgr.get_texture_packs()
|
||||
end
|
||||
|
||||
table.sort(retval, function(a, b)
|
||||
return a.name > b.name
|
||||
return a.title:lower() < b.title:lower()
|
||||
end)
|
||||
|
||||
return retval
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.get_all()
|
||||
local result = {}
|
||||
|
||||
for _, mod in pairs(pkgmgr.global_mods:get_list()) do
|
||||
result[#result + 1] = mod
|
||||
end
|
||||
for _, game in pairs(pkgmgr.games) do
|
||||
result[#result + 1] = game
|
||||
end
|
||||
for _, txp in pairs(pkgmgr.get_texture_packs()) do
|
||||
result[#result + 1] = txp
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.get_folder_type(path)
|
||||
local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
|
||||
@@ -240,101 +273,15 @@ function pkgmgr.get_base_folder(temppath)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.isValidModname(modpath)
|
||||
if modpath:find("-") ~= nil then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
function pkgmgr.is_valid_modname(modpath)
|
||||
return modpath:match("[^a-z0-9_]") == nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
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)
|
||||
--- @param render_list filterlist
|
||||
--- @param use_technical_names boolean to show technical names instead of human-readable titles
|
||||
--- @param with_icon table or nil, from virtual path to icon object
|
||||
function pkgmgr.render_packagelist(render_list, use_technical_names, with_icon)
|
||||
if not render_list then
|
||||
if not pkgmgr.global_mods then
|
||||
pkgmgr.refresh_globals()
|
||||
@@ -346,31 +293,79 @@ function pkgmgr.render_packagelist(render_list)
|
||||
local retval = {}
|
||||
for i, v in ipairs(list) do
|
||||
local color = ""
|
||||
local icon = 0
|
||||
local icon_info = with_icon and with_icon[v.virtual_path or v.path]
|
||||
local function update_icon_info(val)
|
||||
if val and (not icon_info or (icon_info.type == "warning" and val.type == "error")) then
|
||||
icon_info = val
|
||||
end
|
||||
end
|
||||
|
||||
if v.is_modpack then
|
||||
local rawlist = render_list:get_raw_list()
|
||||
color = mt_color_dark_green
|
||||
|
||||
for j = 1, #rawlist, 1 do
|
||||
if rawlist[j].modpack == list[i].name and
|
||||
not rawlist[j].enabled then
|
||||
-- Modpack not entirely enabled so showing as grey
|
||||
color = mt_color_grey
|
||||
break
|
||||
for j = 1, #rawlist do
|
||||
if rawlist[j].modpack == list[i].name then
|
||||
if with_icon then
|
||||
update_icon_info(with_icon[rawlist[j].virtual_path or rawlist[j].path])
|
||||
end
|
||||
|
||||
if rawlist[j].enabled then
|
||||
icon = 1
|
||||
else
|
||||
-- Modpack not entirely enabled so showing as grey
|
||||
color = mt_color_grey
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif v.is_game_content or v.type == "game" then
|
||||
icon = 1
|
||||
color = mt_color_blue
|
||||
|
||||
local rawlist = render_list:get_raw_list()
|
||||
if v.type == "game" and with_icon then
|
||||
for j = 1, #rawlist do
|
||||
if rawlist[j].is_game_content then
|
||||
update_icon_info(with_icon[rawlist[j].virtual_path or rawlist[j].path])
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif v.enabled or v.type == "txp" then
|
||||
icon = 1
|
||||
color = mt_color_green
|
||||
end
|
||||
|
||||
if icon_info then
|
||||
if icon_info.type == "warning" then
|
||||
color = mt_color_orange
|
||||
icon = 2
|
||||
elseif icon_info.type == "error" then
|
||||
color = mt_color_red
|
||||
icon = 3
|
||||
elseif icon_info.type == "update" then
|
||||
icon = 4
|
||||
else
|
||||
error("Unknown icon type " .. icon_info.type)
|
||||
end
|
||||
end
|
||||
|
||||
retval[#retval + 1] = color
|
||||
if v.modpack ~= nil or v.loc == "game" then
|
||||
retval[#retval + 1] = "1"
|
||||
else
|
||||
retval[#retval + 1] = "0"
|
||||
end
|
||||
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
|
||||
|
||||
if with_icon then
|
||||
retval[#retval + 1] = icon
|
||||
end
|
||||
|
||||
if use_technical_names then
|
||||
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
|
||||
else
|
||||
retval[#retval + 1] = core.formspec_escape(v.list_title or v.list_name or v.title or v.name)
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(retval, ",")
|
||||
@@ -397,6 +392,14 @@ function pkgmgr.is_modpack_entirely_enabled(data, name)
|
||||
return true
|
||||
end
|
||||
|
||||
local function disable_all_by_name(list, name, except)
|
||||
for i=1, #list do
|
||||
if list[i].name == name and list[i] ~= except then
|
||||
list[i].enabled = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---------- toggles or en/disables a mod or modpack and its dependencies --------
|
||||
local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
|
||||
if not mod.is_modpack then
|
||||
@@ -405,13 +408,16 @@ local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mo
|
||||
toset = not mod.enabled
|
||||
end
|
||||
if mod.enabled ~= toset then
|
||||
mod.enabled = toset
|
||||
toggled_mods[#toggled_mods+1] = mod.name
|
||||
end
|
||||
if toset then
|
||||
-- Mark this mod for recursive dependency traversal
|
||||
enabled_mods[mod.name] = true
|
||||
|
||||
-- Disable other mods with the same name
|
||||
disable_all_by_name(list, mod.name, mod)
|
||||
end
|
||||
mod.enabled = toset
|
||||
else
|
||||
-- Toggle or en/disable every mod in the modpack,
|
||||
-- interleaved unsupported
|
||||
@@ -472,6 +478,7 @@ function pkgmgr.enable_mod(this, toset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If sp is 0, every dependency is already activated
|
||||
while sp > 0 do
|
||||
local name = to_enable[sp]
|
||||
@@ -483,8 +490,8 @@ function pkgmgr.enable_mod(this, toset)
|
||||
if not mod_to_enable then
|
||||
core.log("warning", "Mod dependency \"" .. name ..
|
||||
"\" not found!")
|
||||
else
|
||||
if not mod_to_enable.enabled then
|
||||
elseif not mod_to_enable.is_game_content then
|
||||
if not mod_to_enable.enabled then
|
||||
mod_to_enable.enabled = true
|
||||
toggled_mods[#toggled_mods+1] = mod_to_enable.name
|
||||
end
|
||||
@@ -538,100 +545,70 @@ function pkgmgr.get_worldconfig(worldpath)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.install_dir(type, path, basename, targetpath)
|
||||
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")
|
||||
|
||||
local basefolder = pkgmgr.get_base_folder(path)
|
||||
|
||||
-- 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 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.
|
||||
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)
|
||||
return nil, fgettext_ne("Unable to install a $1 as a texture pack", basefolder.type)
|
||||
end
|
||||
|
||||
local from = basefolder and basefolder.path or path
|
||||
if targetpath then
|
||||
core.delete_dir(targetpath)
|
||||
else
|
||||
if not targetpath then
|
||||
targetpath = core.get_texturepath() .. DIR_DELIM .. basename
|
||||
end
|
||||
core.delete_dir(targetpath)
|
||||
if not core.copy_dir(from, targetpath, false) then
|
||||
return nil,
|
||||
fgettext("Failed to install $1 to $2", basename, targetpath)
|
||||
fgettext_ne("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 or modpack")
|
||||
return nil, fgettext_ne("Unable to find a valid mod, modpack, or game")
|
||||
end
|
||||
|
||||
--
|
||||
-- Get destination
|
||||
--
|
||||
if basefolder.type == "modpack" then
|
||||
if type ~= "mod" then
|
||||
return nil, fgettext("Unable to install a modpack as a $1", type)
|
||||
end
|
||||
-- Check type
|
||||
if basefolder.type ~= expected_type and (basefolder.type ~= "modpack" or expected_type ~= "mod") then
|
||||
return nil, fgettext_ne("Unable to install a $1 as a $2", basefolder.type, expected_type)
|
||||
end
|
||||
|
||||
-- Get destination name for modpack
|
||||
if targetpath then
|
||||
core.delete_dir(targetpath)
|
||||
-- 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
|
||||
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)
|
||||
error("Unknown content type")
|
||||
end
|
||||
|
||||
if targetpath then
|
||||
core.delete_dir(targetpath)
|
||||
if basename and (basefolder.type ~= "mod" or pkgmgr.is_valid_modname(basename)) then
|
||||
targetpath = content_path .. DIR_DELIM .. basename
|
||||
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)
|
||||
else
|
||||
targetpath = core.get_gamepath() .. DIR_DELIM .. basename
|
||||
return nil,
|
||||
fgettext_ne("Install: Unable to find suitable folder name for $1", path)
|
||||
end
|
||||
end
|
||||
|
||||
-- Copy it
|
||||
core.delete_dir(targetpath)
|
||||
if not core.copy_dir(basefolder.path, targetpath, false) then
|
||||
return nil,
|
||||
fgettext("Failed to install $1 to $2", basename, targetpath)
|
||||
fgettext_ne("Failed to install $1 to $2", basename, targetpath)
|
||||
end
|
||||
|
||||
if basefolder.type == "game" then
|
||||
@@ -652,13 +629,14 @@ function pkgmgr.preparemodlist(data)
|
||||
|
||||
--read global mods
|
||||
local modpaths = core.get_modpaths()
|
||||
for _, modpath in ipairs(modpaths) do
|
||||
get_mods(modpath, global_mods)
|
||||
for key, modpath in pairs(modpaths) do
|
||||
pkgmgr.get_mods(modpath, key, global_mods)
|
||||
end
|
||||
|
||||
for i=1,#global_mods,1 do
|
||||
global_mods[i].type = "mod"
|
||||
global_mods[i].loc = "global"
|
||||
global_mods[i].enabled = false
|
||||
retval[#retval + 1] = global_mods[i]
|
||||
end
|
||||
|
||||
@@ -671,7 +649,7 @@ function pkgmgr.preparemodlist(data)
|
||||
retval[#retval + 1] = {
|
||||
type = "game",
|
||||
is_game_content = true,
|
||||
name = fgettext("$1 mods", gamespec.name),
|
||||
name = fgettext("$1 mods", gamespec.title),
|
||||
path = gamespec.path
|
||||
}
|
||||
end
|
||||
@@ -692,22 +670,37 @@ function pkgmgr.preparemodlist(data)
|
||||
DIR_DELIM .. "world.mt"
|
||||
|
||||
local worldfile = Settings(filename)
|
||||
|
||||
for key,value in pairs(worldfile:to_table()) do
|
||||
for key, value in pairs(worldfile:to_table()) do
|
||||
if key:sub(1, 9) == "load_mod_" then
|
||||
key = key:sub(10)
|
||||
local element = nil
|
||||
for i=1,#retval,1 do
|
||||
local mod_found = false
|
||||
|
||||
local fallback_found = false
|
||||
local fallback_mod = nil
|
||||
|
||||
for i=1, #retval do
|
||||
if retval[i].name == key and
|
||||
not retval[i].is_modpack then
|
||||
element = retval[i]
|
||||
break
|
||||
not retval[i].is_modpack then
|
||||
if core.is_yes(value) or retval[i].virtual_path == value then
|
||||
retval[i].enabled = true
|
||||
mod_found = true
|
||||
break
|
||||
elseif fallback_found then
|
||||
-- Only allow fallback if only one mod matches
|
||||
fallback_mod = nil
|
||||
else
|
||||
fallback_found = true
|
||||
fallback_mod = retval[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
if element ~= nil then
|
||||
element.enabled = value ~= "false" and value ~= "nil" and value
|
||||
else
|
||||
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
|
||||
|
||||
if not mod_found then
|
||||
if fallback_mod and value:find("/") then
|
||||
fallback_mod.enabled = true
|
||||
else
|
||||
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -744,35 +737,6 @@ 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
|
||||
@@ -788,9 +752,9 @@ end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.find_by_gameid(gameid)
|
||||
for i=1,#pkgmgr.games,1 do
|
||||
if pkgmgr.games[i].id == gameid then
|
||||
return pkgmgr.games[i], i
|
||||
for i, game in ipairs(pkgmgr.games) do
|
||||
if game.id == gameid then
|
||||
return game, i
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
@@ -801,51 +765,42 @@ function pkgmgr.get_game_mods(gamespec, retval)
|
||||
if gamespec ~= nil and
|
||||
gamespec.gamemods_path ~= nil and
|
||||
gamespec.gamemods_path ~= "" then
|
||||
get_mods(gamespec.gamemods_path, retval)
|
||||
pkgmgr.get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), 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..","
|
||||
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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Returns the ContentDB ID for an installed piece of content.
|
||||
function pkgmgr.get_contentdb_id(content)
|
||||
-- core.get_games() will return "" instead of nil if there is no "author" field.
|
||||
if content.author and content.author ~= "" and content.release > 0 then
|
||||
if content.type == "game" then
|
||||
return content.author:lower() .. "/" .. content.id
|
||||
end
|
||||
retval = retval .. game_mods[i].name
|
||||
return content.author:lower() .. "/" .. content.name
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.get_game(index)
|
||||
if index > 0 and index <= #pkgmgr.games then
|
||||
return pkgmgr.games[index]
|
||||
-- Until Minetest 5.8.0, Minetest Game was bundled with Minetest.
|
||||
-- Unfortunately, the bundled MTG was not versioned (missing "release"
|
||||
-- field in game.conf).
|
||||
-- Therefore, we consider any installation of MTG that is not versioned,
|
||||
-- has not been cloned from Git, and is not system-wide to be updatable.
|
||||
if content.type == "game" and content.id == "minetest" and content.release == 0 and
|
||||
not core.is_dir(content.path .. "/.git") and core.may_modify_path(content.path) then
|
||||
return "minetest/minetest"
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function pkgmgr.update_gamelist()
|
||||
pkgmgr.games = core.get_games()
|
||||
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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- read initial data
|
||||
--------------------------------------------------------------------------------
|
||||
243
builtin/mainmenu/content/tests/pkgmgr_spec.lua
Normal file
243
builtin/mainmenu/content/tests/pkgmgr_spec.lua
Normal file
@@ -0,0 +1,243 @@
|
||||
--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_ne(fmt, ...)
|
||||
return fmt
|
||||
end
|
||||
|
||||
setfenv(loadfile("builtin/common/misc_helpers.lua"), env)()
|
||||
setfenv(loadfile("builtin/mainmenu/content/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)
|
||||
139
builtin/mainmenu/content/update_detector.lua
Normal file
139
builtin/mainmenu/content/update_detector.lua
Normal file
@@ -0,0 +1,139 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2023 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.
|
||||
|
||||
|
||||
update_detector = {}
|
||||
|
||||
|
||||
if not core.get_http_api then
|
||||
update_detector.get_all = function() return {} end
|
||||
update_detector.get_count = function() return 0 end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local has_fetched = false
|
||||
local latest_releases
|
||||
|
||||
|
||||
local function fetch_latest_releases()
|
||||
local version = core.get_version()
|
||||
local base_url = core.settings:get("contentdb_url")
|
||||
local url = base_url ..
|
||||
"/api/updates/?type=mod&type=game&type=txp&protocol_version=" ..
|
||||
core.get_max_supp_proto() .. "&engine_version=" .. core.urlencode(version.string)
|
||||
local http = core.get_http_api()
|
||||
local response = http.fetch_sync({ url = url })
|
||||
if not response.succeeded then
|
||||
return
|
||||
end
|
||||
|
||||
return core.parse_json(response.data)
|
||||
end
|
||||
|
||||
|
||||
--- Get a table from package key (author/name) to latest release id
|
||||
---
|
||||
--- @param callback function that takes a single argument, table or nil
|
||||
local function get_latest_releases(callback)
|
||||
core.handle_async(fetch_latest_releases, nil, callback)
|
||||
end
|
||||
|
||||
|
||||
local function has_packages_from_cdb()
|
||||
pkgmgr.refresh_globals()
|
||||
pkgmgr.update_gamelist()
|
||||
|
||||
for _, content in pairs(pkgmgr.get_all()) do
|
||||
if pkgmgr.get_contentdb_id(content) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- @returns a new table with all keys lowercase
|
||||
local function lowercase_keys(tab)
|
||||
local ret = {}
|
||||
for key, value in pairs(tab) do
|
||||
ret[key:lower()] = value
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
|
||||
local function fetch()
|
||||
if has_fetched or not has_packages_from_cdb() then
|
||||
return
|
||||
end
|
||||
|
||||
has_fetched = true
|
||||
|
||||
get_latest_releases(function(releases)
|
||||
if not releases then
|
||||
has_fetched = false
|
||||
return
|
||||
end
|
||||
|
||||
latest_releases = lowercase_keys(releases)
|
||||
if update_detector.get_count() > 0 then
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
if not maintab.hidden then
|
||||
ui.update()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
--- @returns a list of content with an update available
|
||||
function update_detector.get_all()
|
||||
if latest_releases == nil then
|
||||
fetch()
|
||||
return {}
|
||||
end
|
||||
|
||||
pkgmgr.refresh_globals()
|
||||
pkgmgr.update_gamelist()
|
||||
|
||||
local ret = {}
|
||||
local all_content = pkgmgr.get_all()
|
||||
for _, content in ipairs(all_content) do
|
||||
local cdb_id = pkgmgr.get_contentdb_id(content)
|
||||
|
||||
if cdb_id then
|
||||
-- The backend will account for aliases in `latest_releases`
|
||||
local latest_release = latest_releases[cdb_id]
|
||||
if not latest_release and content.type == "game" then
|
||||
latest_release = latest_releases[cdb_id .. "_game"]
|
||||
end
|
||||
|
||||
if latest_release and latest_release > content.release then
|
||||
ret[#ret + 1] = content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
|
||||
--- @return number of packages with updates available
|
||||
function update_detector.get_count()
|
||||
return #update_detector.get_all()
|
||||
end
|
||||
@@ -57,8 +57,60 @@ local function init_data(data)
|
||||
hide_game = data.hide_gamemods,
|
||||
hide_modpackcontents = data.hide_modpackcontents
|
||||
})
|
||||
data.list:add_sort_mechanism("alphabetic", sort_mod_list)
|
||||
data.list:set_sortmode("alphabetic")
|
||||
-- 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
|
||||
end
|
||||
|
||||
local function get_formspec(data)
|
||||
@@ -66,7 +118,10 @@ local function get_formspec(data)
|
||||
init_data(data)
|
||||
end
|
||||
|
||||
local mod = data.list:get_list()[data.selected_mod] or {name = ""}
|
||||
local all_mods = data.list:get_list()
|
||||
local with_error, enabled_mods_by_name = check_mod_configuration(data.worldspec.path, all_mods)
|
||||
|
||||
local mod = all_mods[data.selected_mod] or {name = ""}
|
||||
|
||||
local retval =
|
||||
"size[11.5,7.5,true]" ..
|
||||
@@ -87,6 +142,29 @@ local function get_formspec(data)
|
||||
"textarea[0.25,0.7;5.75,7.2;;" .. info .. ";]"
|
||||
else
|
||||
local hard_deps, soft_deps = pkgmgr.get_dependencies(mod.path)
|
||||
|
||||
-- Add error messages to dep lists
|
||||
if mod.enabled or mod.is_game_content then
|
||||
for i, dep_name in ipairs(hard_deps) do
|
||||
local dep = enabled_mods_by_name[dep_name]
|
||||
if not dep then
|
||||
hard_deps[i] = mt_color_red .. dep_name .. " " .. fgettext("(Unsatisfied)")
|
||||
elseif with_error[dep.virtual_path] then
|
||||
hard_deps[i] = mt_color_orange .. dep_name .. " " .. fgettext("(Enabled, has error)")
|
||||
else
|
||||
hard_deps[i] = mt_color_green .. dep_name
|
||||
end
|
||||
end
|
||||
for i, dep_name in ipairs(soft_deps) do
|
||||
local dep = enabled_mods_by_name[dep_name]
|
||||
if dep and with_error[dep.virtual_path] then
|
||||
soft_deps[i] = mt_color_orange .. dep_name .. " " .. fgettext("(Enabled, has error)")
|
||||
elseif dep then
|
||||
soft_deps[i] = mt_color_green .. dep_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local hard_deps_str = table.concat(hard_deps, ",")
|
||||
local soft_deps_str = table.concat(soft_deps, ",")
|
||||
|
||||
@@ -138,7 +216,6 @@ local function get_formspec(data)
|
||||
|
||||
if mod.name ~= "" and not mod.is_game_content then
|
||||
if mod.is_modpack then
|
||||
|
||||
if pkgmgr.is_modpack_entirely_enabled(data, mod.name) then
|
||||
retval = retval ..
|
||||
"button[5.5,0.125;3,0.5;btn_mp_disable;" ..
|
||||
@@ -163,10 +240,16 @@ local function get_formspec(data)
|
||||
"button[8.95,0.125;2.5,0.5;btn_enable_all_mods;" ..
|
||||
fgettext("Enable all") .. "]"
|
||||
end
|
||||
|
||||
local use_technical_names = core.settings:get_bool("show_technical_names")
|
||||
|
||||
return retval ..
|
||||
"tablecolumns[color;tree;text]" ..
|
||||
"tablecolumns[color;tree;image,align=inline,width=1.5,0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") ..
|
||||
",1=" .. core.formspec_escape(defaulttexturedir .. "checkbox_16.png") ..
|
||||
",2=" .. core.formspec_escape(defaulttexturedir .. "error_icon_orange.png") ..
|
||||
",3=" .. core.formspec_escape(defaulttexturedir .. "error_icon_red.png") .. ";text]" ..
|
||||
"table[5.5,0.75;5.75,6;world_config_modlist;" ..
|
||||
pkgmgr.render_packagelist(data.list) .. ";" .. data.selected_mod .."]"
|
||||
pkgmgr.render_packagelist(data.list, use_technical_names, with_error) .. ";" .. data.selected_mod .."]"
|
||||
end
|
||||
|
||||
local function handle_buttons(this, fields)
|
||||
@@ -205,14 +288,19 @@ local function handle_buttons(this, fields)
|
||||
local mods = worldfile:to_table()
|
||||
|
||||
local rawlist = this.data.list:get_raw_list()
|
||||
local was_set = {}
|
||||
|
||||
for i = 1, #rawlist do
|
||||
local mod = rawlist[i]
|
||||
if not mod.is_modpack and
|
||||
not mod.is_game_content then
|
||||
if modname_valid(mod.name) then
|
||||
worldfile:set("load_mod_" .. mod.name,
|
||||
mod.enabled and "true" or "false")
|
||||
if mod.enabled then
|
||||
worldfile:set("load_mod_" .. mod.name, mod.virtual_path)
|
||||
was_set[mod.name] = true
|
||||
elseif not was_set[mod.name] then
|
||||
worldfile:set("load_mod_" .. mod.name, "false")
|
||||
end
|
||||
elseif mod.enabled then
|
||||
gamedata.errormessage = fgettext_ne("Failed to enable mo" ..
|
||||
"d \"$1\" as it contains disallowed characters. " ..
|
||||
@@ -256,12 +344,26 @@ local function handle_buttons(this, fields)
|
||||
if fields.btn_enable_all_mods then
|
||||
local list = this.data.list:get_raw_list()
|
||||
|
||||
-- When multiple copies of a mod are installed, we need to avoid enabling multiple of them
|
||||
-- at a time. So lets first collect all the enabled mods, and then use this to exclude
|
||||
-- multiple enables.
|
||||
|
||||
local was_enabled = {}
|
||||
for i = 1, #list do
|
||||
if not list[i].is_game_content
|
||||
and not list[i].is_modpack then
|
||||
list[i].enabled = true
|
||||
and not list[i].is_modpack and list[i].enabled then
|
||||
was_enabled[list[i].name] = true
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #list do
|
||||
if not list[i].is_game_content and not list[i].is_modpack and
|
||||
not was_enabled[list[i].name] then
|
||||
list[i].enabled = true
|
||||
was_enabled[list[i].name] = true
|
||||
end
|
||||
end
|
||||
|
||||
enabled_all = true
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
-- cf. tab_local, the gamebar already provides game selection so we hide the list from here
|
||||
local hide_gamelist = PLATFORM ~= "Android"
|
||||
|
||||
local function table_to_flags(ftable)
|
||||
-- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"
|
||||
local str = {}
|
||||
@@ -94,31 +91,16 @@ local mgv6_biomes = {
|
||||
|
||||
local function create_world_formspec(dialogdata)
|
||||
|
||||
-- Error out when no games found
|
||||
if #pkgmgr.games == 0 then
|
||||
return "size[12.25,3,true]" ..
|
||||
"box[0,0;12,2;" .. mt_color_orange .. "]" ..
|
||||
"textarea[0.3,0;11.7,2;;;"..
|
||||
fgettext("You have no games installed.") .. "\n" ..
|
||||
fgettext("Download one from minetest.net") .. "]" ..
|
||||
"button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
|
||||
end
|
||||
|
||||
local current_mg = dialogdata.mg
|
||||
local mapgens = core.get_mapgen_names()
|
||||
|
||||
local gameid = core.settings:get("menu_last_game")
|
||||
|
||||
local flags = dialogdata.flags
|
||||
|
||||
local game, gameidx = pkgmgr.find_by_gameid(gameid)
|
||||
if game == nil and hide_gamelist then
|
||||
local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
|
||||
if game == nil then
|
||||
-- should never happen but just pick the first game
|
||||
game = pkgmgr.get_game(1)
|
||||
gameidx = 1
|
||||
game = pkgmgr.games[1]
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
elseif game == nil then
|
||||
gameidx = 0
|
||||
end
|
||||
|
||||
local disallowed_mapgen_settings = {}
|
||||
@@ -296,17 +278,6 @@ local function create_world_formspec(dialogdata)
|
||||
label_spflags = "label[0,"..y_start..";" .. fgettext("Mapgen-specific flags") .. "]"
|
||||
end
|
||||
|
||||
-- Warning if only devtest is installed
|
||||
local devtest_only = ""
|
||||
local gamelist_height = 2.3
|
||||
if #pkgmgr.games == 1 and pkgmgr.games[1].id == "devtest" then
|
||||
devtest_only = "box[0,0;5.8,1.7;#ff8800]" ..
|
||||
"textarea[0.3,0;6,1.8;;;"..
|
||||
fgettext("Warning: The Development Test is meant for developers.") .. "\n" ..
|
||||
fgettext("Download a game, such as Minetest Game, from minetest.net") .. "]"
|
||||
gamelist_height = 0.5
|
||||
end
|
||||
|
||||
local retval =
|
||||
"size[12.25,7,true]" ..
|
||||
|
||||
@@ -315,22 +286,28 @@ local function create_world_formspec(dialogdata)
|
||||
"field[0.3,0.6;6,0.5;te_world_name;" ..
|
||||
fgettext("World name") ..
|
||||
";" .. core.formspec_escape(dialogdata.worldname) .. "]" ..
|
||||
"set_focus[te_world_name;false]" ..
|
||||
"set_focus[te_world_name;false]"
|
||||
|
||||
"field[0.3,1.7;6,0.5;te_seed;" ..
|
||||
fgettext("Seed") ..
|
||||
";".. core.formspec_escape(dialogdata.seed) .. "]" ..
|
||||
if not disallowed_mapgen_settings["seed"] then
|
||||
|
||||
retval = retval .. "field[0.3,1.7;6,0.5;te_seed;" ..
|
||||
fgettext("Seed") ..
|
||||
";".. core.formspec_escape(dialogdata.seed) .. "]"
|
||||
|
||||
end
|
||||
|
||||
retval = retval ..
|
||||
"label[0,2;" .. fgettext("Mapgen") .. "]"..
|
||||
"dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]"
|
||||
|
||||
if not hide_gamelist or devtest_only ~= "" then
|
||||
-- Warning when making a devtest world
|
||||
if game.id == "devtest" then
|
||||
retval = retval ..
|
||||
"label[0,3.35;" .. fgettext("Game") .. "]"..
|
||||
"textlist[0,3.85;5.8,"..gamelist_height..";games;" ..
|
||||
pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" ..
|
||||
"container[0,4.5]" ..
|
||||
devtest_only ..
|
||||
"container[0,3.5]" ..
|
||||
"box[0,0;5.8,1.7;#ff8800]" ..
|
||||
"textarea[0.4,0.1;6,1.8;;;"..
|
||||
fgettext("Development Test is meant for developers.") .. "]" ..
|
||||
"button[1,1;4,0.5;world_create_open_cdb;" .. fgettext("Install another game") .. "]" ..
|
||||
"container_end[]"
|
||||
end
|
||||
|
||||
@@ -353,21 +330,30 @@ end
|
||||
|
||||
local function create_world_buttonhandler(this, fields)
|
||||
|
||||
if fields["world_create_open_cdb"] then
|
||||
local dlg = create_store_dlg("game")
|
||||
dlg:set_parent(this.parent)
|
||||
this:delete()
|
||||
this.parent:hide()
|
||||
dlg:show()
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["world_create_confirm"] or
|
||||
fields["key_enter"] then
|
||||
|
||||
local worldname = fields["te_world_name"]
|
||||
local game, gameindex
|
||||
if hide_gamelist then
|
||||
game, gameindex = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
|
||||
else
|
||||
gameindex = core.get_textlist_index("games")
|
||||
game = pkgmgr.get_game(gameindex)
|
||||
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 message
|
||||
if game == nil then
|
||||
message = fgettext("No game selected")
|
||||
message = fgettext_ne("No game selected")
|
||||
end
|
||||
|
||||
if message == nil then
|
||||
@@ -386,12 +372,12 @@ local function create_world_buttonhandler(this, fields)
|
||||
end
|
||||
|
||||
if menudata.worldlist:uid_exists_raw(worldname) then
|
||||
message = fgettext("A world named \"$1\" already exists", worldname)
|
||||
message = fgettext_ne("A world named \"$1\" already exists", worldname)
|
||||
end
|
||||
end
|
||||
|
||||
if message == nil then
|
||||
this.data.seed = fields["te_seed"]
|
||||
this.data.seed = fields["te_seed"] or ""
|
||||
this.data.mg = fields["dd_mapgen"]
|
||||
|
||||
-- actual names as used by engine
|
||||
@@ -407,14 +393,12 @@ local function create_world_buttonhandler(this, fields)
|
||||
mgvalleys_spflags = table_to_flags(this.data.flags.valleys),
|
||||
mgflat_spflags = table_to_flags(this.data.flags.flat),
|
||||
}
|
||||
message = core.create_world(worldname, gameindex, settings)
|
||||
message = core.create_world(worldname, game.id, settings)
|
||||
end
|
||||
|
||||
if message == nil then
|
||||
core.settings:set("menu_last_game", game.id)
|
||||
if this.data.update_worldlist_filter then
|
||||
menudata.worldlist:set_filtercriteria(game.id)
|
||||
end
|
||||
menudata.worldlist:set_filtercriteria(game.id)
|
||||
menudata.worldlist:refresh()
|
||||
core.settings:set("mainmenu_last_selected_world",
|
||||
menudata.worldlist:raw_index_by_uid(worldname))
|
||||
@@ -426,7 +410,7 @@ local function create_world_buttonhandler(this, fields)
|
||||
end
|
||||
|
||||
this.data.worldname = fields["te_world_name"]
|
||||
this.data.seed = fields["te_seed"]
|
||||
this.data.seed = fields["te_seed"] or ""
|
||||
|
||||
if fields["games"] then
|
||||
local gameindex = core.get_textlist_index("games")
|
||||
@@ -472,13 +456,12 @@ local function create_world_buttonhandler(this, fields)
|
||||
end
|
||||
|
||||
|
||||
function create_create_world_dlg(update_worldlistfilter)
|
||||
function create_create_world_dlg()
|
||||
local retval = dialog_create("sp_create_world",
|
||||
create_world_formspec,
|
||||
create_world_buttonhandler,
|
||||
nil)
|
||||
retval.data = {
|
||||
update_worldlist_filter = update_worldlistfilter,
|
||||
worldname = "",
|
||||
-- settings the world is created with:
|
||||
seed = core.settings:get("fixed_map_seed") or "",
|
||||
|
||||
@@ -18,15 +18,10 @@
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function delete_content_formspec(dialogdata)
|
||||
local retval =
|
||||
"size[11.5,4.5,true]" ..
|
||||
"label[2,2;" ..
|
||||
fgettext("Are you sure you want to delete \"$1\"?", dialogdata.content.name) .. "]"..
|
||||
"style[dlg_delete_content_confirm;bgcolor=red]" ..
|
||||
"button[3.25,3.5;2.5,0.5;dlg_delete_content_confirm;" .. fgettext("Delete") .. "]" ..
|
||||
"button[5.75,3.5;2.5,0.5;dlg_delete_content_cancel;" .. fgettext("Cancel") .. "]"
|
||||
|
||||
return retval
|
||||
return confirmation_formspec(
|
||||
fgettext("Are you sure you want to delete \"$1\"?", dialogdata.content.name),
|
||||
'dlg_delete_content_confirm', fgettext("Delete"),
|
||||
'dlg_delete_content_cancel', fgettext("Cancel"))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -39,7 +34,7 @@ local function delete_content_buttonhandler(this, fields)
|
||||
this.data.content.path ~= core.get_gamepath() and
|
||||
this.data.content.path ~= core.get_texturepath() then
|
||||
if not core.delete_dir(this.data.content.path) then
|
||||
gamedata.errormessage = fgettext("pkgmgr: failed to delete \"$1\"", this.data.content.path)
|
||||
gamedata.errormessage = fgettext_ne("pkgmgr: failed to delete \"$1\"", this.data.content.path)
|
||||
end
|
||||
|
||||
if this.data.content.type == "game" then
|
||||
@@ -48,7 +43,7 @@ local function delete_content_buttonhandler(this, fields)
|
||||
pkgmgr.refresh_globals()
|
||||
end
|
||||
else
|
||||
gamedata.errormessage = fgettext("pkgmgr: invalid path \"$1\"", this.data.content.path)
|
||||
gamedata.errormessage = fgettext_ne("pkgmgr: invalid path \"$1\"", this.data.content.path)
|
||||
end
|
||||
this:delete()
|
||||
return true
|
||||
|
||||
@@ -17,14 +17,10 @@
|
||||
|
||||
|
||||
local function delete_world_formspec(dialogdata)
|
||||
local retval =
|
||||
"size[10,2.5,true]" ..
|
||||
"label[0.5,0.5;" ..
|
||||
fgettext("Delete World \"$1\"?", dialogdata.delete_name) .. "]" ..
|
||||
"style[world_delete_confirm;bgcolor=red]" ..
|
||||
"button[0.5,1.5;2.5,0.5;world_delete_confirm;" .. fgettext("Delete") .. "]" ..
|
||||
"button[7.0,1.5;2.5,0.5;world_delete_cancel;" .. fgettext("Cancel") .. "]"
|
||||
return retval
|
||||
return confirmation_formspec(
|
||||
fgettext("Delete World \"$1\"?", dialogdata.delete_name),
|
||||
'world_delete_confirm', fgettext("Delete"),
|
||||
'world_delete_cancel', fgettext("Cancel"))
|
||||
end
|
||||
|
||||
local function delete_world_buttonhandler(this, fields)
|
||||
|
||||
123
builtin/mainmenu/dlg_register.lua
Normal file
123
builtin/mainmenu/dlg_register.lua
Normal file
@@ -0,0 +1,123 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2022 rubenwardy
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function register_formspec(dialogdata)
|
||||
local title = fgettext("Joining $1", dialogdata.server and dialogdata.server.name or dialogdata.address)
|
||||
local buttons_y = 4 + 1.3
|
||||
if dialogdata.error then
|
||||
buttons_y = buttons_y + 0.8
|
||||
end
|
||||
|
||||
local retval = {
|
||||
"formspec_version[4]",
|
||||
"size[8,", tostring(buttons_y + 1.175), "]",
|
||||
"set_focus[", (dialogdata.name ~= "" and "password" or "name"), "]",
|
||||
"label[0.375,0.8;", title, "]",
|
||||
"field[0.375,1.575;7.25,0.8;name;", core.formspec_escape(fgettext("Name")), ";",
|
||||
core.formspec_escape(dialogdata.name), "]",
|
||||
"pwdfield[0.375,2.875;7.25,0.8;password;", core.formspec_escape(fgettext("Password")), "]",
|
||||
"pwdfield[0.375,4.175;7.25,0.8;password_2;", core.formspec_escape(fgettext("Confirm Password")), "]"
|
||||
}
|
||||
|
||||
if dialogdata.error then
|
||||
table.insert_all(retval, {
|
||||
"box[0.375,", tostring(buttons_y - 0.9), ";7.25,0.6;darkred]",
|
||||
"label[0.625,", tostring(buttons_y - 0.6), ";", core.formspec_escape(dialogdata.error), "]",
|
||||
})
|
||||
end
|
||||
|
||||
table.insert_all(retval, {
|
||||
"container[0.375,", tostring(buttons_y), "]",
|
||||
"button[0,0;2.5,0.8;dlg_register_confirm;", fgettext("Register"), "]",
|
||||
"button[4.75,0;2.5,0.8;dlg_register_cancel;", fgettext("Cancel"), "]",
|
||||
"container_end[]",
|
||||
})
|
||||
|
||||
return table.concat(retval, "")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
local function register_buttonhandler(this, fields)
|
||||
this.data.name = fields.name
|
||||
this.data.error = nil
|
||||
|
||||
if fields.dlg_register_confirm or fields.key_enter then
|
||||
if fields.name == "" then
|
||||
this.data.error = fgettext("Missing name")
|
||||
return true
|
||||
end
|
||||
if fields.password ~= fields.password_2 then
|
||||
this.data.error = fgettext("Passwords do not match")
|
||||
return true
|
||||
end
|
||||
|
||||
gamedata.playername = fields.name
|
||||
gamedata.password = fields.password
|
||||
gamedata.address = this.data.address
|
||||
gamedata.port = this.data.port
|
||||
gamedata.allow_login_or_register = "register"
|
||||
gamedata.selected_world = 0
|
||||
|
||||
assert(gamedata.address and gamedata.port)
|
||||
|
||||
local server = this.data.server
|
||||
if server then
|
||||
serverlistmgr.add_favorite(server)
|
||||
gamedata.servername = server.name
|
||||
gamedata.serverdescription = server.description
|
||||
else
|
||||
gamedata.servername = ""
|
||||
gamedata.serverdescription = ""
|
||||
|
||||
serverlistmgr.add_favorite({
|
||||
address = gamedata.address,
|
||||
port = gamedata.port,
|
||||
})
|
||||
end
|
||||
|
||||
core.settings:set("name", fields.name)
|
||||
core.settings:set("address", gamedata.address)
|
||||
core.settings:set("remote_port", gamedata.port)
|
||||
|
||||
core.start()
|
||||
end
|
||||
|
||||
if fields["dlg_register_cancel"] then
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function create_register_dialog(address, port, server)
|
||||
assert(address)
|
||||
assert(type(port) == "number")
|
||||
|
||||
local retval = dialog_create("dlg_register",
|
||||
register_formspec,
|
||||
register_buttonhandler,
|
||||
nil)
|
||||
retval.data.address = address
|
||||
retval.data.port = port
|
||||
retval.data.server = server
|
||||
retval.data.name = core.settings:get("name") or ""
|
||||
return retval
|
||||
end
|
||||
115
builtin/mainmenu/dlg_reinstall_mtg.lua
Normal file
115
builtin/mainmenu/dlg_reinstall_mtg.lua
Normal file
@@ -0,0 +1,115 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2023 Gregor Parzefall
|
||||
--
|
||||
--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.
|
||||
|
||||
function check_reinstall_mtg()
|
||||
if core.settings:get_bool("no_mtg_notification") then
|
||||
return
|
||||
end
|
||||
|
||||
local games = core.get_games()
|
||||
for _, game in ipairs(games) do
|
||||
if game.id == "minetest" then
|
||||
core.settings:set_bool("no_mtg_notification", true)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local mtg_world_found = false
|
||||
local worlds = core.get_worlds()
|
||||
for _, world in ipairs(worlds) do
|
||||
if world.gameid == "minetest" then
|
||||
mtg_world_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not mtg_world_found then
|
||||
core.settings:set_bool("no_mtg_notification", true)
|
||||
return
|
||||
end
|
||||
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
|
||||
local dlg = create_reinstall_mtg_dlg()
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:show()
|
||||
ui.update()
|
||||
end
|
||||
|
||||
local function get_formspec(dialogdata)
|
||||
local markup = table.concat({
|
||||
"<big>", fgettext("Minetest Game is no longer installed by default"), "</big>\n",
|
||||
fgettext("For a long time, the Minetest engine shipped with a default game called \"Minetest Game\". " ..
|
||||
"Since Minetest 5.8.0, Minetest ships without a default game."), "\n",
|
||||
fgettext("If you want to continue playing in your Minetest Game worlds, you need to reinstall Minetest Game."),
|
||||
})
|
||||
|
||||
return table.concat({
|
||||
"formspec_version[6]",
|
||||
"size[12.8,7]",
|
||||
"hypertext[0.375,0.375;12.05,5.2;text;", minetest.formspec_escape(markup), "]",
|
||||
"container[0.375,5.825]",
|
||||
"style[dismiss;bgcolor=red]",
|
||||
"button[0,0;4,0.8;dismiss;", fgettext("Dismiss"), "]",
|
||||
"button[4.25,0;8,0.8;reinstall;", fgettext("Reinstall Minetest Game"), "]",
|
||||
"container_end[]",
|
||||
})
|
||||
end
|
||||
|
||||
local function buttonhandler(this, fields)
|
||||
if fields.reinstall then
|
||||
-- Don't set "no_mtg_notification" here so that the dialog will be shown
|
||||
-- again if downloading MTG fails for whatever reason.
|
||||
this:delete()
|
||||
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
|
||||
local dlg = create_store_dlg(nil, "minetest/minetest")
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:show()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.dismiss then
|
||||
core.settings:set_bool("no_mtg_notification", true)
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function eventhandler(event)
|
||||
if event == "DialogShow" then
|
||||
mm_game_theme.set_engine()
|
||||
return true
|
||||
elseif event == "MenuQuit" then
|
||||
-- Don't allow closing the dialog with ESC, but still allow exiting
|
||||
-- Minetest.
|
||||
core.close()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function create_reinstall_mtg_dlg()
|
||||
local dlg = dialog_create("dlg_reinstall_mtg", get_formspec,
|
||||
buttonhandler, eventhandler)
|
||||
return dlg
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
180
builtin/mainmenu/dlg_version_info.lua
Normal file
180
builtin/mainmenu/dlg_version_info.lua
Normal file
@@ -0,0 +1,180 @@
|
||||
--[[
|
||||
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 version_info_eventhandler(event)
|
||||
if event == "DialogShow" then
|
||||
mm_game_theme.set_engine()
|
||||
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,
|
||||
version_info_eventhandler)
|
||||
|
||||
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
|
||||
@@ -20,10 +20,6 @@ mm_game_theme = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.init()
|
||||
mm_game_theme.defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
||||
DIR_DELIM .. "pack" .. DIR_DELIM
|
||||
mm_game_theme.basetexturedir = mm_game_theme.defaulttexturedir
|
||||
|
||||
mm_game_theme.texturepack = core.settings:get("texture_path")
|
||||
|
||||
mm_game_theme.gameid = nil
|
||||
@@ -32,35 +28,27 @@ function mm_game_theme.init()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.update(tab,gamedetails)
|
||||
if tab ~= "singleplayer" then
|
||||
mm_game_theme.reset()
|
||||
return
|
||||
end
|
||||
|
||||
if gamedetails == nil then
|
||||
return
|
||||
end
|
||||
|
||||
mm_game_theme.update_game(gamedetails)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.reset()
|
||||
function mm_game_theme.set_engine(hide_decorations)
|
||||
mm_game_theme.gameid = nil
|
||||
mm_game_theme.stop_music()
|
||||
|
||||
core.set_topleft_text("")
|
||||
|
||||
local have_bg = false
|
||||
local have_overlay = mm_game_theme.set_generic("overlay")
|
||||
local have_overlay = mm_game_theme.set_engine_single("overlay")
|
||||
|
||||
if not have_overlay then
|
||||
have_bg = mm_game_theme.set_generic("background")
|
||||
have_bg = mm_game_theme.set_engine_single("background")
|
||||
end
|
||||
|
||||
mm_game_theme.clear("header")
|
||||
mm_game_theme.clear("footer")
|
||||
mm_game_theme.clear_single("header")
|
||||
mm_game_theme.clear_single("footer")
|
||||
core.set_clouds(false)
|
||||
|
||||
mm_game_theme.set_generic("footer")
|
||||
mm_game_theme.set_generic("header")
|
||||
if not hide_decorations then
|
||||
mm_game_theme.set_engine_single("header")
|
||||
mm_game_theme.set_engine_single("footer")
|
||||
end
|
||||
|
||||
if not have_bg then
|
||||
if core.settings:get_bool("menu_clouds") then
|
||||
@@ -69,51 +57,50 @@ function mm_game_theme.reset()
|
||||
mm_game_theme.set_dirt_bg()
|
||||
end
|
||||
end
|
||||
|
||||
if mm_game_theme.music_handle ~= nil then
|
||||
core.sound_stop(mm_game_theme.music_handle)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.update_game(gamedetails)
|
||||
function mm_game_theme.set_game(gamedetails)
|
||||
assert(gamedetails ~= nil)
|
||||
|
||||
if mm_game_theme.gameid == gamedetails.id then
|
||||
return
|
||||
end
|
||||
mm_game_theme.gameid = gamedetails.id
|
||||
mm_game_theme.set_music(gamedetails)
|
||||
|
||||
core.set_topleft_text(gamedetails.name)
|
||||
|
||||
local have_bg = false
|
||||
local have_overlay = mm_game_theme.set_game("overlay",gamedetails)
|
||||
local have_overlay = mm_game_theme.set_game_single("overlay", gamedetails)
|
||||
|
||||
if not have_overlay then
|
||||
have_bg = mm_game_theme.set_game("background",gamedetails)
|
||||
have_bg = mm_game_theme.set_game_single("background", gamedetails)
|
||||
end
|
||||
|
||||
mm_game_theme.clear("header")
|
||||
mm_game_theme.clear("footer")
|
||||
mm_game_theme.clear_single("header")
|
||||
mm_game_theme.clear_single("footer")
|
||||
core.set_clouds(false)
|
||||
|
||||
if not have_bg then
|
||||
mm_game_theme.set_game_single("header", gamedetails)
|
||||
mm_game_theme.set_game_single("footer", gamedetails)
|
||||
|
||||
if not have_bg then
|
||||
if core.settings:get_bool("menu_clouds") then
|
||||
core.set_clouds(true)
|
||||
else
|
||||
mm_game_theme.set_dirt_bg()
|
||||
end
|
||||
end
|
||||
|
||||
mm_game_theme.set_game("footer",gamedetails)
|
||||
mm_game_theme.set_game("header",gamedetails)
|
||||
|
||||
mm_game_theme.gameid = gamedetails.id
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.clear(identifier)
|
||||
function mm_game_theme.clear_single(identifier)
|
||||
core.set_background(identifier,"")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.set_generic(identifier)
|
||||
function mm_game_theme.set_engine_single(identifier)
|
||||
--try texture pack first
|
||||
if mm_game_theme.texturepack ~= nil then
|
||||
local path = mm_game_theme.texturepack .. DIR_DELIM .."menu_" ..
|
||||
@@ -123,25 +110,17 @@ function mm_game_theme.set_generic(identifier)
|
||||
end
|
||||
end
|
||||
|
||||
if mm_game_theme.defaulttexturedir ~= nil then
|
||||
local path = mm_game_theme.defaulttexturedir .. DIR_DELIM .."menu_" ..
|
||||
identifier .. ".png"
|
||||
if core.set_background(identifier,path) then
|
||||
return true
|
||||
end
|
||||
local path = defaulttexturedir .. DIR_DELIM .. "menu_" .. identifier .. ".png"
|
||||
if core.set_background(identifier, path) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.set_game(identifier, gamedetails)
|
||||
|
||||
if gamedetails == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
mm_game_theme.set_music(gamedetails)
|
||||
function mm_game_theme.set_game_single(identifier, gamedetails)
|
||||
assert(gamedetails ~= nil)
|
||||
|
||||
if mm_game_theme.texturepack ~= nil then
|
||||
local path = mm_game_theme.texturepack .. DIR_DELIM ..
|
||||
@@ -194,10 +173,18 @@ function mm_game_theme.set_dirt_bg()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.set_music(gamedetails)
|
||||
function mm_game_theme.stop_music()
|
||||
if mm_game_theme.music_handle ~= nil then
|
||||
core.sound_stop(mm_game_theme.music_handle)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function mm_game_theme.set_music(gamedetails)
|
||||
mm_game_theme.stop_music()
|
||||
|
||||
assert(gamedetails ~= nil)
|
||||
|
||||
local music_path = gamedetails.path .. DIR_DELIM .. "menu" .. DIR_DELIM .. "theme"
|
||||
mm_game_theme.music_handle = core.sound_play(music_path, true)
|
||||
end
|
||||
|
||||
@@ -17,15 +17,19 @@
|
||||
|
||||
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()
|
||||
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
||||
DIR_DELIM .. "pack" .. DIR_DELIM
|
||||
|
||||
dofile(menupath .. DIR_DELIM .. "misc.lua")
|
||||
|
||||
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
|
||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
|
||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
|
||||
@@ -33,25 +37,26 @@ dofile(basepath .. "fstk" .. DIR_DELIM .. "tabview.lua")
|
||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "ui.lua")
|
||||
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 .. "content" .. DIR_DELIM .. "init.lua")
|
||||
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_contentstore.lua")
|
||||
dofile(menupath .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.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")
|
||||
dofile(menupath .. DIR_DELIM .. "dlg_reinstall_mtg.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.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua")
|
||||
tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
|
||||
local tabs = {
|
||||
content = dofile(menupath .. DIR_DELIM .. "tab_content.lua"),
|
||||
about = dofile(menupath .. DIR_DELIM .. "tab_about.lua"),
|
||||
local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua"),
|
||||
play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
local function main_event_handler(tabview, event)
|
||||
@@ -82,22 +87,16 @@ local function init_globals()
|
||||
menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic)
|
||||
menudata.worldlist:set_sortmode("alphabetic")
|
||||
|
||||
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_game_theme.set_engine() -- This is just a fallback.
|
||||
|
||||
-- Create main tabview
|
||||
local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0})
|
||||
local tv_main = tabview_create("maintab", {x = 15.5, y = 7.1}, {x = 0, y = 0})
|
||||
|
||||
tv_main:set_autosave_tab(true)
|
||||
tv_main:add(tabs.local_game)
|
||||
tv_main:add(tabs.play_online)
|
||||
|
||||
tv_main:add(tabs.content)
|
||||
tv_main:add(tabs.settings)
|
||||
tv_main:add(tabs.about)
|
||||
|
||||
tv_main:set_global_event_handler(main_event_handler)
|
||||
@@ -108,19 +107,25 @@ local function init_globals()
|
||||
tv_main:set_tab(last_tab)
|
||||
end
|
||||
|
||||
-- In case the folder of the last selected game has been deleted,
|
||||
-- display "Minetest" as a header
|
||||
if tv_main.current_tab == "local" then
|
||||
local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
|
||||
if game == nil then
|
||||
mm_game_theme.reset()
|
||||
end
|
||||
end
|
||||
tv_main:set_end_button({
|
||||
icon = defaulttexturedir .. "settings_btn.png",
|
||||
label = fgettext("Settings"),
|
||||
name = "open_settings",
|
||||
on_click = function(tabview)
|
||||
local dlg = create_settings_dlg()
|
||||
dlg:set_parent(tabview)
|
||||
tabview:hide()
|
||||
dlg:show()
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
||||
ui.set_default("maintab")
|
||||
tv_main:show()
|
||||
|
||||
ui.update()
|
||||
|
||||
check_reinstall_mtg()
|
||||
check_new_version()
|
||||
end
|
||||
|
||||
init_globals()
|
||||
|
||||
6
builtin/mainmenu/misc.lua
Normal file
6
builtin/mainmenu/misc.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
-- old non-method sound function
|
||||
|
||||
function core.sound_stop(handle, ...)
|
||||
return handle:stop(...)
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user