Compare commits
1426 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d9f1771c | ||
|
|
96fbc52071 | ||
|
|
7556bf43a6 | ||
|
|
722c0044b1 | ||
|
|
b93cce11d8 | ||
|
|
6bdd5c6773 | ||
|
|
a8e7e261b9 | ||
|
|
73c29e7763 | ||
|
|
e27757fa3a | ||
|
|
4e3b8db2b9 | ||
|
|
336f8d4dc7 | ||
|
|
7737906d2e | ||
|
|
a6f1242a11 | ||
|
|
53c2fbb4c4 | ||
|
|
1d042a977d | ||
|
|
166782040e | ||
|
|
9e198fc22c | ||
|
|
ecf535ee83 | ||
|
|
f79a51c265 | ||
|
|
bd25a06f44 | ||
|
|
1caf26f18b | ||
|
|
5861192752 | ||
|
|
02db521c80 | ||
|
|
ba65eb953c | ||
|
|
dbf2d26a85 | ||
|
|
60694cbd30 | ||
|
|
baafec9e9f | ||
|
|
65d4335922 | ||
|
|
5094838837 | ||
|
|
93ac2ed894 | ||
|
|
c011fa0188 | ||
|
|
c2057eee4b | ||
|
|
835dd01fa1 | ||
|
|
20afc762cc | ||
|
|
dfb23c8db0 | ||
|
|
78e94b299d | ||
|
|
b52f7c76cd | ||
|
|
2877e8e624 | ||
|
|
0f166aa7ec | ||
|
|
a760faa3fa | ||
|
|
c0fd23e688 | ||
|
|
e558e44af4 | ||
|
|
0dcf3c57c7 | ||
|
|
6c19e68d6b | ||
|
|
5142f32878 | ||
|
|
aeda08c109 | ||
|
|
3d556da8b8 | ||
|
|
aa11c1a278 | ||
|
|
1cd9591997 | ||
|
|
4208022d55 | ||
|
|
4a4730be9b | ||
|
|
16c4ba599e | ||
|
|
b66aa9a954 | ||
|
|
ebaf3c8d77 | ||
|
|
62a8c5ca32 | ||
|
|
f5a53647f9 | ||
|
|
8ef2c42150 | ||
|
|
10fd41b4a8 | ||
|
|
95a0cc8f9a | ||
|
|
c489cef875 | ||
|
|
8bff2f23c6 | ||
|
|
d566b0e280 | ||
|
|
26deb26f17 | ||
|
|
2ba1d60ba5 | ||
|
|
30dcd41d91 | ||
|
|
90fccc15eb | ||
|
|
ac284e61b4 | ||
|
|
7625f88a0c | ||
|
|
4e1661eded | ||
|
|
60f8c02e18 | ||
|
|
768fd4adee | ||
|
|
682b789dd5 | ||
|
|
02f40e5b0d | ||
|
|
a7a719261e | ||
|
|
eba0806d77 | ||
|
|
b03e9ef1c7 | ||
|
|
8e59d8b682 | ||
|
|
ecf6295b4a | ||
|
|
569df37442 | ||
|
|
d733e1abea | ||
|
|
c3efcb3896 | ||
|
|
1e7f554bcd | ||
|
|
0e9fe7f194 | ||
|
|
f5716ef141 | ||
|
|
652a21ba8a | ||
|
|
4d39943d0f | ||
|
|
540ea34ade | ||
|
|
183a2b5579 | ||
|
|
d2ece3c165 | ||
|
|
a31d6b0886 | ||
|
|
2e62025d7e | ||
|
|
42522a277c | ||
|
|
987ade361d | ||
|
|
385df95ef4 | ||
|
|
5b9fb5ac02 | ||
|
|
c96f246200 | ||
|
|
e55a8ab77a | ||
|
|
fe137c0941 | ||
|
|
285a73cf0e | ||
|
|
306a6f57f3 | ||
|
|
105f5ec092 | ||
|
|
275e2d8ff8 | ||
|
|
ca33bcf2ab | ||
|
|
386a249b0c | ||
|
|
dd8b1680f5 | ||
|
|
6acfa2babf | ||
|
|
7025e9dff9 | ||
|
|
67cdbb54bc | ||
|
|
2028a2c2f3 | ||
|
|
570fdbf9fd | ||
|
|
090a270246 | ||
|
|
fcb09f440f | ||
|
|
ab2ad8aec1 | ||
|
|
55334cfbc0 | ||
|
|
590273e2fd | ||
|
|
a813e261b4 | ||
|
|
043961fc3e | ||
|
|
c85cefde67 | ||
|
|
34326df680 | ||
|
|
6af09cb2ad | ||
|
|
70fc175e74 | ||
|
|
4fd4ed6ca3 | ||
|
|
9c6d5a8408 | ||
|
|
6079d4488a | ||
|
|
0163e0b099 | ||
|
|
ace8c56199 | ||
|
|
abd05a38ad | ||
|
|
e7c8346861 | ||
|
|
69dd8c61c9 | ||
|
|
4e3185af88 | ||
|
|
b69c02a824 | ||
|
|
606da069b4 | ||
|
|
607f473be2 | ||
|
|
e283b6fb25 | ||
|
|
da7e95a221 | ||
|
|
754842c6af | ||
|
|
675b6e9228 | ||
|
|
070c5ee632 | ||
|
|
b78042fc2f | ||
|
|
a55c689d7c | ||
|
|
f0a28c381b | ||
|
|
3d9a5e6866 | ||
|
|
857d5fe6a1 | ||
|
|
1e485d5dbc | ||
|
|
20bc6d6789 | ||
|
|
ed07a70327 | ||
|
|
dfc10f4a32 | ||
|
|
64cc3b90c7 | ||
|
|
02776f9768 | ||
|
|
2ab6662a13 | ||
|
|
be56413593 | ||
|
|
f0de9c100d | ||
|
|
5693327ca1 | ||
|
|
a1f82dcfb7 | ||
|
|
2b7fd07150 | ||
|
|
9c197693d7 | ||
|
|
6afd79c771 | ||
|
|
35485379bd | ||
|
|
da3deefae2 | ||
|
|
1a53086f7c | ||
|
|
0ca6bb3a1f | ||
|
|
c5d461c379 | ||
|
|
531e2b442d | ||
|
|
7ead579c67 | ||
|
|
2e91094afa | ||
|
|
b991941227 | ||
|
|
65aa894749 | ||
|
|
3f478d8597 | ||
|
|
470f6742bc | ||
|
|
3f2f38842c | ||
|
|
a6121c7f67 | ||
|
|
bb3f271a20 | ||
|
|
3de42f56c5 | ||
|
|
fcb4f258f5 | ||
|
|
8ed55b3aff | ||
|
|
d5444e1172 | ||
|
|
ea827e4c5d | ||
|
|
95e77bd7cb | ||
|
|
321b217feb | ||
|
|
3958c19f83 | ||
|
|
7709d92289 | ||
|
|
868b548dd0 | ||
|
|
837aab0e98 | ||
|
|
77ac20a66b | ||
|
|
7362ecb3b4 | ||
|
|
88ffe75b58 | ||
|
|
c1520c9e11 | ||
|
|
9a1501ae89 | ||
|
|
514e106414 | ||
|
|
4c001bd248 | ||
|
|
fb6ceb2664 | ||
|
|
50da26da91 | ||
|
|
28857841aa | ||
|
|
157d129e30 | ||
|
|
9ab447843b | ||
|
|
fe6da3a16b | ||
|
|
fac9aac821 | ||
|
|
d7f4ce6cff | ||
|
|
a9cca5e76c | ||
|
|
7a64527db5 | ||
|
|
bc23a610d3 | ||
|
|
9def45aa80 | ||
|
|
85878d894a | ||
|
|
ae4cd1ebf1 | ||
|
|
781c7a800f | ||
|
|
5133ae52df | ||
|
|
3539af7d77 | ||
|
|
c03894321a | ||
|
|
b7e886a740 | ||
|
|
8268c61b9f | ||
|
|
fee6e8e11b | ||
|
|
558d749d54 | ||
|
|
27cb54c1db | ||
|
|
4c9be808a7 | ||
|
|
71893807b3 | ||
|
|
08485f6781 | ||
|
|
87232358d3 | ||
|
|
ec9c000be9 | ||
|
|
75f3a2183f | ||
|
|
445e485fc5 | ||
|
|
508b5ccc63 | ||
|
|
833bb542fc | ||
|
|
981d67324b | ||
|
|
140b9e5a5a | ||
|
|
a4768d1638 | ||
|
|
abf353c178 | ||
|
|
670bb32b2c | ||
|
|
f836a47bc1 | ||
|
|
43df39c4f7 | ||
|
|
9da5c5e2d0 | ||
|
|
cdbbac5b6d | ||
|
|
3120558dd1 | ||
|
|
f3c91e4f96 | ||
|
|
b1fa430dad | ||
|
|
83bc362dac | ||
|
|
a86baefda7 | ||
|
|
82790b1556 | ||
|
|
728f643ea7 | ||
|
|
d5fc040d2d | ||
|
|
6c0b8229ec | ||
|
|
bc60693a5d | ||
|
|
1298374818 | ||
|
|
a078cfee3e | ||
|
|
bd4572cfd1 | ||
|
|
df8a600b22 | ||
|
|
567f85752d | ||
|
|
5009259473 | ||
|
|
b21a974342 | ||
|
|
ab783b9bb2 | ||
|
|
36d236c5e0 | ||
|
|
408faa11a1 | ||
|
|
8972e829f2 | ||
|
|
c4703a7f19 | ||
|
|
f6cfe6b873 | ||
|
|
bceef8f529 | ||
|
|
b23042839b | ||
|
|
5625be70fa | ||
|
|
5c187363b2 | ||
|
|
93f4844c9c | ||
|
|
39fd9b93c3 | ||
|
|
c38e0d05bf | ||
|
|
5a4d7fb0d6 | ||
|
|
6303334cc2 | ||
|
|
472742266b | ||
|
|
0889048cb5 | ||
|
|
57b6e74abb | ||
|
|
a8af0c0ca4 | ||
|
|
07fe8d4481 | ||
|
|
e0e1d0855d | ||
|
|
178591b6d5 | ||
|
|
dd475d8af4 | ||
|
|
121d22f137 | ||
|
|
59bf1d8cd9 | ||
|
|
780543f0a2 | ||
|
|
de78ffb551 | ||
|
|
d445638265 | ||
|
|
f61f6626b7 | ||
|
|
857ba25115 | ||
|
|
00774bc28b | ||
|
|
f572266f19 | ||
|
|
c3893064a3 | ||
|
|
4183443f02 | ||
|
|
5715434d5e | ||
|
|
ab2419db24 | ||
|
|
35a83c3514 | ||
|
|
de09cac1e9 | ||
|
|
b64f4bdb3a | ||
|
|
008310aad9 | ||
|
|
4d024d737c | ||
|
|
50edb30a18 | ||
|
|
00f6bd0f08 | ||
|
|
81e40a1172 | ||
|
|
fe4f89f3fa | ||
|
|
0d372250c1 | ||
|
|
367720020c | ||
|
|
0dab82085d | ||
|
|
1aba7f1fde | ||
|
|
3017b0213b | ||
|
|
d748c8c653 | ||
|
|
af8cb63292 | ||
|
|
d7f9da49eb | ||
|
|
70bddcf318 | ||
|
|
f0bb5313d3 | ||
|
|
c352fbf5c9 | ||
|
|
9c3c286aab | ||
|
|
e10adf83d5 | ||
|
|
c24a04d246 | ||
|
|
2e89529eef | ||
|
|
92d03f3832 | ||
|
|
2efd0996e6 | ||
|
|
ac4f13e78f | ||
|
|
2bdd0a6bdb | ||
|
|
c044a3c1ca | ||
|
|
72cb4e9bea | ||
|
|
fc0ac64277 | ||
|
|
df4c9e2903 | ||
|
|
73dbd2f0ab | ||
|
|
05d5dc4cec | ||
|
|
815b5cb086 | ||
|
|
0837d674eb | ||
|
|
a7bde8e523 | ||
|
|
c63c05b141 | ||
|
|
12ccbe6f12 | ||
|
|
be540043ee | ||
|
|
de8d80dee0 | ||
|
|
98fd5bd453 | ||
|
|
15b569fb71 | ||
|
|
58eccc7a2a | ||
|
|
e39e47b21f | ||
|
|
e7f6e7d7b6 | ||
|
|
af27d97003 | ||
|
|
c8e46749a4 | ||
|
|
eb432d3da0 | ||
|
|
4ab3c54f5b | ||
|
|
de1d8ec070 | ||
|
|
c8a41409d9 | ||
|
|
d2a089ffd9 | ||
|
|
3bd5169aee | ||
|
|
b7887a339d | ||
|
|
1b89d4d541 | ||
|
|
d8190e1c5f | ||
|
|
2af5191070 | ||
|
|
4027e08cc8 | ||
|
|
72eeb9fecb | ||
|
|
5a07f5a652 | ||
|
|
d767ab0890 | ||
|
|
df2fd399df | ||
|
|
f2b5c35fa2 | ||
|
|
b2057a5da7 | ||
|
|
38cacfa577 | ||
|
|
13e271c6cb | ||
|
|
7a6ca85081 | ||
|
|
ecfe9c5c2f | ||
|
|
9f263319ca | ||
|
|
00ef597639 | ||
|
|
30e280b694 | ||
|
|
b9adf244e5 | ||
|
|
8a5e49c856 | ||
|
|
284f6d3682 | ||
|
|
cc1bfc6d03 | ||
|
|
07fdf7158d | ||
|
|
fca60e2a41 | ||
|
|
e12db0c182 | ||
|
|
1d673ce075 | ||
|
|
7e4462e0ac | ||
|
|
f87994edc7 | ||
|
|
ef0c19477c | ||
|
|
9bee6d899b | ||
|
|
2d8e4df7bc | ||
|
|
4e1679d2a2 | ||
|
|
fd8e02195e | ||
|
|
b2982a6f14 | ||
|
|
a9a0f1e129 | ||
|
|
0ea1ec31fc | ||
|
|
f8bff346f4 | ||
|
|
ff88ed7c75 | ||
|
|
57a737c417 | ||
|
|
eb8785a209 | ||
|
|
d4b10db998 | ||
|
|
3a35db6e67 | ||
|
|
e79587c934 | ||
|
|
8935f2af3c | ||
|
|
517f1602aa | ||
|
|
e8a8525bcd | ||
|
|
6e3246c5fd | ||
|
|
97066bf795 | ||
|
|
5df60d85f7 | ||
|
|
008d6be900 | ||
|
|
d1a1aed23e | ||
|
|
673d2499e8 | ||
|
|
6ac053bbaa | ||
|
|
6c6e48f006 | ||
|
|
bb6782ca58 | ||
|
|
d307d01b18 | ||
|
|
08284e420d | ||
|
|
d53ef90a73 | ||
|
|
b487341c32 | ||
|
|
6a7a613741 | ||
|
|
9cee9bc279 | ||
|
|
f638482fba | ||
|
|
a7908da968 | ||
|
|
20bfaba0b7 | ||
|
|
5a27c05b6a | ||
|
|
24cc33e704 | ||
|
|
6c4a110679 | ||
|
|
b42b03bc40 | ||
|
|
5727d74d37 | ||
|
|
bc4ab8b99e | ||
|
|
24f2c38093 | ||
|
|
dfba79f8ff | ||
|
|
0d817ff4ff | ||
|
|
178943b4b7 | ||
|
|
751ede516b | ||
|
|
cda112493a | ||
|
|
fa072c1d2c | ||
|
|
a862e4290c | ||
|
|
234b01a8c2 | ||
|
|
e3b9828f24 | ||
|
|
8339594206 | ||
|
|
2386bfda7e | ||
|
|
ef0009aea7 | ||
|
|
4d24537590 | ||
|
|
4f84b01356 | ||
|
|
c8b615acc9 | ||
|
|
61a5733692 | ||
|
|
4245a7604b | ||
|
|
d5d6e36ae0 | ||
|
|
658bc9fcc8 | ||
|
|
60810c2d37 | ||
|
|
f07e1026ac | ||
|
|
32f68f35cf | ||
|
|
02a893d613 | ||
|
|
f596c36f4f | ||
|
|
8ff0e1da15 | ||
|
|
4660310db6 | ||
|
|
58bf4f04b1 | ||
|
|
d88f0866b7 | ||
|
|
bf52d1e624 | ||
|
|
fc80f65a6d | ||
|
|
badd42789a | ||
|
|
c524c52baa | ||
|
|
585ca90ae0 | ||
|
|
e734b3f0d8 | ||
|
|
91ea47fddf | ||
|
|
9b97147637 | ||
|
|
d4d4712361 | ||
|
|
879f7e9f03 | ||
|
|
aaf77025b6 | ||
|
|
cdce33dd05 | ||
|
|
b10797b3d5 | ||
|
|
e73e562a63 | ||
|
|
e40417f687 | ||
|
|
aac616fcc5 | ||
|
|
fa0745f7da | ||
|
|
00a3e6bbd7 | ||
|
|
bb7f57b095 | ||
|
|
d85c842ce9 | ||
|
|
dce166dc93 | ||
|
|
13a0e5fb4a | ||
|
|
0c3a4cc7b9 | ||
|
|
e9ab5bc223 | ||
|
|
5da18d34ba | ||
|
|
09d542dfe0 | ||
|
|
5280863300 | ||
|
|
fbec378869 | ||
|
|
9fcd7f2dc0 | ||
|
|
229389b7f6 | ||
|
|
5d8a22066c | ||
|
|
63a9853811 | ||
|
|
39b1311a1b | ||
|
|
f4eba3bfba | ||
|
|
4caf0e4cb9 | ||
|
|
762fca538c | ||
|
|
fa1d80b53b | ||
|
|
b4be483d3e | ||
|
|
57de599a29 | ||
|
|
492aab20fe | ||
|
|
6952bab519 | ||
|
|
87fa4de59c | ||
|
|
0d4b489545 | ||
|
|
0d30a3071a | ||
|
|
753f03ff6a | ||
|
|
bf2098c07f | ||
|
|
2f35b121a4 | ||
|
|
34286d77c7 | ||
|
|
e3cc26cb7c | ||
|
|
84dd812da4 | ||
|
|
4acbd59162 | ||
|
|
1e316a9704 | ||
|
|
6ca214fefc | ||
|
|
fa47af737f | ||
|
|
0f2517070e | ||
|
|
f483d10c95 | ||
|
|
8c3a6a819e | ||
|
|
933432e62d | ||
|
|
2b97fead9e | ||
|
|
9ac6d330b4 | ||
|
|
4843890c56 | ||
|
|
c81e0b7433 | ||
|
|
ce97210eb1 | ||
|
|
cb5fa56e17 | ||
|
|
6cbb9193ea | ||
|
|
3cac17d23e | ||
|
|
d4b107e2e8 | ||
|
|
c90ebad46b | ||
|
|
af69d4f7a9 | ||
|
|
6c8ae2b72a | ||
|
|
7901087466 | ||
|
|
e2ccd14c05 | ||
|
|
a14320fc44 | ||
|
|
eb52a149a0 | ||
|
|
adaa4cc2f3 | ||
|
|
f2b99332d9 | ||
|
|
4859cf44ce | ||
|
|
83f779c52d | ||
|
|
c9e10e1dd9 | ||
|
|
4259ac96ea | ||
|
|
714c9361ea | ||
|
|
93381014a0 | ||
|
|
16aaef097a | ||
|
|
1d9c9710d7 | ||
|
|
e7dbd325d2 | ||
|
|
893594d81a | ||
|
|
176e674a51 | ||
|
|
e10d8080ba | ||
|
|
9da1354f3a | ||
|
|
e1f6108789 | ||
|
|
40bf88ac74 | ||
|
|
ffec698d3e | ||
|
|
b1ee137177 | ||
|
|
fbec168e91 | ||
|
|
89f3502b56 | ||
|
|
5dbc1d4c08 | ||
|
|
c0f852e016 | ||
|
|
397682a5b0 | ||
|
|
2b99dabdac | ||
|
|
df9975f35d | ||
|
|
4158759265 | ||
|
|
8927e7caf6 | ||
|
|
a46fe79939 | ||
|
|
2ef080a51b | ||
|
|
4468813d47 | ||
|
|
6a2eb4da07 | ||
|
|
731b84d725 | ||
|
|
362e4505e8 | ||
|
|
13013d1b8b | ||
|
|
6df0de565f | ||
|
|
89eabb5803 | ||
|
|
6aa4f14a28 | ||
|
|
be7844192b | ||
|
|
9e3a11534f | ||
|
|
a29d3cf074 | ||
|
|
f6ecd931dc | ||
|
|
f0180ad488 | ||
|
|
afc48cf224 | ||
|
|
5958714309 | ||
|
|
fb461d21a5 | ||
|
|
404a063fdf | ||
|
|
8cbd629010 | ||
|
|
e9233bc169 | ||
|
|
bec080be8d | ||
|
|
371b9a7fc2 | ||
|
|
699d1bf27c | ||
|
|
225aa107f6 | ||
|
|
e416c99419 | ||
|
|
7c9706fdcf | ||
|
|
f08e4bb27d | ||
|
|
432988a4ad | ||
|
|
a8cf10b0b5 | ||
|
|
e985b7a0bf | ||
|
|
6caa06eaed | ||
|
|
02fa33252a | ||
|
|
2211f4f8f7 | ||
|
|
5ceb327e55 | ||
|
|
b0f76d82c5 | ||
|
|
2bcebc4e4e | ||
|
|
cd55a533e8 | ||
|
|
021eddac73 | ||
|
|
5756d6262e | ||
|
|
56943bef48 | ||
|
|
ee727eb65e | ||
|
|
e8008c1b21 | ||
|
|
bdc124ba41 | ||
|
|
f27f701251 | ||
|
|
b2f0a37b18 | ||
|
|
050152eb90 | ||
|
|
3987318f09 | ||
|
|
9f684eac92 | ||
|
|
abf3142b26 | ||
|
|
eeb873b23c | ||
|
|
84d4647329 | ||
|
|
7acb14f7a1 | ||
|
|
2587302987 | ||
|
|
db88d24ff8 | ||
|
|
2ea8d9ca11 | ||
|
|
0383c44f0d | ||
|
|
08ee6d8d4b | ||
|
|
e7dd9737bd | ||
|
|
ceaa7e2fb0 | ||
|
|
8093044f07 | ||
|
|
9cca12ff0b | ||
|
|
1b0d2a37bb | ||
|
|
92c55c27cf | ||
|
|
ed7d4037b2 | ||
|
|
dd094d7606 | ||
|
|
1ba26d67bd | ||
|
|
e824e9023f | ||
|
|
d20f1182f2 | ||
|
|
e83530d40b | ||
|
|
6f494a968d | ||
|
|
133f706bf3 | ||
|
|
863c9b55b4 | ||
|
|
45561b89a4 | ||
|
|
6b9250e4ef | ||
|
|
5089e8342f | ||
|
|
b12be0498e | ||
|
|
59abf1bb42 | ||
|
|
518ecd7f4e | ||
|
|
025516a005 | ||
|
|
345e93d19c | ||
|
|
d98ea7fdb6 | ||
|
|
a7eaee77ca | ||
|
|
0d41996562 | ||
|
|
7bae8ab838 | ||
|
|
4bf95703a0 | ||
|
|
2766c70ad3 | ||
|
|
2c390b5473 | ||
|
|
20692d54de | ||
|
|
dc7fb26921 | ||
|
|
171f911237 | ||
|
|
bd42cc2c77 | ||
|
|
8db4ba9e58 | ||
|
|
7c7ae79f9f | ||
|
|
8674dc831d | ||
|
|
3fbe42c3a2 | ||
|
|
6550bc252f | ||
|
|
e04f618979 | ||
|
|
c2c8d4d410 | ||
|
|
15f73258fd | ||
|
|
34ce86a8f5 | ||
|
|
05a53cd330 | ||
|
|
e17455cb22 | ||
|
|
995c192874 | ||
|
|
c9cd0d20ef | ||
|
|
3eab5e9002 | ||
|
|
a22b1700a4 | ||
|
|
8e9d7611ae | ||
|
|
de4cc5c20a | ||
|
|
0b423dd061 | ||
|
|
2c44620e5e | ||
|
|
d0753dddb1 | ||
|
|
c9ab61aa8c | ||
|
|
431444ba9f | ||
|
|
c99196d363 | ||
|
|
22a1653702 | ||
|
|
edd947b645 | ||
|
|
b8dc349099 | ||
|
|
93c2aff2cf | ||
|
|
ad5e9aa5e3 | ||
|
|
467d3a8c62 | ||
|
|
bc336480e6 | ||
|
|
32e492837c | ||
|
|
4f1dbb127a | ||
|
|
93dfa8a6d8 | ||
|
|
5054918efc | ||
|
|
335af393f0 | ||
|
|
524721ee27 | ||
|
|
5405a558fd | ||
|
|
094c433e58 | ||
|
|
961652c2e9 | ||
|
|
322c4a5b2b | ||
|
|
b6c7c5a7ab | ||
|
|
46c930cf70 | ||
|
|
e0d4a9d575 | ||
|
|
04f0d545da | ||
|
|
cad8e895f2 | ||
|
|
cb38b841af | ||
|
|
d58cc7fb7a | ||
|
|
7e143cb33d | ||
|
|
04dc4a10f0 | ||
|
|
47e557b96a | ||
|
|
3b346fd3c9 | ||
|
|
0d61598d8a | ||
|
|
61d0f613df | ||
|
|
00d9d96e48 | ||
|
|
b1aec1b5c8 | ||
|
|
5d3e830176 | ||
|
|
91ba02449b | ||
|
|
7162b536eb | ||
|
|
ca1a723890 | ||
|
|
16c22477c2 | ||
|
|
128ed87dd8 | ||
|
|
9408a1a025 | ||
|
|
777dca7043 | ||
|
|
f5b35a074f | ||
|
|
c6cf90f67b | ||
|
|
cb6e3ac6e1 | ||
|
|
2c2bc4a427 | ||
|
|
e5a6048eec | ||
|
|
3c60d359ed | ||
|
|
94a54375e2 | ||
|
|
c871b6dd4e | ||
|
|
62c6667b0b | ||
|
|
704b5d88b9 | ||
|
|
a292cc42aa | ||
|
|
da832a295e | ||
|
|
64b59184d1 | ||
|
|
bd06466d3a | ||
|
|
d4123a387c | ||
|
|
e7be135b78 | ||
|
|
d1a55e9ca4 | ||
|
|
6eb9269741 | ||
|
|
a98200bb4c | ||
|
|
55fafb7d25 | ||
|
|
321bcf5c44 | ||
|
|
689aaf50b3 | ||
|
|
2ec3325381 | ||
|
|
634e49b961 | ||
|
|
cb211d63cd | ||
|
|
55f40a7f8d | ||
|
|
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 | ||
|
|
587f6656a4 | ||
|
|
4377c03168 | ||
|
|
1e0520074c | ||
|
|
b9f6832347 | ||
|
|
f8bb0cd3d1 | ||
|
|
129aef758e | ||
|
|
94f55cf406 | ||
|
|
a20b758e19 | ||
|
|
128842becf | ||
|
|
d51331f51f | ||
|
|
4ef221a645 | ||
|
|
79010e972e | ||
|
|
0ca530e251 | ||
|
|
57b4d46dbc | ||
|
|
50df5e2f59 | ||
|
|
b6db2c7262 | ||
|
|
9441b69ad2 | ||
|
|
08e3d16a58 | ||
|
|
3f3049fdba | ||
|
|
41fb7a8a7e | ||
|
|
5b9828e094 | ||
|
|
96e35585b0 | ||
|
|
f035fe9336 | ||
|
|
44c2e33c78 | ||
|
|
2785dcbbbf | ||
|
|
9b03bd3243 | ||
|
|
8f30456ee3 | ||
|
|
38557ff635 | ||
|
|
7bc2cde4dd | ||
|
|
f065d3a06b | ||
|
|
21f7e3a987 | ||
|
|
9f688bc433 | ||
|
|
6e9d31d4fb | ||
|
|
e81c48526b | ||
|
|
b405985b80 | ||
|
|
8b010c5a9f | ||
|
|
1e7b5d6fdb | ||
|
|
a55982e7f0 | ||
|
|
1ac378063e | ||
|
|
d497c92684 | ||
|
|
677dc2c155 | ||
|
|
beea8deeb5 | ||
|
|
0d0f1a2fb2 | ||
|
|
439701ed7a | ||
|
|
d945d0129c | ||
|
|
cc91477308 | ||
|
|
ac139ec03d | ||
|
|
b4f0e834bf | ||
|
|
6e6cdc834f | ||
|
|
4b81ae1b35 | ||
|
|
d569dc45a8 | ||
|
|
23d49fda29 | ||
|
|
62ad2c3bc1 | ||
|
|
25373ad294 | ||
|
|
26d0c0fd8d | ||
|
|
3afffcd36b | ||
|
|
5440de1785 | ||
|
|
25a56f11c8 | ||
|
|
8271c6481f | ||
|
|
83e26f839d | ||
|
|
c3f7905d82 | ||
|
|
05b54a8d18 | ||
|
|
e5cfdd369e | ||
|
|
c61d8cfb85 | ||
|
|
27f4195471 | ||
|
|
b2596eda32 | ||
|
|
9379440fcb | ||
|
|
b7c502c8d1 | ||
|
|
3c16d2d9b4 | ||
|
|
6cbc03a418 | ||
|
|
15ecc0fa65 | ||
|
|
7f2f8cdad1 | ||
|
|
a64646cb7b | ||
|
|
05f531c538 | ||
|
|
7ca335446b | ||
|
|
99e96a6581 | ||
|
|
7200d8f90e | ||
|
|
7038837aca | ||
|
|
541dcc0e5a | ||
|
|
1060b5aabf | ||
|
|
6ea73c4982 | ||
|
|
cff847273a | ||
|
|
e669f8db9b | ||
|
|
461cc30842 | ||
|
|
13d7cb957c | ||
|
|
47a439a905 | ||
|
|
053fb96694 | ||
|
|
b72c1f7367 | ||
|
|
e90738575f | ||
|
|
35718eec9c | ||
|
|
8285a53152 | ||
|
|
116fe7815b | ||
|
|
4c2efd7da3 | ||
|
|
1a433e3185 | ||
|
|
fa98a00916 | ||
|
|
f4dd46ad60 | ||
|
|
41825ccfbb | ||
|
|
4d1a5f12c0 | ||
|
|
cc58d56c6d | ||
|
|
683ef07312 | ||
|
|
c77c78cef5 | ||
|
|
88517030a4 | ||
|
|
45471e8e63 | ||
|
|
4e620beb75 | ||
|
|
e7ab875b47 | ||
|
|
be74ed9ab6 | ||
|
|
52b2eacd37 | ||
|
|
6b4210a2ea | ||
|
|
740d0da5ee | ||
|
|
34883d356e | ||
|
|
aeafcce314 | ||
|
|
ae1d82c325 | ||
|
|
1c89a07226 | ||
|
|
43e262f13e | ||
|
|
e5f802ab5c | ||
|
|
847860fc5c | ||
|
|
77e936445f | ||
|
|
41beb74ef7 | ||
|
|
67be50b706 | ||
|
|
cd840b7c9d | ||
|
|
07903949ec | ||
|
|
d2156ddaad | ||
|
|
a84ff4b3ff | ||
|
|
c02f13d33f | ||
|
|
f6490859fd | ||
|
|
4b7816dbf4 | ||
|
|
86ffbc3ec5 | ||
|
|
67257f44a5 | ||
|
|
3086f5567a | ||
|
|
bb8acb095d | ||
|
|
24be3cbb5f | ||
|
|
9773191103 | ||
|
|
ddc703c3ec | ||
|
|
09ec204e4b | ||
|
|
8b6cafa0e0 | ||
|
|
1ee8be9d43 | ||
|
|
91bc190d21 | ||
|
|
4dc833b642 | ||
|
|
895e9f8d5c | ||
|
|
12906ff631 | ||
|
|
9c9bcff107 | ||
|
|
443ca5c63b | ||
|
|
1102b1fc4c | ||
|
|
0d089eab21 | ||
|
|
b44fada29e | ||
|
|
859ea160e6 | ||
|
|
1c49e2ffc0 | ||
|
|
140245c58d | ||
|
|
47adcced60 | ||
|
|
a2054deb12 | ||
|
|
30fdfd9ded | ||
|
|
a87a86eb92 | ||
|
|
49dfbcfbc8 | ||
|
|
c558a4f4f6 | ||
|
|
4f49a2248f | ||
|
|
4e57da42c1 | ||
|
|
4f6c9e206c | ||
|
|
699e1d4b77 | ||
|
|
fa2978b3b9 | ||
|
|
ffcdf741fc | ||
|
|
429d7f33d4 | ||
|
|
b6a229775e | ||
|
|
0a4580368a | ||
|
|
e5b191e1f1 | ||
|
|
a07acc067b | ||
|
|
01e763f879 | ||
|
|
a95e75261a | ||
|
|
dcaa7ac609 | ||
|
|
7d2ae10849 | ||
|
|
642cac3759 | ||
|
|
662af2edba | ||
|
|
6f97e15a8f | ||
|
|
ab2fa2ca9d | ||
|
|
4666c99b27 | ||
|
|
e38415c56d | ||
|
|
3f3fce4664 | ||
|
|
1407a7bc8a | ||
|
|
1d268f4b83 | ||
|
|
92aab79d27 | ||
|
|
0823bb1276 | ||
|
|
18e42efa01 | ||
|
|
0ea16328df | ||
|
|
b037efc5c6 | ||
|
|
839bb71389 | ||
|
|
2ef7df5cfe | ||
|
|
f5d2d79f75 | ||
|
|
8e5fa96ffc | ||
|
|
6dbbe07d50 | ||
|
|
3c23410753 | ||
|
|
8e35ab78b4 | ||
|
|
7f2911b7f0 | ||
|
|
d877e90301 | ||
|
|
93db4be726 | ||
|
|
1f419ad19f | ||
|
|
3c7f6508ad | ||
|
|
39449d4b63 | ||
|
|
28e05295fa | ||
|
|
60c18d3550 | ||
|
|
482ab186a2 | ||
|
|
4814195dc4 | ||
|
|
d215b7a10e | ||
|
|
0e52b78590 | ||
|
|
b3a9d607c4 | ||
|
|
c00081b62c | ||
|
|
5e4739b460 | ||
|
|
0d8f598df2 | ||
|
|
2ef04cc308 | ||
|
|
7a0884e2cd | ||
|
|
fa858530cc | ||
|
|
1c61fe5ed9 | ||
|
|
57409ef382 | ||
|
|
06ba826803 | ||
|
|
6e6ef9489f | ||
|
|
45bbe39d1f | ||
|
|
6c9fc13083 | ||
|
|
4c8a642388 | ||
|
|
6208c9d64f | ||
|
|
76325d0ba9 | ||
|
|
cf1802a6de | ||
|
|
538a7b12bd | ||
|
|
57e0f52aaa | ||
|
|
1ae0335b62 | ||
|
|
ca1bff6b66 | ||
|
|
10cc62d2ca | ||
|
|
dd451a8a00 | ||
|
|
444ec1e412 | ||
|
|
82739f4d7d | ||
|
|
19825d853e | ||
|
|
d8ece2e3e9 | ||
|
|
bf4deb0ce6 | ||
|
|
da4739a26c | ||
|
|
fc24bf0915 | ||
|
|
9329b99cba |
@@ -1,33 +0,0 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
UseTab: Always
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Custom
|
||||
Standard: Cpp11
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
FixNamespaceComments: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
IndentCaseLabels: false
|
||||
AccessModifierOffset: -4
|
||||
ColumnLimit: 90
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
SortIncludes: false
|
||||
IncludeCategories:
|
||||
- Regex: '^".*'
|
||||
Priority: 2
|
||||
- Regex: '^<.*'
|
||||
Priority: 1
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
ContinuationIndentWidth: 8
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
BreakConstructorInitializers: AfterColon
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
2
.editorconfig
Executable file → Normal file
2
.editorconfig
Executable file → Normal file
@@ -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
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,2 +1,5 @@
|
||||
# Forces all files which git considers text files to use LF line endings
|
||||
* text=auto eol=lf
|
||||
|
||||
*.cpp diff=cpp
|
||||
*.h diff=cpp
|
||||
|
||||
21
.github/CONTRIBUTING.md
vendored
21
.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,12 +30,15 @@ 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
|
||||
[Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
|
||||
- Check your code works as expected and document any changes to the Lua API.
|
||||
- To avoid conflicting changes between contributions, do not do the following manually. They will be done before each release.
|
||||
- Run `updatepo.sh` or update `minetest.po{,t}` even if your code adds new translatable strings.
|
||||
- Update `minetest.conf.example` and `settings_translation_file.cpp` even if your code adds new core settings.
|
||||
|
||||
4. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch)
|
||||
- Commit messages should:
|
||||
@@ -67,20 +70,6 @@ Contributions are welcome! Here's how you can help:
|
||||
might need more work in the future.
|
||||
5. It uses protocols and formats which include the required compatibility.
|
||||
|
||||
### Important note about automated GitHub checks
|
||||
|
||||
When you submit a pull request, GitHub automatically runs checks on the Minetest
|
||||
Engine combined with your changes. One of these checks is called 'cpp lint /
|
||||
clang format', which checks code formatting. Because formatting for readability
|
||||
requires human judgement this check often fails and often makes unsuitable
|
||||
formatting requests which make code readability worse.
|
||||
|
||||
If this check fails, look at the details to check for any clear mistakes and
|
||||
correct those. However, you should not apply everything ClangFormat requests.
|
||||
Ignore requests that make code readability worse and any other clearly
|
||||
unsuitable requests. Discuss in the pull request with a core developer about how
|
||||
to progress.
|
||||
|
||||
## Issues
|
||||
|
||||
If you experience an issue, we would like to know the details - especially when
|
||||
|
||||
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
|
||||
<!-- Explain how the problem has happened, providing a minimal test (i.e. a code snippet reduced to the bone) where possible -->
|
||||
84
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
84
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
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 dev version, please also indicate the git commit hash.
|
||||
Refer to the "About" tab of the menu or run `minetest --version` on the command line.
|
||||
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: 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 persists.
|
||||
placeholder: "Example: Ubuntu 22.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: CPU model
|
||||
description: Usually found in OS/system settings.
|
||||
placeholder: "Example: Intel Core i5-2410M"
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: The GPU model and renderer can be omitted if the bug is not a graphical issue.
|
||||
- type: input
|
||||
attributes:
|
||||
label: GPU model
|
||||
description: Usually found in OS/system settings.
|
||||
placeholder: "Example: NVIDIA GeForce GTX 1660"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Active renderer
|
||||
description: You can find this in the "About" tab in the main menu.
|
||||
placeholder: "Example: OpenGL 4.6.0"
|
||||
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 (e.g. a minimized code snippet) 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
|
||||
|
||||
38
.github/workflows/android.yml
vendored
38
.github/workflows/android.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'android/**'
|
||||
- '.github/workflows/android.yml'
|
||||
pull_request:
|
||||
@@ -16,37 +20,55 @@ on:
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'android/**'
|
||||
- '.github/workflows/android.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
sudo apt-get install -y --no-install-recommends gettext
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- 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@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Minetest-armeabi-v7a.apk
|
||||
path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk
|
||||
- name: Save arm64 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
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
|
||||
uses: actions/upload-artifact@v4
|
||||
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
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Minetest-x86_64.apk
|
||||
path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk
|
||||
|
||||
276
.github/workflows/build.yml
vendored
276
.github/workflows/build.yml
vendored
@@ -1,276 +0,0 @@
|
||||
name: build
|
||||
|
||||
# build on c/cpp changes or workflow changes
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/buildbot/**'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/**.yml'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/buildbot/**'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/**.yml'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
|
||||
env:
|
||||
MINETEST_POSTGRESQL_CONNECT_STRING: 'host=localhost user=minetest password=minetest dbname=minetest'
|
||||
|
||||
jobs:
|
||||
# Older gcc version (should be close to our minimum supported version)
|
||||
gcc_5:
|
||||
runs-on: ubuntu-18.04
|
||||
if: "false" # FIXME
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-5
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-5
|
||||
CXX: g++-5
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
# Current gcc version
|
||||
gcc_12:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-12 libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
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
|
||||
if: "false" # FIXME
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-3.9 valgrind
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-3.9
|
||||
CXX: clang++-3.9
|
||||
|
||||
- name: Unittest
|
||||
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
|
||||
|
||||
# Current clang version
|
||||
clang_14:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-14 gdb
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-14
|
||||
CXX: clang++-14
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
# Build with prometheus-cpp (server-only)
|
||||
clang_9_prometheus:
|
||||
name: "clang_9 (PROMETHEUS=1)"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-9
|
||||
|
||||
- name: Build prometheus-cpp
|
||||
run: |
|
||||
./util/ci/build_prometheus_cpp.sh
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0"
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetestserver --run-unittests
|
||||
|
||||
docker:
|
||||
name: "Docker image"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build . -t minetest:latest
|
||||
docker run --rm minetest:latest /usr/local/bin/minetestserver --version
|
||||
|
||||
win32:
|
||||
name: "MinGW cross-compiler (32-bit)"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo 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-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo 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
|
||||
env:
|
||||
VCPKG_VERSION: 5cf60186a241e84e8232641ee973395d4fde90e1
|
||||
# 2022.02
|
||||
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {
|
||||
arch: x86,
|
||||
generator: "-G'Visual Studio 16 2019' -A Win32",
|
||||
vcpkg_triplet: x86-windows
|
||||
}
|
||||
- {
|
||||
arch: x64,
|
||||
generator: "-G'Visual Studio 16 2019' -A x64",
|
||||
vcpkg_triplet: x64-windows
|
||||
}
|
||||
type: [portable]
|
||||
# type: [portable, installer]
|
||||
# The installer type is working, but disabled, to save runner jobs.
|
||||
# Enable it, when working on the installer.
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout IrrlichtMt
|
||||
run: |
|
||||
$ref = @(Get-Content misc\irrlichtmt_tag.txt)
|
||||
git clone https://github.com/minetest/irrlicht lib\irrlichtmt --depth 1 -b $ref[0]
|
||||
|
||||
- name: Restore from cache and run vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: ${{env.vcpkg_packages}}
|
||||
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
|
||||
appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
|
||||
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
|
||||
|
||||
- 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 `
|
||||
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
|
||||
|
||||
- name: Build Minetest
|
||||
run: cmake --build . --config Release
|
||||
|
||||
- name: CPack
|
||||
run: |
|
||||
If ($env:TYPE -eq "installer")
|
||||
{
|
||||
cpack -G WIX -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
ElseIf($env:TYPE -eq "portable")
|
||||
{
|
||||
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
env:
|
||||
TYPE: ${{matrix.type}}
|
||||
|
||||
- name: Package Clean
|
||||
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
path: .\Package\
|
||||
30
.github/workflows/cpp_lint.yml
vendored
30
.github/workflows/cpp_lint.yml
vendored
@@ -8,6 +8,8 @@ on:
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
@@ -18,37 +20,25 @@ on:
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
env:
|
||||
CLANG_TIDY: clang-tidy-15
|
||||
|
||||
jobs:
|
||||
|
||||
# clang_format:
|
||||
# runs-on: ubuntu-20.04
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - name: Install clang-format
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y clang-format-9
|
||||
#
|
||||
# - name: Run clang-format
|
||||
# run: |
|
||||
# source ./util/ci/clang-format.sh
|
||||
# check_format
|
||||
# env:
|
||||
# CLANG_FORMAT: clang-format-9
|
||||
|
||||
clang_tidy:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-tidy-9
|
||||
install_linux_deps $CLANG_TIDY
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
|
||||
98
.github/workflows/docker_image.yml
vendored
Normal file
98
.github/workflows/docker_image.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: docker_image
|
||||
|
||||
# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
|
||||
# https://docs.docker.com/build/ci/github-actions/multi-platform
|
||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
# Publish semver tags as releases.
|
||||
tags: [ "*" ]
|
||||
pull_request:
|
||||
# Build docker image on pull requests. (but do not publish)
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
- 'misc/irrlichtmt_tag.txt'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
- '.github/workflows/docker_image.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
use_cache:
|
||||
description: "Use build cache"
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
# Login against the Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.5.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Minetest
|
||||
org.opencontainers.image.vendor=Minetest
|
||||
org.opencontainers.image.licenses=LGPL-2.1-only
|
||||
|
||||
# Build and push Docker image
|
||||
# https://github.com/docker/build-push-action
|
||||
# No arm support for now. Require cross-compilation support in Dockerfile to not use QEMU.
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
load: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
no-cache: ${{ (github.event_name == 'workflow_dispatch' && !inputs.use_cache) || startsWith(github.ref, 'refs/tags/') }}
|
||||
|
||||
- name: Test Docker Image
|
||||
run: |
|
||||
docker run --rm $(cut -d, -f1 <<<"$DOCKER_METADATA_OUTPUT_TAGS") minetestserver --version
|
||||
shell: bash
|
||||
154
.github/workflows/linux.yml
vendored
Normal file
154
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
name: linux
|
||||
|
||||
# build on c/cpp changes or workflow changes
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
- '.github/workflows/linux.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
- '.github/workflows/linux.yml'
|
||||
|
||||
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_7:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-7
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-7
|
||||
CXX: g++-7
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
# Current gcc version
|
||||
gcc_14:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-14 libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
mkdir nowrite
|
||||
chmod a-w nowrite
|
||||
cd nowrite
|
||||
../bin/minetest --run-unittests
|
||||
|
||||
# Older clang version (should be close to our minimum supported version)
|
||||
clang_7:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-7 llvm
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
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_18:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-18 lldb
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-18
|
||||
CXX: clang++-18
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
# Build with prometheus-cpp (server-only)
|
||||
clang_11_prometheus:
|
||||
name: "clang_11 (PROMETHEUS=1)"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-11
|
||||
|
||||
- name: Build prometheus-cpp
|
||||
run: ./util/ci/build_prometheus_cpp.sh
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-11
|
||||
CXX: clang++-11
|
||||
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0"
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetestserver --run-unittests
|
||||
28
.github/workflows/lua.yml
vendored
28
.github/workflows/lua.yml
vendored
@@ -14,24 +14,24 @@ on:
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
jobs:
|
||||
# Note that the integration tests are also run build.yml, but only when C++ code is changed.
|
||||
# Note that the integration tests are also run in build.yml, but only when C++ code is changed.
|
||||
integration_tests:
|
||||
name: "Compile and run multiplayer tests"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-10 gdb libluajit-5.1-dev
|
||||
install_linux_deps clang 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"
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: "-DENABLE_GETTEXT=0 -DBUILD_SERVER=0 -DBUILD_UNITTESTS=0"
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
@@ -39,22 +39,18 @@ jobs:
|
||||
|
||||
luacheck:
|
||||
name: "Builtin Luacheck and Unit Tests"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: leafo/gh-actions-lua@v9
|
||||
- uses: actions/checkout@v4
|
||||
- uses: leafo/gh-actions-lua@v10
|
||||
with:
|
||||
luaVersion: "5.1.5"
|
||||
- uses: leafo/gh-actions-luarocks@v4
|
||||
- uses: leafo/gh-actions-luarocks@v4.3.0
|
||||
|
||||
- name: Install LuaJIT
|
||||
run: |
|
||||
cd $HOME
|
||||
git clone https://github.com/LuaJIT/LuaJIT/
|
||||
cd LuaJIT
|
||||
make -j$(nproc)
|
||||
run: ./util/ci/build_luajit.sh
|
||||
|
||||
- name: Install luarocks tools
|
||||
run: |
|
||||
|
||||
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:
|
||||
if: github.repository == 'minetest/minetest'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
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@v4
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: 'public/'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
24
.github/workflows/macos.yml
vendored
24
.github/workflows/macos.yml
vendored
@@ -8,6 +8,9 @@ on:
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- 'irr/**.mm' # Objective-C(++)
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- '.github/workflows/macos.yml'
|
||||
@@ -17,20 +20,19 @@ on:
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- 'irr/**.mm' # Objective-C(++)
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- '.github/workflows/macos.yml'
|
||||
|
||||
env:
|
||||
MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git
|
||||
MINETEST_GAME_BRANCH: master
|
||||
MINETEST_GAME_NAME: minetest_game
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-11
|
||||
# use lowest possible macOS running on x86_64 to support more users
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
@@ -38,8 +40,6 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME
|
||||
git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE \
|
||||
-DINSTALL_DEVTEST=TRUE
|
||||
make -j2
|
||||
cmake --build . -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
|
||||
- name: Test
|
||||
@@ -60,9 +60,11 @@ jobs:
|
||||
- name: CPack
|
||||
run: |
|
||||
cd build
|
||||
rm -rf macos
|
||||
cmake .. -DINSTALL_DEVTEST=FALSE
|
||||
cpack -G ZIP -B macos
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: minetest-macos
|
||||
path: ./build/macos/*.zip
|
||||
|
||||
144
.github/workflows/windows.yml
vendored
Normal file
144
.github/workflows/windows.yml
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
name: windows
|
||||
|
||||
# build on c/cpp changes or workflow changes
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/buildbot/**'
|
||||
- 'misc/*.manifest'
|
||||
- '.github/workflows/windows.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/buildbot/**'
|
||||
- 'misc/*.manifest'
|
||||
- '.github/workflows/windows.yml'
|
||||
|
||||
jobs:
|
||||
mingw:
|
||||
name: "MinGW cross-compiler (${{ matrix.bits }}-bit)"
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
bits: [32, 64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends gettext wine wine${{ matrix.bits }}
|
||||
sudo ./util/buildbot/download_toolchain.sh /usr
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
EXISTING_MINETEST_DIR=$PWD \
|
||||
./util/buildbot/buildwin${{ matrix.bits }}.sh B
|
||||
|
||||
# Check that the resulting binary can run (DLLs etc.)
|
||||
- name: Runtime test
|
||||
run: |
|
||||
dest=$(mktemp -d)
|
||||
unzip -q -d "$dest" B/build/*.zip
|
||||
cd "$dest"/minetest-*-win*
|
||||
wine bin/minetest.exe --version
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "mingw${{ matrix.bits }}"
|
||||
path: B/build/*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
msvc:
|
||||
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
runs-on: windows-2019
|
||||
env:
|
||||
VCPKG_VERSION: 01f602195983451bc83e72f4214af2cbc495aa94
|
||||
# 2024.05.24
|
||||
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {
|
||||
arch: x86,
|
||||
generator: "-G'Visual Studio 16 2019' -A Win32",
|
||||
vcpkg_triplet: x86-windows
|
||||
}
|
||||
- {
|
||||
arch: x64,
|
||||
generator: "-G'Visual Studio 16 2019' -A x64",
|
||||
vcpkg_triplet: x64-windows
|
||||
}
|
||||
type: [portable]
|
||||
# type: [portable, installer]
|
||||
# The installer type is working, but disabled, to save runner jobs.
|
||||
# Enable it, when working on the installer.
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Restore from cache and run vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: ${{env.vcpkg_packages}}
|
||||
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
|
||||
appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
|
||||
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
|
||||
|
||||
- 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 Minetest
|
||||
run: cmake --build . --config Release
|
||||
|
||||
- name: Unittests
|
||||
# need this workaround for stdout to work
|
||||
run: |
|
||||
$proc = start .\bin\Release\minetest.exe --run-unittests -NoNewWindow -Wait -PassThru
|
||||
exit $proc.ExitCode
|
||||
continue-on-error: true # FIXME!!
|
||||
|
||||
- name: CPack
|
||||
run: |
|
||||
If ($env:TYPE -eq "installer")
|
||||
{
|
||||
cpack -G WIX -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
ElseIf($env:TYPE -eq "portable")
|
||||
{
|
||||
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
|
||||
env:
|
||||
TYPE: ${{matrix.type}}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
path: .\Package\
|
||||
if-no-files-found: error
|
||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -1,5 +1,8 @@
|
||||
## Editors and development environments
|
||||
*~
|
||||
.cmake
|
||||
CMakeUserPresets.json
|
||||
Testing/*
|
||||
*.swp
|
||||
*.bak*
|
||||
*.orig
|
||||
@@ -26,12 +29,20 @@ gtags.files
|
||||
# Codelite
|
||||
*.project
|
||||
# Visual Studio Code & plugins
|
||||
.vscode/
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
build/.cmake/
|
||||
# Fleet
|
||||
.fleet
|
||||
# Gradle
|
||||
.gradle
|
||||
# Clang
|
||||
.cache
|
||||
# AppImage
|
||||
*.AppImage
|
||||
*.zsync
|
||||
appimage-build
|
||||
AppDir
|
||||
|
||||
## Files related to Minetest development cycle
|
||||
/*.patch
|
||||
@@ -57,6 +68,7 @@ build/.cmake/
|
||||
/clientmods/*
|
||||
!/clientmods/preview/
|
||||
/client/mod_storage/
|
||||
/mod_data
|
||||
|
||||
## Configuration/log files
|
||||
minetest.conf
|
||||
@@ -85,11 +97,8 @@ cmake_install.cmake
|
||||
CMakeCache.txt
|
||||
CPackConfig.cmake
|
||||
CPackSourceConfig.cmake
|
||||
src/test_config.h
|
||||
src/cmake_config.h
|
||||
src/cmake_config_githash.h
|
||||
src/unittest/test_world/world.mt
|
||||
games/devtest/mods/testnodes/textures/testnodes_generated_*.png
|
||||
/locale/
|
||||
.directory
|
||||
*.cbp
|
||||
@@ -102,7 +111,10 @@ games/devtest/mods/testnodes/textures/testnodes_generated_*.png
|
||||
*.iml
|
||||
test_config.h
|
||||
cmake-build-debug/
|
||||
cmake-build-minsizerel/
|
||||
cmake-build-release/
|
||||
cmake-build-relwithdebinfo/
|
||||
cmake-build-default/
|
||||
cmake_config.h
|
||||
cmake_config_githash.h
|
||||
CMakeDoxy*
|
||||
@@ -114,7 +126,7 @@ compile_commands.json
|
||||
*.sln
|
||||
.vs/
|
||||
|
||||
# Optional user provided library folder
|
||||
# Old irrlichtmt. Still ignored to make bisecting easier.
|
||||
lib/irrlichtmt
|
||||
|
||||
# Generated mod storage database
|
||||
|
||||
119
.gitlab-ci.yml
119
.gitlab-ci.yml
@@ -3,131 +3,14 @@
|
||||
# https://gitlab.com/minetest/minetest
|
||||
# Pipelines URL: https://gitlab.com/minetest/minetest/pipelines
|
||||
|
||||
stages:
|
||||
- build
|
||||
- package
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
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 gettext git cmake libpng-dev libjpeg-dev libxi-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
|
||||
script:
|
||||
- git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
- mkdir build && cd build
|
||||
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE ..
|
||||
- make -j $(($(nproc) + 1))
|
||||
- make install
|
||||
artifacts:
|
||||
when: on_success
|
||||
expire_in: 1h
|
||||
paths:
|
||||
- artifact/*
|
||||
|
||||
##
|
||||
## Ubuntu (prerequisite for AppImage build)
|
||||
##
|
||||
|
||||
build:ubuntu-20.04:
|
||||
extends: .build_template
|
||||
image: ubuntu:focal
|
||||
|
||||
##
|
||||
## MinGW for Windows
|
||||
##
|
||||
|
||||
.generic_win_template:
|
||||
image: ubuntu:focal
|
||||
before_script:
|
||||
- apt-get update
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext
|
||||
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
- tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
.build_win_template:
|
||||
extends: .generic_win_template
|
||||
stage: build
|
||||
artifacts:
|
||||
expire_in: 90 day
|
||||
paths:
|
||||
- minetest-*-win*/*
|
||||
|
||||
build:win32:
|
||||
extends: .build_win_template
|
||||
script:
|
||||
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build
|
||||
- unzip -q build/build/*.zip
|
||||
variables:
|
||||
WIN_ARCH: "i686"
|
||||
|
||||
build:win64:
|
||||
extends: .build_win_template
|
||||
script:
|
||||
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build
|
||||
- unzip -q build/build/*.zip
|
||||
variables:
|
||||
WIN_ARCH: "x86_64"
|
||||
|
||||
##
|
||||
## Docker
|
||||
##
|
||||
|
||||
package:docker:
|
||||
stage: package
|
||||
image: docker:stable
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
|
||||
script:
|
||||
- ./util/ci/docker.sh
|
||||
|
||||
##
|
||||
## Gitlab Pages (Lua API documentation)
|
||||
##
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
image: python:3.8
|
||||
before_script:
|
||||
- pip install -U -r doc/mkdocs/requirements.txt
|
||||
script:
|
||||
- cd doc/mkdocs && ./build.sh
|
||||
- ./misc/make_redirects.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
only:
|
||||
- master
|
||||
|
||||
##
|
||||
## AppImage
|
||||
##
|
||||
|
||||
package:appimage-client:
|
||||
stage: package
|
||||
image: appimagecrafters/appimage-builder
|
||||
needs:
|
||||
- build:ubuntu-20.04
|
||||
before_script:
|
||||
- apt-get update
|
||||
- apt-get install -y git
|
||||
# Collect files
|
||||
- mkdir AppDir
|
||||
- cp -a artifact/minetest/usr/ AppDir/usr/
|
||||
- cp -a clientmods AppDir/usr/share/minetest
|
||||
- git clone $MINETEST_GAME_REPO AppDir/usr/share/minetest/games/minetest_game
|
||||
- rm -rf AppDir/usr/share/minetest/games/minetest_game/.git
|
||||
# Remove PrefersNonDefaultGPU property due to validation errors
|
||||
- sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/net.minetest.minetest.desktop
|
||||
script:
|
||||
- export VERSION=$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
|
||||
- appimage-builder --skip-test --recipe misc/AppImageBuilder.yml
|
||||
artifacts:
|
||||
expire_in: 90 day
|
||||
paths:
|
||||
- ./*.AppImage
|
||||
|
||||
@@ -17,6 +17,7 @@ read_globals = {
|
||||
"VoxelArea",
|
||||
"profiler",
|
||||
"Settings",
|
||||
"PerlinNoise", "PerlinNoiseMap",
|
||||
|
||||
string = {fields = {"split", "trim"}},
|
||||
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
|
||||
|
||||
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
184
CMakeLists.txt
184
CMakeLists.txt
@@ -1,25 +1,18 @@
|
||||
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 14)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(GCC_MINIMUM_VERSION "5.1")
|
||||
set(CLANG_MINIMUM_VERSION "3.5")
|
||||
set(GCC_MINIMUM_VERSION "7.5")
|
||||
set(CLANG_MINIMUM_VERSION "7.0.1")
|
||||
|
||||
# You should not need to edit these manually, use util/bump_version.sh
|
||||
set(VERSION_MAJOR 5)
|
||||
set(VERSION_MINOR 7)
|
||||
set(VERSION_PATCH 0)
|
||||
set(VERSION_MINOR 9)
|
||||
set(VERSION_PATCH 1)
|
||||
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
|
||||
|
||||
# Change to false for releases
|
||||
@@ -37,10 +30,32 @@ 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()
|
||||
#### LTO testing list ####
|
||||
# - Linux: seems to work always
|
||||
# - win32/msvc: works
|
||||
# - win32/gcc: fails to link
|
||||
# - win32/clang: works
|
||||
# - macOS on x86: seems to be fine
|
||||
# - macOS on ARM: crashes, see <https://github.com/minetest/minetest/issues/14397>
|
||||
# Note: since CMake has no easy architecture detection disabling for Mac entirely
|
||||
#### ####
|
||||
if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE)
|
||||
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)
|
||||
@@ -48,11 +63,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")
|
||||
set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
|
||||
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")
|
||||
|
||||
@@ -67,27 +84,17 @@ set(ENABLE_UPDATE_CHECKER (NOT ${DEVELOPMENT_BUILD}) CACHE BOOL
|
||||
# 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 "")
|
||||
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
|
||||
# the only one, but it is the one where IrrlichtMt is supposed to export
|
||||
# IrrlichtMtConfig.cmake
|
||||
message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
|
||||
endif()
|
||||
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
|
||||
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
|
||||
if(TRUE)
|
||||
message(STATUS "Using imported IrrlichtMt at subdirectory 'irr'")
|
||||
if(BUILD_CLIENT)
|
||||
# tell IrrlichtMt to create a static library
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared library" FORCE)
|
||||
add_subdirectory(lib/irrlichtmt EXCLUDE_FROM_ALL)
|
||||
unset(BUILD_SHARED_LIBS CACHE)
|
||||
add_subdirectory(irr EXCLUDE_FROM_ALL)
|
||||
|
||||
if(NOT TARGET IrrlichtMt)
|
||||
message(FATAL_ERROR "IrrlichtMt project is missing a CMake target?!")
|
||||
@@ -95,45 +102,22 @@ elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
|
||||
else()
|
||||
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
|
||||
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt/include")
|
||||
endif()
|
||||
else()
|
||||
find_package(IrrlichtMt QUIET)
|
||||
if(NOT TARGET IrrlichtMt::IrrlichtMt)
|
||||
string(CONCAT explanation_msg
|
||||
"The Minetest team has forked Irrlicht to make their own customizations. "
|
||||
"It can be found here: https://github.com/minetest/irrlicht\n"
|
||||
"For example use: git clone --depth=1 https://github.com/minetest/irrlicht lib/irrlichtmt\n")
|
||||
if(BUILD_CLIENT)
|
||||
message(FATAL_ERROR "IrrlichtMt is required to build the client, but it was not found.\n${explanation_msg}")
|
||||
endif()
|
||||
|
||||
include(MinetestFindIrrlichtHeaders)
|
||||
if(NOT IRRLICHT_INCLUDE_DIR)
|
||||
message(FATAL_ERROR "IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
|
||||
endif()
|
||||
message(STATUS "Found IrrlichtMt headers: ${IRRLICHT_INCLUDE_DIR}")
|
||||
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
|
||||
# Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
|
||||
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}")
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/irr/include")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
|
||||
# retrieve version somehow
|
||||
if(NOT IrrlichtMt_VERSION)
|
||||
get_target_property(IrrlichtMt_VERSION IrrlichtMt VERSION)
|
||||
endif()
|
||||
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
|
||||
|
||||
set(TARGET_VER_S 1.9.0mt10)
|
||||
string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S})
|
||||
if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER})
|
||||
message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build")
|
||||
elseif(NOT DEVELOPMENT_BUILD AND IrrlichtMt_VERSION VERSION_GREATER ${TARGET_VER})
|
||||
message(FATAL_ERROR "IrrlichtMt ${TARGET_VER_S} is required to build")
|
||||
if (ENABLE_LTO OR CMAKE_INTERPROCEDURAL_OPTIMIZATION)
|
||||
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()
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE)
|
||||
message(STATUS "LTO/IPO was requested but is not supported by the compiler: ${lto_output}")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "LTO/IPO is not enabled")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
@@ -172,7 +156,7 @@ elseif(UNIX) # Linux, BSD etc
|
||||
set(EXAMPLE_CONF_DIR ".")
|
||||
set(MANDIR "unix/man")
|
||||
set(XDG_APPS_DIR "unix/applications")
|
||||
set(APPDATADIR "unix/metainfo")
|
||||
set(METAINFODIR "unix/metainfo")
|
||||
set(ICONDIR "unix/icons")
|
||||
set(LOCALEDIR "locale")
|
||||
else()
|
||||
@@ -183,7 +167,7 @@ elseif(UNIX) # Linux, BSD etc
|
||||
set(MANDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}")
|
||||
set(EXAMPLE_CONF_DIR ${DOCDIR})
|
||||
set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications")
|
||||
set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||
set(METAINFODIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||
set(ICONDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons")
|
||||
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}")
|
||||
endif()
|
||||
@@ -247,9 +231,6 @@ 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 )
|
||||
|
||||
set(INSTALL_DEVTEST FALSE CACHE BOOL "Install Development Test")
|
||||
|
||||
if(INSTALL_DEVTEST)
|
||||
@@ -267,17 +248,17 @@ 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)
|
||||
install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6")
|
||||
install(FILES "misc/net.minetest.minetest.desktop" DESTINATION "${XDG_APPS_DIR}")
|
||||
install(FILES "misc/net.minetest.minetest.appdata.xml" DESTINATION "${APPDATADIR}")
|
||||
install(FILES "misc/net.minetest.minetest.metainfo.xml" DESTINATION "${METAINFODIR}")
|
||||
install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
|
||||
install(FILES "misc/minetest-xorg-icon-128.png"
|
||||
DESTINATION "${ICONDIR}/hicolor/128x128/apps"
|
||||
@@ -296,8 +277,9 @@ find_package(Lua REQUIRED)
|
||||
if(NOT USE_LUAJIT)
|
||||
add_subdirectory(lib/bitop)
|
||||
endif()
|
||||
add_subdirectory(lib/sha256)
|
||||
|
||||
if(BUILD_BENCHMARKS)
|
||||
if(BUILD_UNITTESTS OR BUILD_BENCHMARKS)
|
||||
add_subdirectory(lib/catch2)
|
||||
endif()
|
||||
|
||||
@@ -321,16 +303,6 @@ 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_group(Subgames
|
||||
DESCRIPTION "Games for the Minetest engine."
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
# Include all dynamically linked runtime libraries such as MSVCRxxx.dll
|
||||
include(InstallRequiredSystemLibraries)
|
||||
@@ -384,13 +356,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()
|
||||
|
||||
41
CMakePresets.json
Normal file
41
CMakePresets.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 12
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"displayName": "Debug",
|
||||
"description": "Debug preset with debug symbols and no optimizations",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Release",
|
||||
"displayName": "Release",
|
||||
"description": "Release preset with optimizations and no debug symbols",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "RelWithDebInfo",
|
||||
"displayName": "RelWithDebInfo",
|
||||
"description": "Release with debug symbols",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MinSizeRel",
|
||||
"displayName": "MinSizeRel",
|
||||
"description": "Release with minimal code size",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
43
Dockerfile
43
Dockerfile
@@ -1,9 +1,6 @@
|
||||
ARG DOCKER_IMAGE=alpine:3.16
|
||||
FROM $DOCKER_IMAGE AS builder
|
||||
ARG DOCKER_IMAGE=alpine:3.19
|
||||
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 \
|
||||
@@ -11,7 +8,7 @@ RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \
|
||||
gmp-dev jsoncpp-dev ninja ca-certificates
|
||||
|
||||
WORKDIR /usr/src/
|
||||
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
|
||||
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp && \
|
||||
cd prometheus-cpp && \
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
@@ -21,7 +18,7 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
|
||||
cmake --build build && \
|
||||
cmake --install build && \
|
||||
cd /usr/src/ && \
|
||||
git clone --recursive https://github.com/libspatialindex/libspatialindex -b ${SPATIALINDEX_VERSION} && \
|
||||
git clone --recursive https://github.com/libspatialindex/libspatialindex && \
|
||||
cd libspatialindex && \
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local && \
|
||||
@@ -30,10 +27,10 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
|
||||
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
|
||||
make amalg && make install && \
|
||||
cd /usr/src/
|
||||
|
||||
FROM dev as builder
|
||||
|
||||
COPY .git /usr/src/minetest/.git
|
||||
COPY CMakeLists.txt /usr/src/minetest/CMakeLists.txt
|
||||
@@ -47,23 +44,21 @@ COPY lib /usr/src/minetest/lib
|
||||
COPY misc /usr/src/minetest/misc
|
||||
COPY po /usr/src/minetest/po
|
||||
COPY src /usr/src/minetest/src
|
||||
COPY irr /usr/src/minetest/irr
|
||||
COPY textures /usr/src/minetest/textures
|
||||
|
||||
WORKDIR /usr/src/minetest
|
||||
RUN git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
|
||||
rm -fr ./games/minetest_game/.git && \
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SERVER=TRUE \
|
||||
-DENABLE_PROMETHEUS=TRUE \
|
||||
-DBUILD_UNITTESTS=FALSE \
|
||||
-DBUILD_CLIENT=FALSE \
|
||||
-GNinja && \
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
RUN cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SERVER=TRUE \
|
||||
-DENABLE_PROMETHEUS=TRUE \
|
||||
-DBUILD_UNITTESTS=FALSE -DBUILD_BENCHMARKS=FALSE \
|
||||
-DBUILD_CLIENT=FALSE \
|
||||
-GNinja && \
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
ARG DOCKER_IMAGE=alpine:3.16
|
||||
FROM $DOCKER_IMAGE AS runtime
|
||||
|
||||
RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \
|
||||
|
||||
12
LICENSE.txt
12
LICENSE.txt
@@ -61,7 +61,11 @@ Zughy:
|
||||
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
|
||||
@@ -71,15 +75,23 @@ erlehmann, Warr1024, rollerozxa:
|
||||
|
||||
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
|
||||
-------------------------------
|
||||
|
||||
|
||||
359
README.md
359
README.md
@@ -10,12 +10,6 @@ Minetest is a free open-source voxel game engine with easy modding and game crea
|
||||
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
|
||||
------------------
|
||||
|
||||
@@ -31,11 +25,11 @@ Table of Contents
|
||||
|
||||
Further documentation
|
||||
----------------------
|
||||
- Website: https://minetest.net/
|
||||
- Website: https://www.minetest.net/
|
||||
- Wiki: https://wiki.minetest.net/
|
||||
- Developer wiki: https://dev.minetest.net/
|
||||
- Forum: https://forum.minetest.net/
|
||||
- GitHub: https://github.com/minetest/minetest/
|
||||
- [Developer documentation](doc/developing/)
|
||||
- [doc/](doc/) directory of source distribution
|
||||
|
||||
Default controls
|
||||
@@ -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 |
|
||||
@@ -124,353 +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 libxi-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
|
||||
|
||||
For Fedora users:
|
||||
|
||||
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libpng-devel libjpeg-devel libvorbis-devel libXi-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
|
||||
|
||||
For Arch users:
|
||||
|
||||
sudo pacman -S base-devel libcurl-gnutls cmake libxi libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
|
||||
|
||||
For Alpine users:
|
||||
|
||||
sudo apk add build-base cmake libpng-dev jpeg-dev libxi-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev
|
||||
|
||||
#### Download
|
||||
|
||||
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
|
||||
|
||||
For Arch users:
|
||||
|
||||
sudo pacman -S git
|
||||
|
||||
For Alpine users:
|
||||
|
||||
sudo apk add git
|
||||
|
||||
Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
|
||||
|
||||
git clone --depth 1 https://github.com/minetest/minetest.git
|
||||
cd minetest
|
||||
|
||||
Download Minetest Game (otherwise only the "Development Test" game is available) using Git:
|
||||
|
||||
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 compile IrrlichtMt, just the headers suffice.
|
||||
- In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlichtmt/include`.
|
||||
|
||||
- Minetest will use the IrrlichtMt package that is found first, given by the following order:
|
||||
1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
|
||||
2. `${PROJECT_SOURCE_DIR}/lib/irrlichtmt` (if existent)
|
||||
3. Installation of IrrlichtMt in the system-specific library paths
|
||||
4. For server builds with disabled `BUILD_CLIENT` variable, the headers from `IRRLICHT_INCLUDE_DIR` will be used.
|
||||
- NOTE: Changing the IrrlichtMt build directory (includes system installs) requires regenerating the CMake cache (`rm CMakeCache.txt`)
|
||||
|
||||
### 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
|
||||
BUILD_BENCHMARKS=FALSE - Build benchmark 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 - Enable extra support code for OpenGL ES (requires support by IrrlichtMt)
|
||||
ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend
|
||||
ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
|
||||
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
|
||||
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
|
||||
RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory)
|
||||
ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default
|
||||
INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest
|
||||
USE_GPROF=FALSE - Enable profiling using GProf
|
||||
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
|
||||
ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt)
|
||||
|
||||
Library specific options:
|
||||
|
||||
CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
|
||||
CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
|
||||
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
|
||||
EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
|
||||
EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
|
||||
EXTRA_DLL - Only on Windows; optional paths to additional DLLs that should be packaged
|
||||
FREETYPE_INCLUDE_DIR_freetype2 - Directory that contains files such as ftimage.h
|
||||
FREETYPE_INCLUDE_DIR_ft2build - Directory that contains ft2build.h
|
||||
FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib
|
||||
FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll
|
||||
GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs
|
||||
GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains libintl.h
|
||||
GETTEXT_LIBRARY - Optional/platform-dependent with gettext; path to libintl.so/libintl.dll.a
|
||||
GETTEXT_MSGFMT - Only when building with gettext; path to msgfmt/msgfmt.exe
|
||||
ICONV_LIBRARY - Optional/platform-dependent; path to libiconv.so/libiconv.dylib
|
||||
IRRLICHT_DLL - Only on Windows; path to IrrlichtMt.dll
|
||||
IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h (usable for server build only)
|
||||
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.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
|
||||
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
|
||||
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 build
|
||||
cd build
|
||||
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
|
||||
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
```
|
||||
open ./build/macos/minetest.app
|
||||
```
|
||||
- [Compiling on GNU/Linux](doc/compiling/linux.md)
|
||||
- [Compiling on Windows](doc/compiling/windows.md)
|
||||
- [Compiling on MacOS](doc/compiling/macos.md)
|
||||
|
||||
Docker
|
||||
------
|
||||
We provide Minetest server Docker images using the GitLab mirror registry.
|
||||
|
||||
Images are built on each commit and available using the following tag scheme:
|
||||
|
||||
* `registry.gitlab.com/minetest/minetest/server:latest` (latest build)
|
||||
* `registry.gitlab.com/minetest/minetest/server:<branch/tag>` (current branch or current tag)
|
||||
* `registry.gitlab.com/minetest/minetest/server:<commit-id>` (current commit id)
|
||||
|
||||
If you want to test it on a Docker server you can easily run:
|
||||
|
||||
sudo docker run registry.gitlab.com/minetest/minetest/server:<docker tag>
|
||||
|
||||
If you want to use it in a production environment you should use volumes bound to the Docker host
|
||||
to persist data and modify the configuration:
|
||||
|
||||
sudo docker create -v /home/minetest/data/:/var/lib/minetest/ -v /home/minetest/conf/:/etc/minetest/ registry.gitlab.com/minetest/minetest/server:master
|
||||
|
||||
Data will be written to `/home/minetest/data` on the host, and configuration will be read from `/home/minetest/conf/minetest.conf`.
|
||||
|
||||
**Note:** If you don't understand the previous commands please read the official Docker documentation before use.
|
||||
|
||||
You can also host your Minetest server inside a Kubernetes cluster. See our example implementation in [`misc/kubernetes.yml`](misc/kubernetes.yml).
|
||||
|
||||
- [Developing minetestserver with Docker](doc/developing/docker.md)
|
||||
- [Running a server with Docker](doc/docker_server.md)
|
||||
|
||||
Version scheme
|
||||
--------------
|
||||
|
||||
18
android/SDL_Java_RMB_fix.patch
Normal file
18
android/SDL_Java_RMB_fix.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
index fd5a056e3..83e3cf657 100644
|
||||
--- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
+++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
@@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
}
|
||||
}
|
||||
|
||||
- if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
|
||||
+ if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ||
|
||||
+ /*
|
||||
+ * CUSTOM ADDITION FOR MINETEST
|
||||
+ * should be upstreamed
|
||||
+ */
|
||||
+ (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) {
|
||||
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
|
||||
// they are ignored here because sending them as mouse input to SDL is messy
|
||||
if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
|
||||
@@ -1,15 +1,18 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion '33.0.2'
|
||||
ndkVersion "$ndk_version"
|
||||
defaultConfig {
|
||||
applicationId 'net.minetest.minetest'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
compileSdk 34
|
||||
targetSdkVersion 34
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||
versionCode project.versionCode
|
||||
versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
// load properties
|
||||
@@ -49,12 +52,15 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
namespace 'net.minetest.minetest'
|
||||
}
|
||||
|
||||
task prepareAssets() {
|
||||
def assetsFolder = "build/assets"
|
||||
def projRoot = rootDir.parent
|
||||
def gameToCopy = "minetest_game"
|
||||
|
||||
// See issue #4638
|
||||
def unsupportedLanguages = new File("${projRoot}/src/unsupported_language_list.txt").text.readLines()
|
||||
|
||||
doFirst {
|
||||
logger.lifecycle('Preparing assets at {}', assetsFolder)
|
||||
@@ -73,20 +79,19 @@ task prepareAssets() {
|
||||
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
|
||||
}
|
||||
copy {
|
||||
from "../native/deps/armeabi-v7a/Irrlicht/Shaders" into "${assetsFolder}/client/shaders/Irrlicht"
|
||||
from "${projRoot}/irr/media/Shaders" into "${assetsFolder}/client/shaders/Irrlicht"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/textures" into "${assetsFolder}/textures"
|
||||
from "${projRoot}/textures/base/pack" into "${assetsFolder}/textures/base/pack"
|
||||
}
|
||||
|
||||
// compile translations
|
||||
fileTree("${projRoot}/po").include("**/*.po").forEach { poFile ->
|
||||
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 {
|
||||
@@ -107,16 +112,8 @@ task prepareAssets() {
|
||||
preBuild.dependsOn zipAssets
|
||||
prepareAssets.dependsOn ':native:getDeps'
|
||||
|
||||
// Map for the version code that gives each ABI a value.
|
||||
import com.android.build.OutputFile
|
||||
|
||||
def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1]
|
||||
android.applicationVariants.all { variant ->
|
||||
variant.outputs.each {
|
||||
output ->
|
||||
def abiName = output.getFilter(OutputFile.ABI)
|
||||
output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode
|
||||
}
|
||||
clean {
|
||||
delete new File("src/main/assets", "Minetest.zip")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="net.minetest.minetest"
|
||||
android:installLocation="auto">
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-feature android:glEsVersion="0x00020000" />
|
||||
|
||||
<!--
|
||||
`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
|
||||
-->
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/label"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:resizeableActivity="false"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
@@ -53,7 +45,7 @@
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="Minetest" />
|
||||
android:value="minetest" />
|
||||
</activity>
|
||||
|
||||
<service
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
package net.minetest.minetest;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
@@ -32,88 +33,85 @@ 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 android.widget.Toast;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
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");
|
||||
public class GameActivity extends SDLActivity {
|
||||
@Override
|
||||
protected String getMainSharedObject() {
|
||||
return getContext().getApplicationInfo().nativeLibraryDir + "/libminetest.so";
|
||||
}
|
||||
|
||||
private int messageReturnCode = -1;
|
||||
@Override
|
||||
protected String getMainFunction() {
|
||||
return "SDL_Main";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[] {
|
||||
"minetest"
|
||||
};
|
||||
}
|
||||
|
||||
// Prevent SDL from changing orientation settings since we already set the
|
||||
// correct orientation in our AndroidManifest.xml
|
||||
@Override
|
||||
public void setOrientationBis(int w, int h, boolean resizable, String hint) {}
|
||||
|
||||
enum DialogType { TEXT_INPUT, SELECTION_INPUT }
|
||||
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
|
||||
|
||||
private DialogType lastDialogType = DialogType.TEXT_INPUT;
|
||||
private DialogState inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
private String messageReturnValue = "";
|
||||
private int selectionReturnValue = 0;
|
||||
|
||||
public static native void putMessageBoxResult(String text);
|
||||
private native void saveSettings();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
public void showTextInputDialog(String hint, String current, int editType) {
|
||||
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus)
|
||||
makeFullScreen();
|
||||
public void showSelectionInputDialog(String[] optionList, int selectedIdx) {
|
||||
runOnUiThread(() -> showSelectionInputDialogUI(optionList, selectedIdx));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
makeFullScreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Ignore the back press so Minetest can handle it
|
||||
}
|
||||
|
||||
public void showDialog(String acceptButton, String hint, String current, int editType) {
|
||||
runOnUiThread(() -> showDialogUI(hint, current, editType));
|
||||
}
|
||||
|
||||
private void showDialogUI(String hint, String current, int editType) {
|
||||
private void showTextInputDialogUI(String hint, String current, int editType) {
|
||||
lastDialogType = DialogType.TEXT_INPUT;
|
||||
inputDialogState = DialogState.DIALOG_SHOWN;
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
LinearLayout container = new LinearLayout(this);
|
||||
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);
|
||||
@@ -122,12 +120,13 @@ 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) {
|
||||
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
||||
messageReturnCode = 0;
|
||||
inputDialogState = DialogState.DIALOG_INPUTTED;
|
||||
messageReturnValue = editText.getText().toString();
|
||||
alertDialog.dismiss();
|
||||
return true;
|
||||
@@ -141,28 +140,55 @@ public class GameActivity extends NativeActivity {
|
||||
doneButton.setText(R.string.ime_dialog_done);
|
||||
doneButton.setOnClickListener((view -> {
|
||||
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
||||
messageReturnCode = 0;
|
||||
inputDialogState = DialogState.DIALOG_INPUTTED;
|
||||
messageReturnValue = editText.getText().toString();
|
||||
alertDialog.dismiss();
|
||||
}));
|
||||
}
|
||||
alertDialog.show();
|
||||
alertDialog.setOnCancelListener(dialog -> {
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
messageReturnValue = current;
|
||||
messageReturnCode = -1;
|
||||
});
|
||||
alertDialog.show();
|
||||
editText.requestFocusTryShow();
|
||||
}
|
||||
|
||||
public int getDialogState() {
|
||||
return messageReturnCode;
|
||||
public void showSelectionInputDialogUI(String[] optionList, int selectedIdx) {
|
||||
lastDialogType = DialogType.SELECTION_INPUT;
|
||||
inputDialogState = DialogState.DIALOG_SHOWN;
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setSingleChoiceItems(optionList, selectedIdx, (dialog, selection) -> {
|
||||
inputDialogState = DialogState.DIALOG_INPUTTED;
|
||||
selectionReturnValue = selection;
|
||||
dialog.dismiss();
|
||||
});
|
||||
builder.setOnCancelListener(dialog -> {
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
selectionReturnValue = selectedIdx;
|
||||
});
|
||||
AlertDialog alertDialog = builder.create();
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
public String getDialogValue() {
|
||||
messageReturnCode = -1;
|
||||
public int getLastDialogType() {
|
||||
return lastDialogType.ordinal();
|
||||
}
|
||||
|
||||
public int getInputDialogState() {
|
||||
return inputDialogState.ordinal();
|
||||
}
|
||||
|
||||
public String getDialogMessage() {
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
return messageReturnValue;
|
||||
}
|
||||
|
||||
public int getDialogSelection() {
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
return selectionReturnValue;
|
||||
}
|
||||
|
||||
public float getDensity() {
|
||||
return getResources().getDisplayMetrics().density;
|
||||
}
|
||||
@@ -177,7 +203,11 @@ public class GameActivity extends NativeActivity {
|
||||
|
||||
public void openURI(String uri) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
|
||||
startActivity(browserIntent);
|
||||
try {
|
||||
startActivity(browserIntent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
runOnUiThread(() -> Toast.makeText(this, R.string.no_web_browser, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
||||
|
||||
public String getUserDataPath() {
|
||||
@@ -205,4 +235,32 @@ public class GameActivity extends NativeActivity {
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean hasPhysicalKeyboard() {
|
||||
return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,38 +20,31 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
package net.minetest.minetest;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
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";
|
||||
|
||||
@@ -91,67 +84,30 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
IntentFilter filter = new IntentFilter(ACTION_UPDATE);
|
||||
registerReceiver(myReceiver, filter);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(myReceiver, filter, RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
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
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, 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);
|
||||
@@ -176,6 +132,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,13 +22,11 @@ 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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -88,7 +88,6 @@ public class UnzipService extends IntentService {
|
||||
}
|
||||
}
|
||||
|
||||
migrate(notificationBuilder, userDataDirectory);
|
||||
unzip(notificationBuilder, zipFile, userDataDirectory);
|
||||
} catch (IOException e) {
|
||||
isSuccess = false;
|
||||
@@ -101,28 +100,13 @@ public class UnzipService extends IntentService {
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@@ -137,9 +121,9 @@ public class UnzipService extends IntentService {
|
||||
PendingIntent intent = PendingIntent.getActivity(this, 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);
|
||||
@@ -182,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");
|
||||
}
|
||||
@@ -200,46 +184,9 @@ public class UnzipService extends IntentService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates user data from deprecated external storage to app scoped storage
|
||||
*/
|
||||
private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return;
|
||||
}
|
||||
|
||||
File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest");
|
||||
if (!oldLocation.isDirectory())
|
||||
return;
|
||||
|
||||
publishProgress(notificationBuilder, R.string.migrating, 0);
|
||||
if (!newLocation.mkdir()) {
|
||||
Log.e("UnzipService", "New installation folder cannot be made");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!recursivelyDeleteDirectory(oldLocation)) {
|
||||
Log.w("UnzipService", "Old installation files cannot be deleted successfully");
|
||||
}
|
||||
}
|
||||
|
||||
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.setPackage(getPackageName());
|
||||
intentUpdate.putExtra(ACTION_PROGRESS, progress);
|
||||
intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message);
|
||||
if (!isSuccess)
|
||||
|
||||
@@ -38,7 +38,6 @@ public class Utils {
|
||||
public static boolean isInstallValid(@NonNull Context context) {
|
||||
File userDataDirectory = getUserDataDirectory(context);
|
||||
return userDataDirectory.isDirectory() &&
|
||||
new File(userDataDirectory, "games").isDirectory() &&
|
||||
new File(userDataDirectory, "builtin").isDirectory() &&
|
||||
new File(userDataDirectory, "client").isDirectory() &&
|
||||
new File(userDataDirectory, "textures").isDirectory();
|
||||
|
||||
22
android/app/src/main/java/org/libsdl/app/HIDDevice.java
Normal file
22
android/app/src/main/java/org/libsdl/app/HIDDevice.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
interface HIDDevice
|
||||
{
|
||||
public int getId();
|
||||
public int getVendorId();
|
||||
public int getProductId();
|
||||
public String getSerialNumber();
|
||||
public int getVersion();
|
||||
public String getManufacturerName();
|
||||
public String getProductName();
|
||||
public UsbDevice getDevice();
|
||||
public boolean open();
|
||||
public int sendFeatureReport(byte[] report);
|
||||
public int sendOutputReport(byte[] report);
|
||||
public boolean getFeatureReport(byte[] report);
|
||||
public void setFrozen(boolean frozen);
|
||||
public void close();
|
||||
public void shutdown();
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.os.*;
|
||||
|
||||
//import com.android.internal.util.HexDump;
|
||||
|
||||
import java.lang.Runnable;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
private HIDDeviceManager mManager;
|
||||
private BluetoothDevice mDevice;
|
||||
private int mDeviceId;
|
||||
private BluetoothGatt mGatt;
|
||||
private boolean mIsRegistered = false;
|
||||
private boolean mIsConnected = false;
|
||||
private boolean mIsChromebook = false;
|
||||
private boolean mIsReconnecting = false;
|
||||
private boolean mFrozen = false;
|
||||
private LinkedList<GattOperation> mOperations;
|
||||
GattOperation mCurrentOperation = null;
|
||||
private Handler mHandler;
|
||||
|
||||
private static final int TRANSPORT_AUTO = 0;
|
||||
private static final int TRANSPORT_BREDR = 1;
|
||||
private static final int TRANSPORT_LE = 2;
|
||||
|
||||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||
|
||||
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||
|
||||
static class GattOperation {
|
||||
private enum Operation {
|
||||
CHR_READ,
|
||||
CHR_WRITE,
|
||||
ENABLE_NOTIFICATION
|
||||
}
|
||||
|
||||
Operation mOp;
|
||||
UUID mUuid;
|
||||
byte[] mValue;
|
||||
BluetoothGatt mGatt;
|
||||
boolean mResult = true;
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
}
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// This is executed in main thread
|
||||
BluetoothGattCharacteristic chr;
|
||||
|
||||
switch (mOp) {
|
||||
case CHR_READ:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||
if (!mGatt.readCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case CHR_WRITE:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||
chr.setValue(mValue);
|
||||
if (!mGatt.writeCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case ENABLE_NOTIFICATION:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
||||
if (chr != null) {
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
int properties = chr.getProperties();
|
||||
byte[] value;
|
||||
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
||||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
||||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||
} else {
|
||||
Log.e(TAG, "Unable to start notifications on input characteristic");
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mGatt.setCharacteristicNotification(chr, true);
|
||||
cccd.setValue(value);
|
||||
if (!mGatt.writeDescriptor(cccd)) {
|
||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
mResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean finish() {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||
if (valveService == null)
|
||||
return null;
|
||||
return valveService.getCharacteristic(uuid);
|
||||
}
|
||||
|
||||
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
||||
}
|
||||
|
||||
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
||||
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
||||
}
|
||||
|
||||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||
mManager = manager;
|
||||
mDevice = device;
|
||||
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mIsRegistered = false;
|
||||
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
mOperations = new LinkedList<GattOperation>();
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
mGatt = connectGatt();
|
||||
// final HIDDeviceBLESteamController finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.checkConnectionForChromebookIssue();
|
||||
// }
|
||||
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("SteamController.%s", mDevice.getAddress());
|
||||
}
|
||||
|
||||
public BluetoothGatt getGatt() {
|
||||
return mGatt;
|
||||
}
|
||||
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
} else {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
}
|
||||
|
||||
private BluetoothGatt connectGatt() {
|
||||
return connectGatt(false);
|
||||
}
|
||||
|
||||
protected int getConnectionState() {
|
||||
|
||||
Context context = mManager.getContext();
|
||||
if (context == null) {
|
||||
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (btManager == null) {
|
||||
// This device doesn't support Bluetooth. We should never be here, because how did
|
||||
// we instantiate a device to start with?
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
|
||||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void checkConnectionForChromebookIssue() {
|
||||
if (!mIsChromebook) {
|
||||
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
||||
// over and over.
|
||||
return;
|
||||
}
|
||||
|
||||
int connectionState = getConnectionState();
|
||||
|
||||
switch (connectionState) {
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
if (!mIsConnected) {
|
||||
// We are in the Bad Chromebook Place. We can force a disconnect
|
||||
// to try to recover.
|
||||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
else if (!isRegistered()) {
|
||||
if (mGatt.getServices().size() > 0) {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
||||
probeService(this);
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
||||
break;
|
||||
}
|
||||
|
||||
final HIDDeviceBLESteamController finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.checkConnectionForChromebookIssue();
|
||||
}
|
||||
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
private boolean isRegistered() {
|
||||
return mIsRegistered;
|
||||
}
|
||||
|
||||
private void setRegistered() {
|
||||
mIsRegistered = true;
|
||||
}
|
||||
|
||||
private boolean probeService(HIDDeviceBLESteamController controller) {
|
||||
|
||||
if (isRegistered()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mIsConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.v(TAG, "probeService controller=" + controller);
|
||||
|
||||
for (BluetoothGattService service : mGatt.getServices()) {
|
||||
if (service.getUuid().equals(steamControllerService)) {
|
||||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||
|
||||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
Log.v(TAG, "Found input characteristic");
|
||||
// Start notifications
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
enableNotification(chr.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
||||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||
mIsConnected = false;
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void finishCurrentGattOperation() {
|
||||
GattOperation op = null;
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null) {
|
||||
op = mCurrentOperation;
|
||||
mCurrentOperation = null;
|
||||
}
|
||||
}
|
||||
if (op != null) {
|
||||
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
||||
|
||||
// Our operation failed, let's add it back to the beginning of our queue.
|
||||
if (!result) {
|
||||
mOperations.addFirst(op);
|
||||
}
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void executeNextGattOperation() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null)
|
||||
return;
|
||||
|
||||
if (mOperations.isEmpty())
|
||||
return;
|
||||
|
||||
mCurrentOperation = mOperations.removeFirst();
|
||||
}
|
||||
|
||||
// Run in main thread
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation == null) {
|
||||
Log.e(TAG, "Current operation null in executor?");
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentOperation.run();
|
||||
// now wait for the GATT callback and when it comes, finish this operation
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void queueGattOperation(GattOperation op) {
|
||||
synchronized (mOperations) {
|
||||
mOperations.add(op);
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void enableNotification(UUID chrUuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void writeCharacteristic(UUID uuid, byte[] value) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void readCharacteristic(UUID uuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////// BluetoothGattCallback overridden methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
||||
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
||||
mIsReconnecting = false;
|
||||
if (newState == 2) {
|
||||
mIsConnected = true;
|
||||
// Run directly, without GattOperation
|
||||
if (!isRegistered()) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mGatt.discoverServices();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (newState == 0) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
||||
}
|
||||
|
||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
||||
if (status == 0) {
|
||||
if (gatt.getServices().size() == 0) {
|
||||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||
mIsReconnecting = true;
|
||||
mIsConnected = false;
|
||||
gatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
else {
|
||||
probeService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
||||
// Only register controller with the native side once it has been fully configured
|
||||
if (!isRegistered()) {
|
||||
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
||||
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
||||
setRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
// Enable this for verbose logging of controller input reports
|
||||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||
|
||||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||
}
|
||||
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
boolean hasWrittenInputDescriptor = true;
|
||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||
if (reportChr != null) {
|
||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||
reportChr.setValue(enterValveMode);
|
||||
gatt.writeCharacteristic(reportChr);
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
||||
}
|
||||
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
||||
}
|
||||
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
//Log.v(TAG, "onMtuChanged status=" + status);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////// Public API
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
// Valve Corporation
|
||||
final int VALVE_USB_VID = 0x28DE;
|
||||
return VALVE_USB_VID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
||||
final int D0G_BLE2_PID = 0x1106;
|
||||
return D0G_BLE2_PID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
// This will be read later via feature report by Steam
|
||||
return "12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
return "Valve Corporation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
return "Steam Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We need to skip the first byte, as that doesn't go over the air
|
||||
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
||||
writeCharacteristic(reportCharacteristic, actual_report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
||||
writeCharacteristic(reportCharacteristic, report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "getFeatureReport");
|
||||
readCharacteristic(reportCharacteristic);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
|
||||
BluetoothGatt g = mGatt;
|
||||
if (g != null) {
|
||||
g.disconnect();
|
||||
g.close();
|
||||
mGatt = null;
|
||||
}
|
||||
mManager = null;
|
||||
mIsRegistered = false;
|
||||
mIsConnected = false;
|
||||
mOperations.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
691
android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
Normal file
691
android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
Normal file
@@ -0,0 +1,691 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class HIDDeviceManager {
|
||||
private static final String TAG = "hidapi";
|
||||
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
||||
|
||||
private static HIDDeviceManager sManager;
|
||||
private static int sManagerRefCount = 0;
|
||||
|
||||
public static HIDDeviceManager acquire(Context context) {
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager = new HIDDeviceManager(context);
|
||||
}
|
||||
++sManagerRefCount;
|
||||
return sManager;
|
||||
}
|
||||
|
||||
public static void release(HIDDeviceManager manager) {
|
||||
if (manager == sManager) {
|
||||
--sManagerRefCount;
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager.close();
|
||||
sManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
||||
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
||||
private int mNextDeviceId = 0;
|
||||
private SharedPreferences mSharedPreferences = null;
|
||||
private boolean mIsChromebook = false;
|
||||
private UsbManager mUsbManager;
|
||||
private Handler mHandler;
|
||||
private BluetoothManager mBluetoothManager;
|
||||
private List<BluetoothDevice> mLastBluetoothDevices;
|
||||
|
||||
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceDetached(usbDevice);
|
||||
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device connected: " + device);
|
||||
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Bluetooth device was disconnected, remove from controller manager (if any)
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
||||
|
||||
disconnectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private HIDDeviceManager(final Context context) {
|
||||
mContext = context;
|
||||
|
||||
HIDDeviceRegisterCallback();
|
||||
|
||||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
|
||||
// if (shouldClear) {
|
||||
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
// spedit.clear();
|
||||
// spedit.commit();
|
||||
// }
|
||||
// else
|
||||
{
|
||||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||
}
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public int getDeviceIDForIdentifier(String identifier) {
|
||||
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
|
||||
int result = mSharedPreferences.getInt(identifier, 0);
|
||||
if (result == 0) {
|
||||
result = mNextDeviceId++;
|
||||
spedit.putInt("next_device_id", mNextDeviceId);
|
||||
}
|
||||
|
||||
spedit.putInt(identifier, result);
|
||||
spedit.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void initializeUSB() {
|
||||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||
if (mUsbManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// Logging
|
||||
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
||||
Log.i(TAG,"Path: " + device.getDeviceName());
|
||||
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
||||
Log.i(TAG,"Product: " + device.getProductName());
|
||||
Log.i(TAG,"ID: " + device.getDeviceId());
|
||||
Log.i(TAG,"Class: " + device.getDeviceClass());
|
||||
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
||||
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
||||
Log.i(TAG,"Product ID: " + device.getProductId());
|
||||
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
||||
Log.i(TAG,"---------------------------------------");
|
||||
|
||||
// Get interface details
|
||||
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
||||
UsbInterface mUsbInterface = device.getInterface(index);
|
||||
Log.i(TAG," ***** *****");
|
||||
Log.i(TAG," Interface index: " + index);
|
||||
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
||||
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
||||
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||
|
||||
// Get endpoint details
|
||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||
{
|
||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||
Log.i(TAG," ++++ ++++ ++++");
|
||||
Log.i(TAG," Endpoint index: " + epi);
|
||||
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
||||
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
||||
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
||||
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
||||
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
||||
Log.i(TAG," Type: " + mEndpoint.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.i(TAG," No more devices connected.");
|
||||
*/
|
||||
|
||||
// Register for USB broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||
|
||||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
}
|
||||
}
|
||||
|
||||
UsbManager getUSBManager() {
|
||||
return mUsbManager;
|
||||
}
|
||||
|
||||
private void shutdownUSB() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mUsbBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
||||
return true;
|
||||
}
|
||||
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB360_IFACE_SUBCLASS = 93;
|
||||
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
||||
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x0079, // GPD Win 2
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x046d, // Logitech
|
||||
0x056e, // Elecom
|
||||
0x06a3, // Saitek
|
||||
0x0738, // Mad Catz
|
||||
0x07ff, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x1038, // SteelSeries
|
||||
0x11c9, // Nacon
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
0x146b, // BigBen
|
||||
0x1532, // Razer Sabertooth
|
||||
0x15e4, // Numark
|
||||
0x162e, // Joytech
|
||||
0x1689, // Razer Onza
|
||||
0x1949, // Lab126, Inc.
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
||||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB1_IFACE_SUBCLASS = 71;
|
||||
final int XB1_IFACE_PROTOCOL = 208;
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x03f0, // HP
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x10f5, // Turtle Beach
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, // 8BitDo
|
||||
0x2e24, // Hyperkin
|
||||
0x3537, // GameSir
|
||||
};
|
||||
|
||||
if (usbInterface.getId() == 0 &&
|
||||
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
||||
connectHIDDeviceUSB(usbDevice);
|
||||
}
|
||||
|
||||
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
||||
List<Integer> devices = new ArrayList<Integer>();
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
devices.add(device.getId());
|
||||
}
|
||||
}
|
||||
for (int id : devices) {
|
||||
HIDDevice device = mDevicesById.get(id);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
boolean opened = false;
|
||||
if (permission_granted) {
|
||||
opened = device.open();
|
||||
}
|
||||
HIDDeviceOpenResult(device.getId(), opened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
||||
synchronized (this) {
|
||||
int interface_mask = 0;
|
||||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||
// Check to see if we've already added this interface
|
||||
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||
int interface_id = usbInterface.getId();
|
||||
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||
continue;
|
||||
}
|
||||
interface_mask |= (1 << interface_id);
|
||||
|
||||
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
||||
int id = device.getId();
|
||||
mDevicesById.put(id, device);
|
||||
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBluetooth() {
|
||||
Log.d(TAG, "Initializing Bluetooth");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find bonded bluetooth controllers and create SteamControllers for them
|
||||
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (mBluetoothManager == null) {
|
||||
// This device doesn't support Bluetooth.
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
||||
if (btAdapter == null) {
|
||||
// This device has Bluetooth support in the codebase, but has no available adapters.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get our bonded devices.
|
||||
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
||||
|
||||
Log.d(TAG, "Bluetooth device available: " + device);
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
||||
|
||||
if (mIsChromebook) {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
||||
|
||||
// final HIDDeviceManager finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.chromebookConnectionHandler();
|
||||
// }
|
||||
// }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownBluetooth() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mBluetoothBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
||||
// This function provides a sort of dummy version of that, watching for changes in the
|
||||
// connected devices and attempting to add controllers as things change.
|
||||
public void chromebookConnectionHandler() {
|
||||
if (!mIsChromebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||
|
||||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||
connected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
||||
if (!currentConnected.contains(bluetoothDevice)) {
|
||||
disconnected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
|
||||
mLastBluetoothDevices = currentConnected;
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : disconnected) {
|
||||
disconnectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : connected) {
|
||||
connectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
|
||||
final HIDDeviceManager finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.chromebookConnectionHandler();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
||||
synchronized (this) {
|
||||
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
||||
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
||||
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
device.reconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.put(bluetoothDevice, device);
|
||||
mDevicesById.put(id, device);
|
||||
|
||||
// The Steam Controller will mark itself connected once initialization is complete
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
synchronized (this) {
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
if (device == null)
|
||||
return;
|
||||
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.remove(bluetoothDevice);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
||||
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
||||
if (bluetoothDevice == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the device has no local name, we really don't want to try an equality check against it.
|
||||
if (bluetoothDevice.getName() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
shutdownUSB();
|
||||
shutdownBluetooth();
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.shutdown();
|
||||
}
|
||||
mDevicesById.clear();
|
||||
mBluetoothDevices.clear();
|
||||
HIDDeviceReleaseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFrozen(boolean frozen) {
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.setFrozen(frozen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private HIDDevice getDevice(int id) {
|
||||
synchronized (this) {
|
||||
HIDDevice result = mDevicesById.get(id);
|
||||
if (result == null) {
|
||||
Log.v(TAG, "No device for id: " + id);
|
||||
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////// JNI interface functions
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||
|
||||
if (usb) {
|
||||
initializeUSB();
|
||||
}
|
||||
if (bluetooth) {
|
||||
initializeBluetooth();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean openDevice(int deviceID) {
|
||||
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||
HIDDevice device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look to see if this is a USB device and we have permission to access it
|
||||
UsbDevice usbDevice = device.getDevice();
|
||||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||
HIDDeviceOpenPending(deviceID);
|
||||
try {
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||
HIDDeviceOpenResult(deviceID, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return device.open();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int sendOutputReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendOutputReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int sendFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean getFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
return device.getFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void closeDevice(int deviceID) {
|
||||
try {
|
||||
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return;
|
||||
}
|
||||
|
||||
device.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////// Native methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private native void HIDDeviceRegisterCallback();
|
||||
private native void HIDDeviceReleaseCallback();
|
||||
|
||||
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
||||
native void HIDDeviceOpenPending(int deviceID);
|
||||
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
||||
native void HIDDeviceDisconnected(int deviceID);
|
||||
|
||||
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
||||
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
||||
}
|
||||
309
android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
Normal file
309
android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
Normal file
@@ -0,0 +1,309 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import java.util.Arrays;
|
||||
|
||||
class HIDDeviceUSB implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
|
||||
protected HIDDeviceManager mManager;
|
||||
protected UsbDevice mDevice;
|
||||
protected int mInterfaceIndex;
|
||||
protected int mInterface;
|
||||
protected int mDeviceId;
|
||||
protected UsbDeviceConnection mConnection;
|
||||
protected UsbEndpoint mInputEndpoint;
|
||||
protected UsbEndpoint mOutputEndpoint;
|
||||
protected InputThread mInputThread;
|
||||
protected boolean mRunning;
|
||||
protected boolean mFrozen;
|
||||
|
||||
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
||||
mManager = manager;
|
||||
mDevice = usbDevice;
|
||||
mInterfaceIndex = interface_index;
|
||||
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
||||
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
return mDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
return mDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
catch (SecurityException exception) {
|
||||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getManufacturerName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getVendorId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getProductName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getProductId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||||
if (mConnection == null) {
|
||||
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force claim our interface
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
if (!mConnection.claimInterface(iface, true)) {
|
||||
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the endpoints
|
||||
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||||
UsbEndpoint endpt = iface.getEndpoint(j);
|
||||
switch (endpt.getDirection()) {
|
||||
case UsbConstants.USB_DIR_IN:
|
||||
if (mInputEndpoint == null) {
|
||||
mInputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
case UsbConstants.USB_DIR_OUT:
|
||||
if (mOutputEndpoint == null) {
|
||||
mOutputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the required endpoints were present
|
||||
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||||
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening for input
|
||||
mRunning = true;
|
||||
mInputThread = new InputThread();
|
||||
mInputThread.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||||
0x09/*HID set_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||||
if (r != report.length) {
|
||||
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
/* Offset the return buffer by 1, so that the report ID
|
||||
will remain in byte 0. */
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||||
0x01/*HID get_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++res;
|
||||
++length;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
if (res == length) {
|
||||
data = report;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(report, 0, res);
|
||||
}
|
||||
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mRunning = false;
|
||||
if (mInputThread != null) {
|
||||
while (mInputThread.isAlive()) {
|
||||
mInputThread.interrupt();
|
||||
try {
|
||||
mInputThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Keep trying until we're done
|
||||
}
|
||||
}
|
||||
mInputThread = null;
|
||||
}
|
||||
if (mConnection != null) {
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
mConnection.releaseInterface(iface);
|
||||
mConnection.close();
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
mManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
protected class InputThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||||
byte[] packet = new byte[packetSize];
|
||||
while (mRunning) {
|
||||
int r;
|
||||
try
|
||||
{
|
||||
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||||
break;
|
||||
}
|
||||
if (r < 0) {
|
||||
// Could be a timeout or an I/O error
|
||||
}
|
||||
if (r > 0) {
|
||||
byte[] data;
|
||||
if (r == packetSize) {
|
||||
data = packet;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(packet, 0, r);
|
||||
}
|
||||
|
||||
if (!mFrozen) {
|
||||
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
android/app/src/main/java/org/libsdl/app/SDL.java
Normal file
86
android/app/src/main/java/org/libsdl/app/SDL.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
SDL library initialization
|
||||
*/
|
||||
public class SDL {
|
||||
|
||||
// This function should be called first and sets up the native code
|
||||
// so it can call into the Java classes
|
||||
public static void setupJNI() {
|
||||
SDLActivity.nativeSetupJNI();
|
||||
SDLAudioManager.nativeSetupJNI();
|
||||
SDLControllerManager.nativeSetupJNI();
|
||||
}
|
||||
|
||||
// This function should be called each time the activity is started
|
||||
public static void initialize() {
|
||||
setContext(null);
|
||||
|
||||
SDLActivity.initialize();
|
||||
SDLAudioManager.initialize();
|
||||
SDLControllerManager.initialize();
|
||||
}
|
||||
|
||||
// This function stores the current activity (SDL or not)
|
||||
public static void setContext(Context context) {
|
||||
SDLAudioManager.setContext(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
|
||||
if (libraryName == null) {
|
||||
throw new NullPointerException("No library name provided.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Let's see if we have ReLinker available in the project. This is necessary for
|
||||
// some projects that have huge numbers of local libraries bundled, and thus may
|
||||
// trip a bug in Android's native library loader which ReLinker works around. (If
|
||||
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
||||
// internally.)
|
||||
//
|
||||
// To use ReLinker, just add it as a dependency. For more information, see
|
||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||
//
|
||||
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
|
||||
|
||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||
// they've changed during updates.
|
||||
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
||||
Object relinkInstance = forceMethod.invoke(null);
|
||||
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
||||
|
||||
// Actually load the library!
|
||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
|
||||
}
|
||||
catch (final Throwable e) {
|
||||
// Fall back
|
||||
try {
|
||||
System.loadLibrary(libraryName);
|
||||
}
|
||||
catch (final UnsatisfiedLinkError ule) {
|
||||
throw ule;
|
||||
}
|
||||
catch (final SecurityException se) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Context mContext;
|
||||
}
|
||||
2122
android/app/src/main/java/org/libsdl/app/SDLActivity.java
Normal file
2122
android/app/src/main/java/org/libsdl/app/SDLActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
514
android/app/src/main/java/org/libsdl/app/SDLAudioManager.java
Normal file
514
android/app/src/main/java/org/libsdl/app/SDLAudioManager.java
Normal file
@@ -0,0 +1,514 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceCallback;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SDLAudioManager {
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
protected static Context mContext;
|
||||
|
||||
private static final int[] NO_DEVICES = {};
|
||||
|
||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
mAudioDeviceCallback = null;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||
{
|
||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
if (context != null) {
|
||||
registerAudioDeviceCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void release(Context context) {
|
||||
unregisterAudioDeviceCallback(context);
|
||||
}
|
||||
|
||||
// Audio
|
||||
|
||||
protected static String getAudioFormatString(int audioFormat) {
|
||||
switch (audioFormat) {
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
int channelConfig;
|
||||
int sampleSize;
|
||||
int frameSize;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||
|
||||
/* On older devices let's use known good settings */
|
||||
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
}
|
||||
switch (audioFormat)
|
||||
{
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
sampleSize = 1;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
sampleSize = 2;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
sampleSize = 4;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
sampleSize = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isCapture) {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
break;
|
||||
case 7:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
} else {
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||
desiredChannels = 6;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Log.v(TAG, "Speaker configuration (and order of channels):");
|
||||
|
||||
if ((channelConfig & 0x00000004) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000008) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000010) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000020) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
||||
}
|
||||
if ((channelConfig & 0x00000040) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000080) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000100) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000200) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000400) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000800) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00001000) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
||||
}
|
||||
*/
|
||||
}
|
||||
frameSize = (sampleSize * desiredChannels);
|
||||
|
||||
// Let the user pick a larger buffer if they really want -- but ye
|
||||
// gods they probably shouldn't, the minimums are horrifyingly high
|
||||
// latency already
|
||||
int minBufferSize;
|
||||
if (isCapture) {
|
||||
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
} else {
|
||||
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
}
|
||||
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
||||
|
||||
int[] results = new int[4];
|
||||
|
||||
if (isCapture) {
|
||||
if (mAudioRecord == null) {
|
||||
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
||||
channelConfig, audioFormat, desiredFrames * frameSize);
|
||||
|
||||
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
||||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.e(TAG, "Failed during initialization of AudioRecord");
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
results[0] = mAudioRecord.getSampleRate();
|
||||
results[1] = mAudioRecord.getAudioFormat();
|
||||
results[2] = mAudioRecord.getChannelCount();
|
||||
|
||||
} else {
|
||||
if (mAudioTrack == null) {
|
||||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
||||
|
||||
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
||||
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
||||
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
||||
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||
/* Try again, with safer values */
|
||||
|
||||
Log.e(TAG, "Failed during initialization of Audio Track");
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
results[0] = mAudioTrack.getSampleRate();
|
||||
results[1] = mAudioTrack.getAudioFormat();
|
||||
results[2] = mAudioTrack.getChannelCount();
|
||||
}
|
||||
results[3] = desiredFrames;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerAudioDeviceCallback() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unregisterAudioDeviceCallback(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioOutputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioInputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteFloatBuffer(float[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(float)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteShortBuffer(short[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(short)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteByteBuffer(byte[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length; ) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(byte)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return 0;
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioClose() {
|
||||
if (mAudioTrack != null) {
|
||||
mAudioTrack.stop();
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void captureClose() {
|
||||
if (mAudioRecord != null) {
|
||||
mAudioRecord.stop();
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
||||
try {
|
||||
|
||||
/* Set thread name */
|
||||
if (iscapture) {
|
||||
Thread.currentThread().setName("SDLAudioC" + device_id);
|
||||
} else {
|
||||
Thread.currentThread().setName("SDLAudioP" + device_id);
|
||||
}
|
||||
|
||||
/* Set thread priority */
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "modify thread properties failed " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,854 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
public class SDLControllerManager
|
||||
{
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||
int vendor_id, int product_id,
|
||||
boolean is_accelerometer, int button_mask,
|
||||
int naxes, int axis_mask, int nhats, int nballs);
|
||||
public static native int nativeRemoveJoystick(int device_id);
|
||||
public static native int nativeAddHaptic(int device_id, String name);
|
||||
public static native int nativeRemoveHaptic(int device_id);
|
||||
public static native int onNativePadDown(int device_id, int keycode);
|
||||
public static native int onNativePadUp(int device_id, int keycode);
|
||||
public static native void onNativeJoy(int device_id, int axis,
|
||||
float value);
|
||||
public static native void onNativeHat(int device_id, int hat_id,
|
||||
int x, int y);
|
||||
|
||||
protected static SDLJoystickHandler mJoystickHandler;
|
||||
protected static SDLHapticHandler mHapticHandler;
|
||||
|
||||
private static final String TAG = "SDLControllerManager";
|
||||
|
||||
public static void initialize() {
|
||||
if (mJoystickHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
}
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
mHapticHandler = new SDLHapticHandler_API26();
|
||||
} else {
|
||||
mHapticHandler = new SDLHapticHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
||||
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
||||
return mJoystickHandler.handleMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollInputDevices() {
|
||||
mJoystickHandler.pollInputDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollHapticDevices() {
|
||||
mHapticHandler.pollHapticDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticRun(int device_id, float intensity, int length) {
|
||||
mHapticHandler.run(device_id, intensity, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticStop(int device_id)
|
||||
{
|
||||
mHapticHandler.stop(device_id);
|
||||
}
|
||||
|
||||
// Check if a given device is considered a possible SDL joystick
|
||||
public static boolean isDeviceSDLJoystick(int deviceId) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
||||
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
||||
if ((device == null) || (deviceId < 0)) {
|
||||
return false;
|
||||
}
|
||||
int sources = device.getSources();
|
||||
|
||||
/* This is called for every button press, so let's not spam the logs */
|
||||
/*
|
||||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
||||
}
|
||||
*/
|
||||
|
||||
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
||||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
||||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLJoystickHandler {
|
||||
|
||||
/**
|
||||
* Handles given MotionEvent.
|
||||
* @param event the event to be handled.
|
||||
* @return if given event was processed.
|
||||
*/
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding and removing of input devices.
|
||||
*/
|
||||
public void pollInputDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Actual joystick functionality available for API >= 12 devices */
|
||||
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
|
||||
static class SDLJoystick {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public String desc;
|
||||
public ArrayList<InputDevice.MotionRange> axes;
|
||||
public ArrayList<InputDevice.MotionRange> hats;
|
||||
}
|
||||
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||
@Override
|
||||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
||||
int arg0Axis = arg0.getAxis();
|
||||
int arg1Axis = arg1.getAxis();
|
||||
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg0Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<SDLJoystick> mJoysticks;
|
||||
|
||||
public SDLJoystickHandler_API16() {
|
||||
|
||||
mJoysticks = new ArrayList<SDLJoystick>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pollInputDevices() {
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
|
||||
for (int device_id : deviceIds) {
|
||||
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||
SDLJoystick joystick = getJoystick(device_id);
|
||||
if (joystick == null) {
|
||||
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||
joystick = new SDLJoystick();
|
||||
joystick.device_id = device_id;
|
||||
joystick.name = joystickDevice.getName();
|
||||
joystick.desc = getJoystickDescriptor(joystickDevice);
|
||||
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
||||
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
||||
|
||||
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
||||
Collections.sort(ranges, new RangeComparator());
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||
joystick.hats.add(range);
|
||||
} else {
|
||||
joystick.axes.add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mJoysticks.add(joystick);
|
||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
int device_id = joystick.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||
if (mJoysticks.get(i).device_id == device_id) {
|
||||
mJoysticks.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLJoystick getJoystick(int device_id) {
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
if (joystick.device_id == device_id) {
|
||||
return joystick;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||
if (joystick != null) {
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||
/* Normalize the value to -1...1 */
|
||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||
}
|
||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
||||
String desc = joystickDevice.getDescriptor();
|
||||
|
||||
if (desc != null && !desc.isEmpty()) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
return joystickDevice.getName();
|
||||
}
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
return -1;
|
||||
}
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
|
||||
@Override
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
// For compatibility, keep computing the axis mask like before,
|
||||
// only really distinguishing 2, 4 and 6 axes.
|
||||
int axis_mask = 0;
|
||||
if (ranges.size() >= 2) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||
axis_mask |= 0x0003;
|
||||
}
|
||||
if (ranges.size() >= 4) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||
axis_mask |= 0x000c;
|
||||
}
|
||||
if (ranges.size() >= 6) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
axis_mask |= 0x0030;
|
||||
}
|
||||
// Also add an indicator bit for whether the sorting order has changed.
|
||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||
boolean have_z = false;
|
||||
boolean have_past_z_before_rz = false;
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_Z) {
|
||||
have_z = true;
|
||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||
have_past_z_before_rz = true;
|
||||
}
|
||||
}
|
||||
if (have_z && have_past_z_before_rz) {
|
||||
// If both these exist, the compare() function changed sorting order.
|
||||
// Set a bit to indicate this fact.
|
||||
axis_mask |= 0x8000;
|
||||
}
|
||||
return axis_mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
int button_mask = 0;
|
||||
int[] keys = new int[] {
|
||||
KeyEvent.KEYCODE_BUTTON_A,
|
||||
KeyEvent.KEYCODE_BUTTON_B,
|
||||
KeyEvent.KEYCODE_BUTTON_X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y,
|
||||
KeyEvent.KEYCODE_BACK,
|
||||
KeyEvent.KEYCODE_MENU,
|
||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||
KeyEvent.KEYCODE_BUTTON_START,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||
KeyEvent.KEYCODE_BUTTON_L1,
|
||||
KeyEvent.KEYCODE_BUTTON_R1,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||
|
||||
// These don't map into any SDL controller buttons directly
|
||||
KeyEvent.KEYCODE_BUTTON_L2,
|
||||
KeyEvent.KEYCODE_BUTTON_R2,
|
||||
KeyEvent.KEYCODE_BUTTON_C,
|
||||
KeyEvent.KEYCODE_BUTTON_Z,
|
||||
KeyEvent.KEYCODE_BUTTON_1,
|
||||
KeyEvent.KEYCODE_BUTTON_2,
|
||||
KeyEvent.KEYCODE_BUTTON_3,
|
||||
KeyEvent.KEYCODE_BUTTON_4,
|
||||
KeyEvent.KEYCODE_BUTTON_5,
|
||||
KeyEvent.KEYCODE_BUTTON_6,
|
||||
KeyEvent.KEYCODE_BUTTON_7,
|
||||
KeyEvent.KEYCODE_BUTTON_8,
|
||||
KeyEvent.KEYCODE_BUTTON_9,
|
||||
KeyEvent.KEYCODE_BUTTON_10,
|
||||
KeyEvent.KEYCODE_BUTTON_11,
|
||||
KeyEvent.KEYCODE_BUTTON_12,
|
||||
KeyEvent.KEYCODE_BUTTON_13,
|
||||
KeyEvent.KEYCODE_BUTTON_14,
|
||||
KeyEvent.KEYCODE_BUTTON_15,
|
||||
KeyEvent.KEYCODE_BUTTON_16,
|
||||
};
|
||||
int[] masks = new int[] {
|
||||
(1 << 0), // A -> A
|
||||
(1 << 1), // B -> B
|
||||
(1 << 2), // X -> X
|
||||
(1 << 3), // Y -> Y
|
||||
(1 << 4), // BACK -> BACK
|
||||
(1 << 6), // MENU -> START
|
||||
(1 << 5), // MODE -> GUIDE
|
||||
(1 << 6), // START -> START
|
||||
(1 << 7), // THUMBL -> LEFTSTICK
|
||||
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||
(1 << 9), // L1 -> LEFTSHOULDER
|
||||
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||
(1 << 4), // SELECT -> BACK
|
||||
(1 << 0), // DPAD_CENTER -> A
|
||||
(1 << 15), // L2 -> ??
|
||||
(1 << 16), // R2 -> ??
|
||||
(1 << 17), // C -> ??
|
||||
(1 << 18), // Z -> ??
|
||||
(1 << 20), // 1 -> ??
|
||||
(1 << 21), // 2 -> ??
|
||||
(1 << 22), // 3 -> ??
|
||||
(1 << 23), // 4 -> ??
|
||||
(1 << 24), // 5 -> ??
|
||||
(1 << 25), // 6 -> ??
|
||||
(1 << 26), // 7 -> ??
|
||||
(1 << 27), // 8 -> ??
|
||||
(1 << 28), // 9 -> ??
|
||||
(1 << 29), // 10 -> ??
|
||||
(1 << 30), // 11 -> ??
|
||||
(1 << 31), // 12 -> ??
|
||||
// We're out of room...
|
||||
0xFFFFFFFF, // 13 -> ??
|
||||
0xFFFFFFFF, // 14 -> ??
|
||||
0xFFFFFFFF, // 15 -> ??
|
||||
0xFFFFFFFF, // 16 -> ??
|
||||
};
|
||||
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
||||
for (int i = 0; i < keys.length; ++i) {
|
||||
if (has_keys[i]) {
|
||||
button_mask |= masks[i];
|
||||
}
|
||||
}
|
||||
return button_mask;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
||||
@Override
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
||||
if (intensity == 0.0f) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
|
||||
int vibeValue = Math.round(intensity * 255);
|
||||
|
||||
if (vibeValue > 255) {
|
||||
vibeValue = 255;
|
||||
}
|
||||
if (vibeValue < 1) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
||||
// something went horribly wrong with the Android 8.0 APIs.
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler {
|
||||
|
||||
static class SDLHaptic {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public Vibrator vib;
|
||||
}
|
||||
|
||||
private final ArrayList<SDLHaptic> mHaptics;
|
||||
|
||||
public SDLHapticHandler() {
|
||||
mHaptics = new ArrayList<SDLHaptic>();
|
||||
}
|
||||
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(int device_id) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void pollHapticDevices() {
|
||||
|
||||
final int deviceId_VIBRATOR_SERVICE = 999999;
|
||||
boolean hasVibratorService = false;
|
||||
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
// It helps processing the device ids in reverse order
|
||||
// For example, in the case of the XBox 360 wireless dongle,
|
||||
// so the first controller seen by SDL matches what the receiver
|
||||
// considers to be the first controller
|
||||
|
||||
for (int i = deviceIds.length - 1; i > -1; i--) {
|
||||
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
||||
if (haptic == null) {
|
||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||
Vibrator vib = device.getVibrator();
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check VIBRATOR_SERVICE */
|
||||
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vib != null) {
|
||||
hasVibratorService = vib.hasVibrator();
|
||||
|
||||
if (hasVibratorService) {
|
||||
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
||||
if (haptic == null) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
||||
haptic.name = "VIBRATOR_SERVICE";
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
int device_id = haptic.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
|
||||
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
} // else: don't remove the vibrator if it is still present
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||
for (int i = 0; i < mHaptics.size(); i++) {
|
||||
if (mHaptics.get(i).device_id == device_id) {
|
||||
mHaptics.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLHaptic getHaptic(int device_id) {
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
if (haptic.device_id == device_id) {
|
||||
return haptic;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsRelativeMouse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean inRelativeMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public float getEventX(MotionEvent event) {
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
public float getEventY(MotionEvent event) {
|
||||
return event.getY(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
|
||||
// Handle relative mouse mode
|
||||
if (mRelativeModeEnabled) {
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event was not managed, call SDLGenericMotionListener_API12 method
|
||||
return super.onGenericMotion(v, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
} else {
|
||||
return event.getX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
} else {
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
// DeX desktop mouse cursor is a separate non-standard input type.
|
||||
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||
if (enabled) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
} else {
|
||||
SDLActivity.getContentView().releasePointerCapture();
|
||||
}
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
405
android/app/src/main/java/org/libsdl/app/SDLSurface.java
Normal file
405
android/app/src/main/java/org/libsdl/app/SDLSurface.java
Normal file
@@ -0,0 +1,405 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
|
||||
/**
|
||||
SDLSurface. This is what we draw on, so we need to know when it's created
|
||||
in order to do anything useful.
|
||||
|
||||
Because of this, that's where we set up the SDL thread
|
||||
*/
|
||||
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
|
||||
// Keep track of the surface size to normalize touch events
|
||||
protected float mWidth, mHeight;
|
||||
|
||||
// Is SurfaceView ready for rendering
|
||||
public boolean mIsSurfaceReady;
|
||||
|
||||
// Startup
|
||||
public SDLSurface(Context context) {
|
||||
super(context);
|
||||
getHolder().addCallback(this);
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
|
||||
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
||||
|
||||
// Some arbitrary defaults to avoid a potential division by zero
|
||||
mWidth = 1.0f;
|
||||
mHeight = 1.0f;
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
}
|
||||
|
||||
public void handlePause() {
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
||||
}
|
||||
|
||||
public void handleResume() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
||||
}
|
||||
|
||||
public Surface getNativeSurface() {
|
||||
return getHolder().getSurface();
|
||||
}
|
||||
|
||||
// Called when we have a valid drawing surface
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
// Called when we lose the surface
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
SDLActivity.onNativeSurfaceDestroyed();
|
||||
}
|
||||
|
||||
// Called when the surface is resized
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder,
|
||||
int format, int width, int height) {
|
||||
Log.v("SDL", "surfaceChanged()");
|
||||
|
||||
if (SDLActivity.mSingleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
int nDeviceWidth = width;
|
||||
int nDeviceHeight = height;
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||
mDisplay.getRealMetrics( realMetrics );
|
||||
nDeviceWidth = realMetrics.widthPixels;
|
||||
nDeviceHeight = realMetrics.heightPixels;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
synchronized(SDLActivity.getContext()) {
|
||||
// In case we're waiting on a size change after going fullscreen, send a notification.
|
||||
SDLActivity.getContext().notifyAll();
|
||||
}
|
||||
|
||||
Log.v("SDL", "Window size: " + width + "x" + height);
|
||||
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
||||
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
||||
SDLActivity.onNativeResize();
|
||||
|
||||
// Prevent a screen distortion glitch,
|
||||
// for instance when the device is in Landscape and a Portrait App is resumed.
|
||||
boolean skip = false;
|
||||
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
||||
|
||||
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
||||
if (mWidth > mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
||||
if (mWidth < mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special Patch for Square Resolution: Black Berry Passport
|
||||
if (skip) {
|
||||
double min = Math.min(mWidth, mHeight);
|
||||
double max = Math.max(mWidth, mHeight);
|
||||
|
||||
if (max / min < 1.20) {
|
||||
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't skip in MultiWindow.
|
||||
if (skip) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||
Log.v("SDL", "Don't skip in Multi-Window");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
Log.v("SDL", "Skip .. Surface is not ready.");
|
||||
mIsSurfaceReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
||||
SDLActivity.onNativeSurfaceChanged();
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
}
|
||||
|
||||
// Key events
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
||||
}
|
||||
|
||||
// Touch events
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
||||
int touchDevId = event.getDeviceId();
|
||||
final int pointerCount = event.getPointerCount();
|
||||
int action = event.getActionMasked();
|
||||
int pointerFingerId;
|
||||
int i = -1;
|
||||
float x,y,p;
|
||||
|
||||
/*
|
||||
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
||||
* Appears when using Android emulator, eg:
|
||||
* adb shell input mouse tap 100 100
|
||||
* adb shell input touchscreen tap 100 100
|
||||
*/
|
||||
if (touchDevId < 0) {
|
||||
touchDevId -= 1;
|
||||
}
|
||||
|
||||
// 12290 = Samsung DeX mode desktop mouse
|
||||
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
||||
// 0x2 = SOURCE_CLASS_POINTER
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
int mouseButton = 1;
|
||||
try {
|
||||
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
||||
if (object != null) {
|
||||
mouseButton = (Integer) object;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
||||
// if we are. We'll leverage our existing mouse motion listener
|
||||
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
||||
x = motionListener.getEventX(event);
|
||||
y = motionListener.getEventY(event);
|
||||
|
||||
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
||||
} else {
|
||||
switch(action) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Primary pointer up/down, the index is always zero
|
||||
i = 0;
|
||||
/* fallthrough */
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
// Non primary pointer up/down
|
||||
if (i == -1) {
|
||||
i = event.getActionIndex();
|
||||
}
|
||||
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sensor events
|
||||
public void enableSensor(int sensortype, boolean enabled) {
|
||||
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
||||
if (enabled) {
|
||||
mSensorManager.registerListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype),
|
||||
SensorManager.SENSOR_DELAY_GAME, null);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||
|
||||
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
||||
// We thus should check here.
|
||||
int newOrientation;
|
||||
|
||||
float x, y;
|
||||
switch (mDisplay.getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
x = -event.values[1];
|
||||
y = event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = event.values[1];
|
||||
y = -event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -event.values[0];
|
||||
y = -event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
x = event.values[0];
|
||||
y = event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
||||
SDLActivity.mCurrentOrientation = newOrientation;
|
||||
SDLActivity.onNativeOrientationChanged(newOrientation);
|
||||
}
|
||||
|
||||
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
||||
y / SensorManager.GRAVITY_EARTH,
|
||||
event.values[2] / SensorManager.GRAVITY_EARTH);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Captured pointer events for API 26.
|
||||
public boolean onCapturedPointerEvent(MotionEvent event)
|
||||
{
|
||||
int action = event.getActionMasked();
|
||||
|
||||
float x, y;
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
|
||||
// Change our action value to what SDL's code expects.
|
||||
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
||||
action = MotionEvent.ACTION_DOWN;
|
||||
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
||||
action = MotionEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
int button = event.getButtonState();
|
||||
|
||||
SDLActivity.onNativeMouse(button, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
<?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>
|
||||
|
||||
<string name="no_web_browser">No web browser found</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
project.ext.set("versionMajor", 5) // Version Major
|
||||
project.ext.set("versionMinor", 7) // Version Minor
|
||||
project.ext.set("versionPatch", 0) // Version Patch
|
||||
project.ext.set("versionExtra", "") // Version Extra
|
||||
project.ext.set("versionCode", 44) // Android Version Code
|
||||
project.ext.set("developmentBuild", 0) // Whether it is a development build, or a release
|
||||
// NOTE: +2 after each release!
|
||||
// +1 for ARM and +1 for ARM64 APK's, because
|
||||
// each APK must have a larger `versionCode` than the previous
|
||||
project.ext.set("versionMinor", 9) // Version Minor
|
||||
project.ext.set("versionPatch", 1) // Version Patch
|
||||
// ^ keep in sync with cmake
|
||||
|
||||
project.ext.set("versionBuild", 0) // Version Build
|
||||
// ^ fourth version number to allow releasing Android-only fixes and beta versions
|
||||
|
||||
buildscript {
|
||||
ext.ndk_version = '25.2.9519653'
|
||||
ext.ndk_version = '26.2.11394342'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
classpath 'com.android.tools.build:gradle:8.5.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
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<#if isLowMemory>
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxPermSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||
<#else>
|
||||
org.gradle.jvmargs=-Xmx16G -XX:MaxPermSize=8G -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx16G -XX:+HeapDumpOnOutOfMemoryError
|
||||
</#if>
|
||||
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
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
|
||||
295
android/gradlew
vendored
295
android/gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,78 +17,111 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
command -v java >/dev/null || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
100
android/gradlew.bat
vendored
100
android/gradlew.bat
vendored
@@ -1,100 +0,0 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
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,27 +2,24 @@ apply plugin: 'com.android.library'
|
||||
apply plugin: 'de.undercouch.download'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion '33.0.2'
|
||||
ndkVersion "$ndk_version"
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
compileSdk 34
|
||||
targetSdkVersion 34
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments '-j' + Runtime.getRuntime().availableProcessors(),
|
||||
"versionMajor=${versionMajor}",
|
||||
"versionMinor=${versionMinor}",
|
||||
"versionPatch=${versionPatch}",
|
||||
"versionExtra=${versionExtra}",
|
||||
"developmentBuild=${developmentBuild}"
|
||||
cmake {
|
||||
arguments "-DANDROID_STL=c++_shared",
|
||||
"-DENABLE_CURL=1", "-DENABLE_SOUND=1",
|
||||
"-DENABLE_GETTEXT=1",
|
||||
"-DBUILD_UNITTESTS=0", "-DENABLE_UPDATE_CHECKER=0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path file('jni/Android.mk')
|
||||
cmake {
|
||||
path file("../../CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,33 +34,34 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments 'NDEBUG=1'
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
debugSymbolLevel 'SYMBOL_TABLE'
|
||||
debugSymbolLevel 'FULL'
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace 'net.minetest'
|
||||
}
|
||||
|
||||
// get precompiled deps
|
||||
task downloadDeps(type: Download) {
|
||||
def depsDir = new File(buildDir.parent, 'deps')
|
||||
def depsZip = new File(buildDir, 'deps.zip')
|
||||
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')
|
||||
|
||||
src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps.zip'
|
||||
dest depsZip
|
||||
overwrite false
|
||||
src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps-lite.zip'
|
||||
dest depsZip
|
||||
overwrite false
|
||||
|
||||
task getDeps(dependsOn: downloadDeps, type: Copy) {
|
||||
depsDir.mkdir()
|
||||
from zipTree(depsZip)
|
||||
into depsDir
|
||||
doFirst { logger.lifecycle('Extracting to {}', depsDir) }
|
||||
task getDeps(dependsOn: downloadDeps, type: Copy) {
|
||||
depsDir.mkdir()
|
||||
from zipTree(depsZip)
|
||||
into depsDir
|
||||
doFirst { logger.lifecycle('Extracting to {}', depsDir) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
LOCAL_PATH := $(call my-dir)/..
|
||||
|
||||
#LOCAL_ADDRESS_SANITIZER:=true
|
||||
#USE_BUILTIN_LUA:=true
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Curl
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libcurl.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmbedcrypto
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedcrypto.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmbedtls
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedtls.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmbedx509
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedx509.a
|
||||
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 := Irrlicht-libpng
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libpng.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Irrlicht-libjpeg
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libjpeg.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
ifndef USE_BUILTIN_LUA
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := LuaJIT
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := OpenAL
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/libopenal.a
|
||||
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_GETTEXT=1 \
|
||||
-DVERSION_MAJOR=${versionMajor} \
|
||||
-DVERSION_MINOR=${versionMinor} \
|
||||
-DVERSION_PATCH=${versionPatch} \
|
||||
-DVERSION_EXTRA=${versionExtra} \
|
||||
-DDEVELOPMENT_BUILD=${developmentBuild} \
|
||||
$(GPROF_DEF)
|
||||
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_CFLAGS += -DUSE_LUAJIT=0
|
||||
else
|
||||
LOCAL_CFLAGS += -DUSE_LUAJIT=1
|
||||
endif
|
||||
|
||||
ifdef NDEBUG
|
||||
LOCAL_CFLAGS += -DNDEBUG=1
|
||||
endif
|
||||
|
||||
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)/OpenAL-Soft/include \
|
||||
deps/$(APP_ABI)/SQLite/include \
|
||||
deps/$(APP_ABI)/Vorbis/include \
|
||||
deps/$(APP_ABI)/Zstd/include
|
||||
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_C_INCLUDES += \
|
||||
../../lib/lua/src \
|
||||
../../lib/bitop
|
||||
else
|
||||
LOCAL_C_INCLUDES += deps/$(APP_ABI)/LuaJIT/include
|
||||
endif
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(wildcard ../../src/client/*.cpp) \
|
||||
$(wildcard ../../src/client/*/*.cpp) \
|
||||
$(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/lighting.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
|
||||
|
||||
# Built-in Lua
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_SRC_FILES += \
|
||||
../../lib/lua/src/lapi.c \
|
||||
../../lib/lua/src/lauxlib.c \
|
||||
../../lib/lua/src/lbaselib.c \
|
||||
../../lib/lua/src/lcode.c \
|
||||
../../lib/lua/src/ldblib.c \
|
||||
../../lib/lua/src/ldebug.c \
|
||||
../../lib/lua/src/ldo.c \
|
||||
../../lib/lua/src/ldump.c \
|
||||
../../lib/lua/src/lfunc.c \
|
||||
../../lib/lua/src/lgc.c \
|
||||
../../lib/lua/src/linit.c \
|
||||
../../lib/lua/src/liolib.c \
|
||||
../../lib/lua/src/llex.c \
|
||||
../../lib/lua/src/lmathlib.c \
|
||||
../../lib/lua/src/lmem.c \
|
||||
../../lib/lua/src/loadlib.c \
|
||||
../../lib/lua/src/lobject.c \
|
||||
../../lib/lua/src/lopcodes.c \
|
||||
../../lib/lua/src/loslib.c \
|
||||
../../lib/lua/src/lparser.c \
|
||||
../../lib/lua/src/lstate.c \
|
||||
../../lib/lua/src/lstring.c \
|
||||
../../lib/lua/src/lstrlib.c \
|
||||
../../lib/lua/src/ltable.c \
|
||||
../../lib/lua/src/ltablib.c \
|
||||
../../lib/lua/src/ltm.c \
|
||||
../../lib/lua/src/lundump.c \
|
||||
../../lib/lua/src/lvm.c \
|
||||
../../lib/lua/src/lzio.c \
|
||||
../../lib/bitop/bit.c
|
||||
endif
|
||||
|
||||
# GMP
|
||||
LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c
|
||||
|
||||
# JSONCPP
|
||||
LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += \
|
||||
Curl libmbedcrypto libmbedtls libmbedx509 \
|
||||
Freetype \
|
||||
Iconv libcharset \
|
||||
Irrlicht Irrlicht-libpng Irrlicht-libjpeg \
|
||||
OpenAL \
|
||||
Gettext \
|
||||
SQLite3 \
|
||||
Vorbis libvorbisfile libogg \
|
||||
Zstd
|
||||
ifndef USE_BUILTIN_LUA
|
||||
LOCAL_STATIC_LIBRARIES += LuaJIT
|
||||
endif
|
||||
LOCAL_STATIC_LIBRARIES += android_native_app_glue $(PROFILER_LIBS)
|
||||
|
||||
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES -lz
|
||||
|
||||
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++14
|
||||
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
|
||||
@@ -1 +1 @@
|
||||
<manifest package="net.minetest" />
|
||||
<manifest />
|
||||
|
||||
@@ -36,11 +36,16 @@ do
|
||||
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 == "craftitem" then
|
||||
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
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ dofile(clientpath .. "register.lua")
|
||||
dofile(commonpath .. "after.lua")
|
||||
dofile(commonpath .. "mod_storage.lua")
|
||||
dofile(commonpath .. "chatcommands.lua")
|
||||
dofile(commonpath .. "information_formspecs.lua")
|
||||
dofile(clientpath .. "chatcommands.lua")
|
||||
dofile(clientpath .. "death_formspec.lua")
|
||||
dofile(clientpath .. "misc.lua")
|
||||
|
||||
@@ -5,3 +5,14 @@ function core.setting_get_pos(name)
|
||||
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
|
||||
|
||||
@@ -1,4 +1,116 @@
|
||||
local jobs = {}
|
||||
-- This is an implementation of a job sheduling mechanism. It guarantees that
|
||||
-- coexisting jobs will execute primarily in order of least expiry, and
|
||||
-- secondarily in order of first registration.
|
||||
|
||||
-- These functions implement an intrusive singly linked list of one or more
|
||||
-- elements where the first element has a pointer to the last. The next pointer
|
||||
-- is stored with key list_next. The pointer to the last is with key list_end.
|
||||
|
||||
local function list_init(first)
|
||||
first.list_end = first
|
||||
end
|
||||
|
||||
local function list_append(first, append)
|
||||
first.list_end.list_next = append
|
||||
first.list_end = append
|
||||
end
|
||||
|
||||
local function list_append_list(first, first_append)
|
||||
first.list_end.list_next = first_append
|
||||
first.list_end = first_append.list_end
|
||||
end
|
||||
|
||||
-- The jobs are stored in a map from expiration times to linked lists of jobs
|
||||
-- as above. The expiration times are also stored in an array representing a
|
||||
-- binary min heap, which is a particular arrangement of binary tree. A parent
|
||||
-- at index i has children at indices i*2 and i*2+1. Out-of-bounds indices
|
||||
-- represent nonexistent children. A parent is never greater than its children.
|
||||
-- This structure means that, if there is at least one job, the next expiration
|
||||
-- time is the first item in the array.
|
||||
|
||||
-- Push element on a binary min-heap,
|
||||
-- "bubbling up" the element by swapping with larger parents.
|
||||
local function heap_push(heap, element)
|
||||
local index = #heap + 1
|
||||
while index > 1 do
|
||||
local parent_index = math.floor(index / 2)
|
||||
local parent = heap[parent_index]
|
||||
if element < parent then
|
||||
heap[index] = parent
|
||||
index = parent_index
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
heap[index] = element
|
||||
end
|
||||
|
||||
-- Pop smallest element from the heap,
|
||||
-- "sinking down" the last leaf on the last layer of the heap
|
||||
-- by swapping with the smaller child.
|
||||
local function heap_pop(heap)
|
||||
local removed_element = heap[1]
|
||||
local length = #heap
|
||||
local element = heap[length]
|
||||
heap[length] = nil
|
||||
length = length - 1
|
||||
if length > 0 then
|
||||
local index = 1
|
||||
while true do
|
||||
local old_index = index
|
||||
local smaller_element = element
|
||||
local left_index = index * 2
|
||||
local right_index = index * 2 + 1
|
||||
if left_index <= length then
|
||||
local left_element = heap[left_index]
|
||||
if left_element < smaller_element then
|
||||
index = left_index
|
||||
smaller_element = left_element
|
||||
end
|
||||
end
|
||||
if right_index <= length then
|
||||
if heap[right_index] < smaller_element then
|
||||
index = right_index
|
||||
end
|
||||
end
|
||||
if old_index ~= index then
|
||||
heap[old_index] = heap[index]
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
heap[index] = element
|
||||
end
|
||||
return removed_element
|
||||
end
|
||||
|
||||
local job_map = {}
|
||||
local expiries = {}
|
||||
|
||||
-- Adds an individual job with the given expiry.
|
||||
-- The worst-case complexity is O(log n), where n is the number of distinct
|
||||
-- expiration times.
|
||||
local function add_job(expiry, job)
|
||||
local list = job_map[expiry]
|
||||
if list then
|
||||
list_append(list, job)
|
||||
else
|
||||
list_init(job)
|
||||
job_map[expiry] = job
|
||||
heap_push(expiries, expiry)
|
||||
end
|
||||
end
|
||||
|
||||
-- Removes the next expiring jobs and returns the linked list of them.
|
||||
-- The worst-case complexity is O(log n), where n is the number of distinct
|
||||
-- expiration times.
|
||||
local function remove_first_jobs()
|
||||
local removed_expiry = heap_pop(expiries)
|
||||
local removed = job_map[removed_expiry]
|
||||
job_map[removed_expiry] = nil
|
||||
return removed
|
||||
end
|
||||
|
||||
local time = 0.0
|
||||
local time_next = math.huge
|
||||
|
||||
@@ -9,42 +121,54 @@ core.register_globalstep(function(dtime)
|
||||
return
|
||||
end
|
||||
|
||||
time_next = math.huge
|
||||
-- Remove the expired jobs.
|
||||
local expired = remove_first_jobs()
|
||||
|
||||
-- Iterate backwards so that we miss any new timers added by
|
||||
-- a timer callback.
|
||||
for i = #jobs, 1, -1 do
|
||||
local job = jobs[i]
|
||||
if time >= job.expire then
|
||||
core.set_last_run_mod(job.mod_origin)
|
||||
job.func(unpack(job.arg))
|
||||
local jobs_l = #jobs
|
||||
jobs[i] = jobs[jobs_l]
|
||||
jobs[jobs_l] = nil
|
||||
elseif job.expire < time_next then
|
||||
time_next = job.expire
|
||||
-- Remove other expired jobs and append them to the list.
|
||||
while true do
|
||||
time_next = expiries[1] or math.huge
|
||||
if time_next > time then
|
||||
break
|
||||
end
|
||||
list_append_list(expired, remove_first_jobs())
|
||||
end
|
||||
|
||||
-- Run the callbacks afterward to prevent infinite loops with core.after(0, ...).
|
||||
local last_expired = expired.list_end
|
||||
while true do
|
||||
core.set_last_run_mod(expired.mod_origin)
|
||||
expired.func(unpack(expired.args, 1, expired.args.n))
|
||||
if expired == last_expired then
|
||||
break
|
||||
end
|
||||
expired = expired.list_next
|
||||
end
|
||||
end)
|
||||
|
||||
function core.after(after, func, ...)
|
||||
assert(tonumber(after) and type(func) == "function",
|
||||
"Invalid minetest.after invocation")
|
||||
local expire = time + after
|
||||
local new_job = {
|
||||
func = func,
|
||||
expire = expire,
|
||||
arg = {...},
|
||||
mod_origin = core.get_last_run_mod(),
|
||||
}
|
||||
local job_metatable = {__index = {}}
|
||||
|
||||
jobs[#jobs + 1] = new_job
|
||||
time_next = math.min(time_next, expire)
|
||||
|
||||
return {
|
||||
cancel = function()
|
||||
new_job.func = function() end
|
||||
new_job.args = {}
|
||||
end
|
||||
}
|
||||
local function dummy_func() end
|
||||
function job_metatable.__index:cancel()
|
||||
self.func = dummy_func
|
||||
self.args = {n = 0}
|
||||
end
|
||||
|
||||
function core.after(after, func, ...)
|
||||
assert(tonumber(after) and not core.is_nan(after) and type(func) == "function",
|
||||
"Invalid minetest.after invocation")
|
||||
|
||||
local new_job = {
|
||||
mod_origin = core.get_last_run_mod(),
|
||||
func = func,
|
||||
args = {
|
||||
n = select("#", ...),
|
||||
...
|
||||
},
|
||||
}
|
||||
|
||||
local expiry = time + after
|
||||
add_job(expiry, new_job)
|
||||
time_next = math.min(time_next, expiry)
|
||||
|
||||
return setmetatable(new_job, job_metatable)
|
||||
end
|
||||
|
||||
@@ -89,7 +89,7 @@ local function do_help_cmd(name, param)
|
||||
if #args > 1 then
|
||||
return false, S("Too many arguments, try using just /help <command>")
|
||||
end
|
||||
local use_gui = INIT ~= "client" and core.get_player_by_name(name)
|
||||
local use_gui = INIT == "client" or core.get_player_by_name(name)
|
||||
use_gui = use_gui and not opts:find("t")
|
||||
|
||||
if #args == 0 and not use_gui then
|
||||
@@ -163,8 +163,8 @@ end
|
||||
|
||||
if INIT == "client" then
|
||||
core.register_chatcommand("help", {
|
||||
params = core.gettext("[all | <cmd>]"),
|
||||
description = core.gettext("Get help for commands"),
|
||||
params = core.gettext("[all | <cmd>] [-t]"),
|
||||
description = core.gettext("Get help for commands (-t: output in chat)"),
|
||||
func = function(param)
|
||||
return do_help_cmd(nil, param)
|
||||
end,
|
||||
|
||||
@@ -61,15 +61,20 @@ local function build_chatcommands_formspec(name, sel, copy)
|
||||
for i, data in ipairs(mod_cmds) do
|
||||
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
|
||||
for j, cmds in ipairs(data[2]) do
|
||||
local has_priv = check_player_privs(name, cmds[2].privs)
|
||||
local has_priv = INIT == "client" or check_player_privs(name, cmds[2].privs)
|
||||
rows[#rows + 1] = ("%s,1,%s,%s"):format(
|
||||
has_priv and COLOR_GREEN or COLOR_GRAY,
|
||||
cmds[1], F(cmds[2].params))
|
||||
if sel == #rows then
|
||||
description = cmds[2].description
|
||||
if copy then
|
||||
core.chat_send_player(name, S("Command: @1 @2",
|
||||
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params))
|
||||
local msg = S("Command: @1 @2",
|
||||
core.colorize("#0FF", (INIT == "client" and "." or "/") .. cmds[1]), cmds[2].params)
|
||||
if INIT == "client" then
|
||||
core.display_chat_message(msg)
|
||||
else
|
||||
core.chat_send_player(name, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -111,26 +116,46 @@ end
|
||||
|
||||
|
||||
-- DETAILED CHAT COMMAND INFORMATION
|
||||
if INIT == "client" then
|
||||
core.register_on_formspec_input(function(formname, fields)
|
||||
if formname ~= "__builtin:help_cmds" or fields.quit then
|
||||
return
|
||||
end
|
||||
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "__builtin:help_cmds" or fields.quit then
|
||||
return
|
||||
end
|
||||
local event = core.explode_table_event(fields.list)
|
||||
if event.type ~= "INV" then
|
||||
core.show_formspec("__builtin:help_cmds",
|
||||
build_chatcommands_formspec(nil, event.row, event.type == "DCL"))
|
||||
end
|
||||
end)
|
||||
else
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "__builtin:help_cmds" or fields.quit then
|
||||
return
|
||||
end
|
||||
|
||||
local event = core.explode_table_event(fields.list)
|
||||
if event.type ~= "INV" then
|
||||
local name = player:get_player_name()
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name, event.row, event.type == "DCL"))
|
||||
end
|
||||
end)
|
||||
local event = core.explode_table_event(fields.list)
|
||||
if event.type ~= "INV" then
|
||||
local name = player:get_player_name()
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name, event.row, event.type == "DCL"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function core.show_general_help_formspec(name)
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name))
|
||||
if INIT == "client" then
|
||||
core.show_formspec("__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name))
|
||||
else
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name))
|
||||
end
|
||||
end
|
||||
|
||||
function core.show_privs_help_formspec(name)
|
||||
core.show_formspec(name, "__builtin:help_privs",
|
||||
build_privs_formspec(name))
|
||||
if INIT ~= "client" then
|
||||
function core.show_privs_help_formspec(name)
|
||||
core.show_formspec(name, "__builtin:help_privs",
|
||||
build_privs_formspec(name))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -144,6 +144,8 @@ local wallmounted_to_dir = {
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 0, 0, -1),
|
||||
vector.new( 0, 1, 0),
|
||||
vector.new( 0, -1, 0),
|
||||
}
|
||||
function core.wallmounted_to_dir(wallmounted)
|
||||
return wallmounted_to_dir[wallmounted % 8]
|
||||
|
||||
19
builtin/common/metatable.lua
Normal file
19
builtin/common/metatable.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Registered metatables, used by the C++ packer
|
||||
local known_metatables = {}
|
||||
function core.register_portable_metatable(name, mt)
|
||||
assert(type(name) == "string", ("attempt to use %s value as metatable name"):format(type(name)))
|
||||
assert(type(mt) == "table", ("attempt to register a %s value as metatable"):format(type(mt)))
|
||||
assert(known_metatables[name] == nil or known_metatables[name] == mt,
|
||||
("attempt to override metatable %s"):format(name))
|
||||
known_metatables[name] = mt
|
||||
known_metatables[mt] = name
|
||||
end
|
||||
core.known_metatables = known_metatables
|
||||
|
||||
function core.register_async_metatable(...)
|
||||
core.log("deprecated", "minetest.register_async_metatable is deprecated. " ..
|
||||
"Use minetest.register_portable_metatable instead.")
|
||||
return core.register_portable_metatable(...)
|
||||
end
|
||||
|
||||
core.register_portable_metatable("__builtin:vector", vector.metatable)
|
||||
@@ -240,12 +240,15 @@ function math.factorial(x)
|
||||
return v
|
||||
end
|
||||
|
||||
|
||||
function math.round(x)
|
||||
if x >= 0 then
|
||||
return math.floor(x + 0.5)
|
||||
if x < 0 then
|
||||
local int = math.ceil(x)
|
||||
local frac = x - int
|
||||
return int - ((frac <= -0.5) and 1 or 0)
|
||||
end
|
||||
return math.ceil(x - 0.5)
|
||||
local int = math.floor(x)
|
||||
local frac = x - int
|
||||
return int + ((frac >= 0.5) and 1 or 0)
|
||||
end
|
||||
|
||||
local formspec_escapes = {
|
||||
@@ -490,6 +493,9 @@ end
|
||||
|
||||
|
||||
function table.insert_all(t, other)
|
||||
if table.move then -- LuaJIT
|
||||
return table.move(other, 1, #other, #t + 1, t)
|
||||
end
|
||||
for i=1, #other do
|
||||
t[#t + 1] = other[i]
|
||||
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
|
||||
113
builtin/common/tests/after_spec.lua
Normal file
113
builtin/common/tests/after_spec.lua
Normal file
@@ -0,0 +1,113 @@
|
||||
_G.core = {}
|
||||
_G.vector = {metatable = {}}
|
||||
|
||||
dofile("builtin/common/vector.lua")
|
||||
dofile("builtin/common/misc_helpers.lua")
|
||||
|
||||
function core.get_last_run_mod() return "*test*" end
|
||||
function core.set_last_run_mod() end
|
||||
|
||||
local do_step
|
||||
function core.register_globalstep(func)
|
||||
do_step = func
|
||||
end
|
||||
|
||||
dofile("builtin/common/after.lua")
|
||||
|
||||
describe("after", function()
|
||||
it("executes callbacks when expected", function()
|
||||
local result = ""
|
||||
core.after(0, function()
|
||||
result = result .. "a"
|
||||
end)
|
||||
core.after(1, function()
|
||||
result = result .. "b"
|
||||
end)
|
||||
core.after(1, function()
|
||||
result = result .. "c"
|
||||
end)
|
||||
core.after(2, function()
|
||||
result = result .. "d"
|
||||
end)
|
||||
local cancel = core.after(2, function()
|
||||
result = result .. "e"
|
||||
end)
|
||||
do_step(0)
|
||||
assert.same("a", result)
|
||||
|
||||
do_step(1)
|
||||
assert.same("abc", result)
|
||||
|
||||
core.after(2, function()
|
||||
result = result .. "f"
|
||||
end)
|
||||
core.after(1, function()
|
||||
result = result .. "g"
|
||||
end)
|
||||
core.after(-1, function()
|
||||
result = result .. "h"
|
||||
end)
|
||||
cancel:cancel()
|
||||
do_step(1)
|
||||
assert.same("abchdg", result)
|
||||
|
||||
do_step(1)
|
||||
assert.same("abchdgf", result)
|
||||
end)
|
||||
|
||||
it("defers jobs with delay 0", function()
|
||||
local result = ""
|
||||
core.after(0, function()
|
||||
core.after(0, function()
|
||||
result = result .. "b"
|
||||
end)
|
||||
result = result .. "a"
|
||||
end)
|
||||
do_step(1)
|
||||
assert.same("a", result)
|
||||
do_step(1)
|
||||
assert.same("ab", result)
|
||||
end)
|
||||
|
||||
it("passes arguments", function()
|
||||
core.after(0, function(...)
|
||||
assert.same(0, select("#", ...))
|
||||
end)
|
||||
core.after(0, function(...)
|
||||
assert.same(4, select("#", ...))
|
||||
assert.same(1, (select(1, ...)))
|
||||
assert.same(nil, (select(2, ...)))
|
||||
assert.same("a", (select(3, ...)))
|
||||
assert.same(nil, (select(4, ...)))
|
||||
end, 1, nil, "a", nil)
|
||||
do_step(0)
|
||||
end)
|
||||
|
||||
it("rejects invalid arguments", function()
|
||||
assert.has.errors(function() core.after() end)
|
||||
assert.has.errors(function() core.after(nil, nil) end)
|
||||
assert.has.errors(function() core.after(0) end)
|
||||
assert.has.errors(function() core.after(0, nil) end)
|
||||
assert.has.errors(function() core.after(nil, function() end) end)
|
||||
assert.has.errors(function() core.after(0 / 0, function() end) end)
|
||||
end)
|
||||
|
||||
-- Make sure that the underlying heap is working correctly
|
||||
it("can be abused as a heapsort", function()
|
||||
local t = {}
|
||||
for i = 1, 1000 do
|
||||
t[i] = math.random(100)
|
||||
end
|
||||
local sorted = table.copy(t)
|
||||
table.sort(sorted)
|
||||
local i = 0
|
||||
for _, v in ipairs(t) do
|
||||
core.after(v, function()
|
||||
i = i + 1
|
||||
assert.equal(v, sorted[i])
|
||||
end)
|
||||
end
|
||||
do_step(math.max(unpack(t)))
|
||||
assert.equal(#t, i)
|
||||
end)
|
||||
end)
|
||||
@@ -176,3 +176,17 @@ describe("formspec_escape", function()
|
||||
assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\["))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("math", function()
|
||||
it("round()", function()
|
||||
assert.equal(0, math.round(0))
|
||||
assert.equal(10, math.round(10.3))
|
||||
assert.equal(11, math.round(10.5))
|
||||
assert.equal(11, math.round(10.7))
|
||||
assert.equal(-10, math.round(-10.3))
|
||||
assert.equal(-11, math.round(-10.5))
|
||||
assert.equal(-11, math.round(-10.7))
|
||||
assert.equal(0, math.round(0.49999999999999994))
|
||||
assert.equal(0, math.round(-0.49999999999999994))
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -462,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)
|
||||
|
||||
@@ -369,6 +369,12 @@ function vector.dir_to_rotation(forward, up)
|
||||
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
|
||||
|
||||
56
builtin/emerge/env.lua
Normal file
56
builtin/emerge/env.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Reimplementations of some environment function on vmanips, since this is
|
||||
-- what the emerge environment operates on
|
||||
|
||||
-- core.vmanip = <VoxelManip> -- set by C++
|
||||
|
||||
function core.set_node(pos, node)
|
||||
return core.vmanip:set_node_at(pos, node)
|
||||
end
|
||||
|
||||
function core.bulk_set_node(pos_list, node)
|
||||
local vm = core.vmanip
|
||||
local set_node_at = vm.set_node_at
|
||||
for _, pos in ipairs(pos_list) do
|
||||
if not set_node_at(vm, pos, node) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
core.add_node = core.set_node
|
||||
|
||||
-- we don't deal with metadata currently
|
||||
core.swap_node = core.set_node
|
||||
|
||||
function core.remove_node(pos)
|
||||
return core.vmanip:set_node_at(pos, {name="air"})
|
||||
end
|
||||
|
||||
function core.get_node(pos)
|
||||
return core.vmanip:get_node_at(pos)
|
||||
end
|
||||
|
||||
function core.get_perlin(seed, octaves, persist, spread)
|
||||
local params
|
||||
if type(seed) == "table" then
|
||||
params = table.copy(seed)
|
||||
else
|
||||
assert(type(seed) == "number")
|
||||
params = {
|
||||
seed = seed,
|
||||
octaves = octaves,
|
||||
persist = persist,
|
||||
spread = {x=spread, y=spread, z=spread},
|
||||
}
|
||||
end
|
||||
params.seed = core.get_seed(params.seed) -- add mapgen seed
|
||||
return PerlinNoise(params)
|
||||
end
|
||||
|
||||
|
||||
function core.get_perlin_map(params, size)
|
||||
local params2 = table.copy(params)
|
||||
params2.seed = core.get_seed(params.seed) -- add mapgen seed
|
||||
return PerlinNoiseMap(params2, size)
|
||||
end
|
||||
21
builtin/emerge/init.lua
Normal file
21
builtin/emerge/init.lua
Normal file
@@ -0,0 +1,21 @@
|
||||
local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM
|
||||
local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM
|
||||
local epath = core.get_builtin_path() .. "emerge" .. DIR_DELIM
|
||||
|
||||
local builtin_shared = {}
|
||||
|
||||
-- Import parts shared with "game" environment
|
||||
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")
|
||||
|
||||
-- Now for our own stuff
|
||||
assert(loadfile(commonpath .. "register.lua"))(builtin_shared)
|
||||
assert(loadfile(epath .. "register.lua"))(builtin_shared)
|
||||
dofile(epath .. "env.lua")
|
||||
|
||||
builtin_shared.cache_content_ids()
|
||||
|
||||
core.log("info", "Initialized emerge Lua environment")
|
||||
54
builtin/emerge/register.lua
Normal file
54
builtin/emerge/register.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
local builtin_shared = ...
|
||||
|
||||
-- Copy all the registration tables over
|
||||
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)
|
||||
|
||||
--
|
||||
-- Callbacks
|
||||
--
|
||||
|
||||
local make_registration = builtin_shared.make_registration
|
||||
|
||||
core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration()
|
||||
core.registered_on_generateds, core.register_on_generated = make_registration()
|
||||
core.registered_on_shutdown, core.register_on_shutdown = make_registration()
|
||||
@@ -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,113 @@
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
|
||||
local function buttonbar_formspec(self)
|
||||
local BASE_SPACING = 0.1
|
||||
local function get_scroll_btn_width()
|
||||
return core.settings:get_bool("enable_touch") and 0.8 or 0.5
|
||||
end
|
||||
|
||||
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*get_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 + get_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 - get_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, get_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, get_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
|
||||
return true
|
||||
if fields[self.btn_prev_name] then
|
||||
if self.cur_page > 1 then
|
||||
self.cur_page = self.cur_page - 1
|
||||
return true
|
||||
elseif self.cur_page == 1 then
|
||||
self.cur_page = self.num_pages
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if fields["btnbar_dec_" .. self.name] ~= nil and self.startbutton > 1 then
|
||||
self.startbutton = self.startbutton - 1
|
||||
return true
|
||||
if fields[self.btn_next_name] then
|
||||
if self.cur_page < self.num_pages then
|
||||
self.cur_page = self.cur_page + 1
|
||||
return true
|
||||
elseif self.cur_page == self.num_pages then
|
||||
self.cur_page = 1
|
||||
return true
|
||||
end
|
||||
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 +140,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()
|
||||
@@ -68,15 +78,12 @@ function dialog_create(name,get_formspec,buttonhandler,eventhandler)
|
||||
return self
|
||||
end
|
||||
|
||||
-- "message" must already be formspec-escaped, e.g. via fgettext or
|
||||
-- core.formspec_escape.
|
||||
function messagebox(name, message)
|
||||
return dialog_create(name,
|
||||
function()
|
||||
return ([[
|
||||
formspec_version[3]
|
||||
size[8,3]
|
||||
textarea[0.375,0.375;7.25,1.2;;;%s]
|
||||
button[3,1.825;2,0.8;ok;%s]
|
||||
]]):format(message, fgettext("OK"))
|
||||
return ui.get_message_formspec("", message, "ok")
|
||||
end,
|
||||
function(this, fields)
|
||||
if fields.ok then
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -50,6 +50,20 @@ function ui.find_by_name(name)
|
||||
return ui.childlist[name]
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- "title" and "message" must already be formspec-escaped, e.g. via fgettext or
|
||||
-- core.formspec_escape.
|
||||
function ui.get_message_formspec(title, message, btn_id)
|
||||
return table.concat({
|
||||
"size[14,8]",
|
||||
"real_coordinates[true]",
|
||||
"set_focus[", btn_id, ";true]",
|
||||
"box[0.5,1.2;13,5;#000]",
|
||||
("textarea[0.5,1.2;13,5;;%s;%s]"):format(title, message),
|
||||
"button[5,6.6;4,1;", btn_id, ";" .. fgettext("OK") .. "]",
|
||||
})
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
-- Internal functions not to be called from user
|
||||
@@ -62,8 +76,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]",
|
||||
@@ -76,6 +90,9 @@ function ui.update()
|
||||
}
|
||||
ui.overridden = true
|
||||
elseif gamedata ~= nil and gamedata.errormessage ~= nil then
|
||||
-- Note to API users:
|
||||
-- "gamedata.errormessage" must not be formspec-escaped yet.
|
||||
-- For translations, fgettext_ne should be used.
|
||||
local error_message = core.formspec_escape(gamedata.errormessage)
|
||||
|
||||
local error_title
|
||||
@@ -84,15 +101,7 @@ function ui.update()
|
||||
else
|
||||
error_title = fgettext("An error occurred:")
|
||||
end
|
||||
formspec = {
|
||||
"size[14,8]",
|
||||
"real_coordinates[true]",
|
||||
"set_focus[btn_error_confirm;true]",
|
||||
"box[0.5,1.2;13,5;#000]",
|
||||
("textarea[0.5,1.2;13,5;;%s;%s]"):format(
|
||||
error_title, error_message),
|
||||
"button[5,6.6;4,1;btn_error_confirm;" .. fgettext("OK") .. "]"
|
||||
}
|
||||
formspec = {ui.get_message_formspec(error_title, error_message, "btn_error_confirm")}
|
||||
ui.overridden = true
|
||||
else
|
||||
local active_toplevel_ui_elements = 0
|
||||
|
||||
@@ -87,20 +87,29 @@ core.builtin_auth_handler = {
|
||||
core.settings:get("default_password")))
|
||||
end
|
||||
|
||||
local prev_privs = auth_entry.privileges
|
||||
auth_entry.privileges = privileges
|
||||
|
||||
core_auth.save(auth_entry)
|
||||
|
||||
-- Run grant callbacks
|
||||
for priv, _ in pairs(privileges) do
|
||||
if not auth_entry.privileges[priv] then
|
||||
for priv, value in pairs(privileges) do
|
||||
-- Warnings for improper API usage
|
||||
if value == false then
|
||||
core.log('deprecated', "`false` value given to `minetest.set_player_privs`, "..
|
||||
"this is almost certainly a bug, "..
|
||||
"granting a privilege rather than revoking it")
|
||||
elseif value ~= true then
|
||||
core.log('deprecated', "non-`true` value given to `minetest.set_player_privs`")
|
||||
end
|
||||
-- Run grant callbacks
|
||||
if prev_privs[priv] == nil then
|
||||
core.run_priv_callbacks(name, priv, nil, "grant")
|
||||
end
|
||||
end
|
||||
|
||||
-- Run revoke callbacks
|
||||
for priv, _ in pairs(auth_entry.privileges) do
|
||||
if not privileges[priv] then
|
||||
for priv, _ in pairs(prev_privs) do
|
||||
if privileges[priv] == nil then
|
||||
core.run_priv_callbacks(name, priv, nil, "revoke")
|
||||
end
|
||||
end
|
||||
@@ -179,6 +188,20 @@ core.set_player_privs = auth_pass("set_privileges")
|
||||
core.remove_player_auth = auth_pass("delete_auth")
|
||||
core.auth_reload = auth_pass("reload")
|
||||
|
||||
function core.change_player_privs(name, changes)
|
||||
local privs = core.get_player_privs(name)
|
||||
for priv, change in pairs(changes) do
|
||||
if change == true then
|
||||
privs[priv] = true
|
||||
elseif change == false then
|
||||
privs[priv] = nil
|
||||
else
|
||||
error("non-bool value given to `minetest.change_player_privs`")
|
||||
end
|
||||
end
|
||||
core.set_player_privs(name, privs)
|
||||
end
|
||||
|
||||
local record_login = auth_pass("record_login")
|
||||
core.register_on_joinplayer(function(player)
|
||||
record_login(player:get_player_name())
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
core.detached_inventories = {}
|
||||
|
||||
local create_detached_inventory_raw = core.create_detached_inventory_raw
|
||||
core.create_detached_inventory_raw = nil
|
||||
|
||||
function core.create_detached_inventory(name, callbacks, player_name)
|
||||
local stuff = {}
|
||||
stuff.name = name
|
||||
@@ -15,10 +18,13 @@ function core.create_detached_inventory(name, callbacks, player_name)
|
||||
end
|
||||
stuff.mod_origin = core.get_current_modname() or "??"
|
||||
core.detached_inventories[name] = stuff
|
||||
return core.create_detached_inventory_raw(name, player_name)
|
||||
return create_detached_inventory_raw(name, player_name)
|
||||
end
|
||||
|
||||
local remove_detached_inventory_raw = core.remove_detached_inventory_raw
|
||||
core.remove_detached_inventory_raw = nil
|
||||
|
||||
function core.remove_detached_inventory(name)
|
||||
core.detached_inventories[name] = nil
|
||||
return core.remove_detached_inventory_raw(name)
|
||||
return remove_detached_inventory_raw(name)
|
||||
end
|
||||
|
||||
@@ -79,6 +79,9 @@ core.register_entity(":__builtin:falling_node", {
|
||||
-- Cache whether we're supposed to float on water
|
||||
self.floats = core.get_item_group(node.name, "float") ~= 0
|
||||
|
||||
-- Save liquidtype for falling water
|
||||
self.liquidtype = def.liquidtype
|
||||
|
||||
-- Set entity visuals
|
||||
if def.drawtype == "torchlike" or def.drawtype == "signlike" then
|
||||
local textures
|
||||
@@ -150,7 +153,12 @@ core.register_entity(":__builtin:falling_node", {
|
||||
|
||||
-- Rotate entity
|
||||
if def.drawtype == "torchlike" then
|
||||
self.object:set_yaw(math.pi*0.25)
|
||||
if (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted")
|
||||
and node.param2 % 8 == 7 then
|
||||
self.object:set_yaw(-math.pi*0.25)
|
||||
else
|
||||
self.object:set_yaw(math.pi*0.25)
|
||||
end
|
||||
elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
|
||||
and (def.wield_image == "" or def.wield_image == nil))
|
||||
or def.drawtype == "signlike"
|
||||
@@ -190,6 +198,10 @@ core.register_entity(":__builtin:falling_node", {
|
||||
pitch, yaw = 0, -math.pi/2
|
||||
elseif rot == 4 then
|
||||
pitch, yaw = 0, math.pi
|
||||
elseif rot == 6 then
|
||||
pitch, yaw = math.pi/2, 0
|
||||
elseif rot == 7 then
|
||||
pitch, yaw = -math.pi/2, math.pi
|
||||
end
|
||||
else
|
||||
if rot == 1 then
|
||||
@@ -202,6 +214,10 @@ core.register_entity(":__builtin:falling_node", {
|
||||
pitch, yaw = math.pi/2, math.pi
|
||||
elseif rot == 5 then
|
||||
pitch, yaw = math.pi/2, 0
|
||||
elseif rot == 6 then
|
||||
pitch, yaw = math.pi, -math.pi/2
|
||||
elseif rot == 7 then
|
||||
pitch, yaw = 0, -math.pi/2
|
||||
end
|
||||
end
|
||||
if def.drawtype == "signlike" then
|
||||
@@ -210,10 +226,20 @@ core.register_entity(":__builtin:falling_node", {
|
||||
yaw = yaw + math.pi/2
|
||||
elseif rot == 1 then
|
||||
yaw = yaw - math.pi/2
|
||||
elseif rot == 6 then
|
||||
yaw = yaw - math.pi/2
|
||||
pitch = pitch + math.pi
|
||||
elseif rot == 7 then
|
||||
yaw = yaw + math.pi/2
|
||||
pitch = pitch + math.pi
|
||||
end
|
||||
elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then
|
||||
if rot >= 0 and rot <= 1 then
|
||||
if rot == 0 or rot == 1 then
|
||||
roll = roll + math.pi
|
||||
elseif rot == 6 or rot == 7 then
|
||||
if def.drawtype ~= "normal" then
|
||||
roll = roll - math.pi/2
|
||||
end
|
||||
else
|
||||
yaw = yaw + math.pi
|
||||
end
|
||||
@@ -271,9 +297,17 @@ core.register_entity(":__builtin:falling_node", {
|
||||
end
|
||||
|
||||
-- Decide if we're replacing the node or placing on top
|
||||
-- This condition is very similar to the check in core.check_single_for_falling(p)
|
||||
local np = vector.copy(bcp)
|
||||
if bcd and bcd.buildable_to and
|
||||
(not self.floats or bcd.liquidtype == "none") then
|
||||
if bcd and bcd.buildable_to
|
||||
and -- Take "float" group into consideration:
|
||||
(
|
||||
-- Fall through non-liquids
|
||||
not self.floats or bcd.liquidtype == "none" or
|
||||
-- Only let sources fall through flowing liquids
|
||||
(self.floats and self.liquidtype ~= "none" and bcd.liquidtype ~= "source")
|
||||
) then
|
||||
|
||||
core.remove_node(bcp)
|
||||
else
|
||||
np.y = np.y + 1
|
||||
@@ -284,7 +318,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
local nd = core.registered_nodes[n2.name]
|
||||
-- If it's not air or liquid, remove node and replace it with
|
||||
-- it's drops
|
||||
if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
|
||||
if n2.name ~= "air" and (not nd or nd.liquidtype ~= "source") then
|
||||
if nd and nd.buildable_to == false then
|
||||
nd.on_dig(np, n2, nil)
|
||||
-- If it's still there, it might be protected
|
||||
@@ -529,16 +563,24 @@ 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
|
||||
local d_falling = core.registered_nodes[n.name]
|
||||
local do_float = core.get_item_group(n.name, "float") > 0
|
||||
-- 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
|
||||
(not d_bottom.walkable or d_bottom.buildable_to)
|
||||
and -- Take "float" group into consideration:
|
||||
(
|
||||
-- Fall through non-liquids
|
||||
not do_float or d_bottom.liquidtype == "none" or
|
||||
-- Only let sources fall through flowing liquids
|
||||
(do_float and d_falling.liquidtype == "source" and d_bottom.liquidtype ~= "source")
|
||||
) then
|
||||
|
||||
local success, _ = convert_to_falling_node(p, n)
|
||||
return success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,6 +27,21 @@ core.features = {
|
||||
get_light_data_buffer = true,
|
||||
mod_storage_on_disk = true,
|
||||
compress_zstd = true,
|
||||
sound_params_start_time = true,
|
||||
physics_overrides_v2 = true,
|
||||
hud_def_type_field = true,
|
||||
random_state_restore = true,
|
||||
after_order_expiry_registration = true,
|
||||
wallmounted_rotate = true,
|
||||
item_specific_pointabilities = true,
|
||||
blocking_pointability_type = true,
|
||||
dynamic_add_media_startup = true,
|
||||
dynamic_add_media_filepath = true,
|
||||
lsystem_decoration_type = true,
|
||||
item_meta_range = true,
|
||||
node_interaction_actor = true,
|
||||
moveresult_new_pos = true,
|
||||
override_item_remove_fields = true,
|
||||
}
|
||||
|
||||
function core.has_feature(arg)
|
||||
|
||||
266
builtin/game/hud.lua
Normal file
266
builtin/game/hud.lua
Normal file
@@ -0,0 +1,266 @@
|
||||
--[[
|
||||
Register function to easily register new builtin hud elements
|
||||
`def` is a table and contains the following fields:
|
||||
elem_def the HUD element definition which can be changed with hud_replace_builtin
|
||||
events (optional) additional event names on which the element will be updated
|
||||
("hud_changed" will always be used.)
|
||||
show_elem(player, flags, id)
|
||||
(optional) a function to decide if the element should be shown to a player
|
||||
It is called before the element gets updated.
|
||||
update_def(player, elem_def)
|
||||
(optional) a function to change the elem_def before it will be used.
|
||||
(elem_def can be changed, since the table which got set by using
|
||||
hud_replace_builtin isn't exposed to the API.)
|
||||
update_elem(player, id)
|
||||
(optional) a function to change the element after it has been updated
|
||||
(Is not called when the element is first set or recreated.)
|
||||
]]--
|
||||
|
||||
local registered_elements = {}
|
||||
local update_events = {}
|
||||
local function register_builtin_hud_element(name, def)
|
||||
registered_elements[name] = def
|
||||
for _, event in ipairs(def.events or {}) do
|
||||
update_events[event] = update_events[event] or {}
|
||||
table.insert(update_events[event], name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Stores HUD ids for all players
|
||||
local hud_ids = {}
|
||||
|
||||
-- Updates one element
|
||||
-- In case the element is already added, it only calls the update_elem function from
|
||||
-- registered_elements. (To recreate the element remove it first.)
|
||||
local function update_element(player, player_hud_ids, elem_name, flags)
|
||||
local def = registered_elements[elem_name]
|
||||
local id = player_hud_ids[elem_name]
|
||||
|
||||
if def.show_elem and not def.show_elem(player, flags, id) then
|
||||
if id then
|
||||
player:hud_remove(id)
|
||||
player_hud_ids[elem_name] = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if not id then
|
||||
if def.update_def then
|
||||
def.update_def(player, def.elem_def)
|
||||
end
|
||||
|
||||
id = player:hud_add(def.elem_def)
|
||||
player_hud_ids[elem_name] = id
|
||||
return
|
||||
end
|
||||
|
||||
if def.update_elem then
|
||||
def.update_elem(player, id)
|
||||
end
|
||||
end
|
||||
|
||||
-- Updates all elements
|
||||
-- If to_update is specified it will only update those elements.
|
||||
local function update_hud(player, to_update)
|
||||
local flags = player:hud_get_flags()
|
||||
local playername = player:get_player_name()
|
||||
hud_ids[playername] = hud_ids[playername] or {}
|
||||
local player_hud_ids = hud_ids[playername]
|
||||
|
||||
if to_update then
|
||||
for _, elem_name in ipairs(to_update) do
|
||||
update_element(player, player_hud_ids, elem_name, flags)
|
||||
end
|
||||
else
|
||||
for elem_name, _ in pairs(registered_elements) do
|
||||
update_element(player, player_hud_ids, elem_name, flags)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function player_event_handler(player, eventname)
|
||||
assert(player:is_player())
|
||||
|
||||
if eventname == "hud_changed" then
|
||||
update_hud(player)
|
||||
return
|
||||
end
|
||||
|
||||
-- Custom events
|
||||
local to_update = update_events[eventname]
|
||||
if to_update then
|
||||
update_hud(player, to_update)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns true if successful, otherwise false,
|
||||
-- but currently the return value is not documented in the Lua API.
|
||||
function core.hud_replace_builtin(elem_name, elem_def)
|
||||
assert(type(elem_def) == "table")
|
||||
|
||||
local registered = registered_elements[elem_name]
|
||||
if not registered then
|
||||
return false
|
||||
end
|
||||
|
||||
registered.elem_def = table.copy(elem_def)
|
||||
|
||||
for playername, player_hud_ids in pairs(hud_ids) do
|
||||
local player = core.get_player_by_name(playername)
|
||||
local id = player_hud_ids[elem_name]
|
||||
if player and id then
|
||||
player:hud_remove(id)
|
||||
player_hud_ids[elem_name] = nil
|
||||
update_element(player, player_hud_ids, elem_name, player:hud_get_flags())
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function cleanup_builtin_hud(player)
|
||||
hud_ids[player:get_player_name()] = nil
|
||||
end
|
||||
|
||||
|
||||
-- Append "update_hud" as late as possible
|
||||
-- This ensures that the HUD is hidden when the flags are updated in this callback
|
||||
core.register_on_mods_loaded(function()
|
||||
core.register_on_joinplayer(function(player)
|
||||
update_hud(player)
|
||||
end)
|
||||
end)
|
||||
core.register_on_leaveplayer(cleanup_builtin_hud)
|
||||
core.register_playerevent(player_event_handler)
|
||||
|
||||
|
||||
---- Builtin HUD Elements
|
||||
|
||||
--- Healthbar
|
||||
|
||||
-- Cache setting
|
||||
local enable_damage = core.settings:get_bool("enable_damage")
|
||||
|
||||
local function scale_to_hud_max(player, field)
|
||||
-- Scale "hp" or "breath" to the hud maximum dimensions
|
||||
local current = player["get_" .. field](player)
|
||||
local nominal
|
||||
if field == "hp" then -- HUD is called health but field is hp
|
||||
nominal = registered_elements.health.elem_def.item
|
||||
else
|
||||
nominal = registered_elements[field].elem_def.item
|
||||
end
|
||||
local max_display = math.max(player:get_properties()[field .. "_max"], current)
|
||||
return math.ceil(current / max_display * nominal)
|
||||
end
|
||||
|
||||
register_builtin_hud_element("health", {
|
||||
elem_def = {
|
||||
type = "statbar",
|
||||
position = {x = 0.5, y = 1},
|
||||
text = "heart.png",
|
||||
text2 = "heart_gone.png",
|
||||
number = core.PLAYER_MAX_HP_DEFAULT,
|
||||
item = core.PLAYER_MAX_HP_DEFAULT,
|
||||
direction = 0,
|
||||
size = {x = 24, y = 24},
|
||||
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
|
||||
},
|
||||
events = {"properties_changed", "health_changed"},
|
||||
show_elem = function(player, flags)
|
||||
return flags.healthbar and enable_damage and
|
||||
player:get_armor_groups().immortal ~= 1
|
||||
end,
|
||||
update_def = function(player, elem_def)
|
||||
elem_def.item = elem_def.item or elem_def.number or core.PLAYER_MAX_HP_DEFAULT
|
||||
elem_def.number = scale_to_hud_max(player, "hp")
|
||||
end,
|
||||
update_elem = function(player, id)
|
||||
player:hud_change(id, "number", scale_to_hud_max(player, "hp"))
|
||||
end,
|
||||
})
|
||||
|
||||
--- Breathbar
|
||||
|
||||
-- Stores core.after calls for every player
|
||||
local breathbar_removal_jobs = {}
|
||||
|
||||
register_builtin_hud_element("breath", {
|
||||
elem_def = {
|
||||
type = "statbar",
|
||||
position = {x = 0.5, y = 1},
|
||||
text = "bubble.png",
|
||||
text2 = "bubble_gone.png",
|
||||
number = core.PLAYER_MAX_BREATH_DEFAULT * 2,
|
||||
item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
|
||||
direction = 0,
|
||||
size = {x = 24, y = 24},
|
||||
offset = {x = 25, y= -(48 + 24 + 16)},
|
||||
},
|
||||
events = {"properties_changed", "breath_changed"},
|
||||
show_elem = function(player, flags, id)
|
||||
local show_breathbar = flags.breathbar and enable_damage and
|
||||
player:get_armor_groups().immortal ~= 1
|
||||
if id then
|
||||
-- The element will not prematurely be removed by update_element
|
||||
-- (but may still be instantly removed if the flag changed)
|
||||
return show_breathbar
|
||||
end
|
||||
-- Don't add the element if the breath is full
|
||||
local breath_relevant = player:get_breath() < player:get_properties().breath_max
|
||||
return show_breathbar and breath_relevant
|
||||
end,
|
||||
update_def = function(player, elem_def)
|
||||
elem_def.item = elem_def.item or elem_def.number or core.PLAYER_MAX_BREATH_DEFAULT
|
||||
elem_def.number = scale_to_hud_max(player, "breath")
|
||||
end,
|
||||
update_elem = function(player, id)
|
||||
player:hud_change(id, "number", scale_to_hud_max(player, "breath"))
|
||||
|
||||
local player_name = player:get_player_name()
|
||||
local breath_relevant = player:get_breath() < player:get_properties().breath_max
|
||||
|
||||
if not breath_relevant then
|
||||
if not breathbar_removal_jobs[player_name] then
|
||||
-- The breathbar stays for some time and then gets removed.
|
||||
breathbar_removal_jobs[player_name] = core.after(1, function()
|
||||
local player = core.get_player_by_name(player_name)
|
||||
local player_hud_ids = hud_ids[player_name]
|
||||
if player and player_hud_ids and player_hud_ids.breath then
|
||||
player:hud_remove(player_hud_ids.breath)
|
||||
player_hud_ids.breath = nil
|
||||
end
|
||||
breathbar_removal_jobs[player_name] = nil
|
||||
end)
|
||||
end
|
||||
else
|
||||
-- Cancel removal
|
||||
local job = breathbar_removal_jobs[player_name]
|
||||
if job then
|
||||
job:cancel()
|
||||
breathbar_removal_jobs[player_name] = nil
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--- Minimap
|
||||
|
||||
register_builtin_hud_element("minimap", {
|
||||
elem_def = {
|
||||
type = "minimap",
|
||||
position = {x = 1, y = 0},
|
||||
alignment = {x = -1, y = 1},
|
||||
offset = {x = -10, y = 10},
|
||||
size = {x = 0, y = -25},
|
||||
},
|
||||
show_elem = function(player, flags)
|
||||
local proto_ver = core.get_player_information(player:get_player_name()).protocol_version
|
||||
-- Don't add a minimap for clients which already have it hardcoded in C++.
|
||||
return flags.minimap and proto_ver >= 44
|
||||
end,
|
||||
update_def = function(player, elem_def)
|
||||
local proto_ver = core.get_player_information(player:get_player_name()).protocol_version
|
||||
-- Only use percentage when the client supports it.
|
||||
elem_def.size = proto_ver >= 45 and {x = 0, y = -25} or {x = 256, y = 256}
|
||||
end,
|
||||
})
|
||||
@@ -10,13 +10,15 @@ 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 .. "metatable.lua")
|
||||
dofile(commonpath .. "mod_storage.lua")
|
||||
dofile(gamepath .. "item_entity.lua")
|
||||
dofile(gamepath .. "deprecated.lua")
|
||||
@@ -33,7 +35,7 @@ assert(loadfile(gamepath .. "falling.lua"))(builtin_shared)
|
||||
dofile(gamepath .. "features.lua")
|
||||
dofile(gamepath .. "voxelarea.lua")
|
||||
dofile(gamepath .. "forceloading.lua")
|
||||
dofile(gamepath .. "statbars.lua")
|
||||
dofile(gamepath .. "hud.lua")
|
||||
dofile(gamepath .. "knockback.lua")
|
||||
dofile(gamepath .. "async.lua")
|
||||
|
||||
|
||||
@@ -202,7 +202,40 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
elseif (def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted") and not param2 then
|
||||
local dir = vector.subtract(under, above)
|
||||
-- If you change this code, also change src/client/game.cpp
|
||||
newnode.param2 = core.dir_to_wallmounted(dir)
|
||||
if def.wallmounted_rotate_vertical and
|
||||
(newnode.param2 == 0 or newnode.param2 == 1) then
|
||||
local placer_pos = placer and placer:get_pos()
|
||||
if placer_pos then
|
||||
local pdir = {
|
||||
x = above.x - placer_pos.x,
|
||||
y = dir.y,
|
||||
z = above.z - placer_pos.z
|
||||
}
|
||||
local rotate = false
|
||||
if def.drawtype == "torchlike" then
|
||||
if not ((pdir.x < 0 and pdir.z > 0) or
|
||||
(pdir.x > 0 and pdir.z < 0)) then
|
||||
rotate = true
|
||||
end
|
||||
if pdir.y > 0 then
|
||||
rotate = not rotate
|
||||
end
|
||||
elseif def.drawtype == "signlike" then
|
||||
if math.abs(pdir.x) < math.abs(pdir.z) then
|
||||
rotate = true
|
||||
end
|
||||
else
|
||||
if math.abs(pdir.x) > math.abs(pdir.z) then
|
||||
rotate = true
|
||||
end
|
||||
end
|
||||
if rotate then
|
||||
newnode.param2 = newnode.param2 + 6
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir" or
|
||||
@@ -703,3 +736,22 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
|
||||
on_drop = nil,
|
||||
on_use = nil,
|
||||
}
|
||||
|
||||
--
|
||||
-- get_node implementation
|
||||
--
|
||||
|
||||
local get_node_raw = core.get_node_raw
|
||||
core.get_node_raw = nil
|
||||
|
||||
function core.get_node(pos)
|
||||
local content, param1, param2 = get_node_raw(pos.x, pos.y, pos.z)
|
||||
return {name = core.get_name_from_content_id(content), param1 = param1, param2 = param2}
|
||||
end
|
||||
|
||||
function core.get_node_or_nil(pos)
|
||||
local content, param1, param2, pos_ok = get_node_raw(pos.x, pos.y, pos.z)
|
||||
return pos_ok and
|
||||
{name = core.get_name_from_content_id(content), param1 = param1, param2 = param2}
|
||||
or nil
|
||||
end
|
||||
|
||||
@@ -69,6 +69,7 @@ core.register_entity(":__builtin:item", {
|
||||
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
|
||||
@@ -178,6 +179,11 @@ 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
|
||||
|
||||
@@ -22,14 +22,16 @@ local function vector_absmax(v)
|
||||
return max(max(abs(v.x), abs(v.y)), abs(v.z))
|
||||
end
|
||||
|
||||
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, unused_dir, damage)
|
||||
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
if player:get_hp() == 0 then
|
||||
return -- RIP
|
||||
end
|
||||
|
||||
-- Server::handleCommand_Interact() adds eye offset to one but not the other
|
||||
-- so the direction is slightly off, calculate it ourselves
|
||||
local dir = vector.subtract(player:get_pos(), hitter:get_pos())
|
||||
if hitter then
|
||||
-- Server::handleCommand_Interact() adds eye offset to one but not the other
|
||||
-- so the direction is slightly off, calculate it ourselves
|
||||
dir = vector.subtract(player:get_pos(), hitter:get_pos())
|
||||
end
|
||||
local d = vector.length(dir)
|
||||
if d ~= 0.0 then
|
||||
dir = vector.divide(dir, d)
|
||||
|
||||
@@ -237,8 +237,8 @@ end
|
||||
core.dynamic_media_callbacks = {}
|
||||
|
||||
|
||||
-- Transfer of certain globals into async environment
|
||||
-- see builtin/async/game.lua for the other side
|
||||
-- Transfer of certain globals into seconday Lua environments
|
||||
-- see builtin/async/game.lua or builtin/emerge/register.lua for the unpacking
|
||||
|
||||
local function copy_filtering(t, seen)
|
||||
if type(t) == "userdata" or type(t) == "function" then
|
||||
@@ -261,6 +261,40 @@ function core.get_globals_to_transfer()
|
||||
local all = {
|
||||
registered_items = copy_filtering(core.registered_items),
|
||||
registered_aliases = core.registered_aliases,
|
||||
registered_biomes = core.registered_biomes,
|
||||
registered_ores = core.registered_ores,
|
||||
registered_decorations = core.registered_decorations,
|
||||
|
||||
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
|
||||
|
||||
do
|
||||
local function valid_object_iterator(objects)
|
||||
local i = 0
|
||||
local function next_valid_object()
|
||||
i = i + 1
|
||||
local obj = objects[i]
|
||||
if obj == nil then
|
||||
return
|
||||
end
|
||||
if obj:is_valid() then
|
||||
return obj
|
||||
end
|
||||
return next_valid_object()
|
||||
end
|
||||
return next_valid_object
|
||||
end
|
||||
|
||||
function core.objects_inside_radius(center, radius)
|
||||
return valid_object_iterator(core.get_objects_inside_radius(center, radius))
|
||||
end
|
||||
|
||||
function core.objects_in_area(min_pos, max_pos)
|
||||
return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -64,6 +64,13 @@ function core.encode_png(width, height, data, compression)
|
||||
error("Incorrect type for 'height', expected number, got " .. type(height))
|
||||
end
|
||||
|
||||
if width < 1 then
|
||||
error("Incorrect value for 'width', must be at least 1")
|
||||
end
|
||||
if height < 1 then
|
||||
error("Incorrect value for 'height', must be at least 1")
|
||||
end
|
||||
|
||||
local expected_byte_count = width * height * 4
|
||||
|
||||
if type(data) ~= "table" and type(data) ~= "string" then
|
||||
@@ -90,3 +97,26 @@ function core.encode_png(width, height, data, compression)
|
||||
|
||||
return o_encode_png(width, height, data, compression or 6)
|
||||
end
|
||||
|
||||
-- Helper that pushes a collisionMoveResult structure
|
||||
if core.set_push_moveresult1 then
|
||||
-- must match CollisionAxis in collision.h
|
||||
local AXES = {"x", "y", "z"}
|
||||
-- <=> script/common/c_content.cpp push_collision_move_result()
|
||||
core.set_push_moveresult1(function(b0, b1, b2, axis, npx, npy, npz, v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, v2z)
|
||||
return {
|
||||
touching_ground = b0,
|
||||
collides = b1,
|
||||
standing_on_object = b2,
|
||||
collisions = {{
|
||||
type = "node",
|
||||
axis = AXES[axis + 1],
|
||||
node_pos = vector.new(npx, npy, npz),
|
||||
new_pos = vector.new(v0x, v0y, v0z),
|
||||
old_velocity = vector.new(v1x, v1y, v1z),
|
||||
new_velocity = vector.new(v2x, v2y, v2z),
|
||||
}},
|
||||
}
|
||||
end)
|
||||
core.set_push_moveresult1 = nil
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
-- Minetest: builtin/register.lua
|
||||
|
||||
local builtin_shared = ...
|
||||
local S = core.get_translator("__builtin")
|
||||
|
||||
--
|
||||
@@ -403,7 +404,7 @@ core.register_item(":", {
|
||||
})
|
||||
|
||||
|
||||
function core.override_item(name, redefinition)
|
||||
function core.override_item(name, redefinition, del_fields)
|
||||
if redefinition.name ~= nil then
|
||||
error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2)
|
||||
end
|
||||
@@ -417,58 +418,12 @@ function core.override_item(name, redefinition)
|
||||
for k, v in pairs(redefinition) do
|
||||
rawset(item, k, v)
|
||||
end
|
||||
for _, field in ipairs(del_fields or {}) do
|
||||
rawset(item, field, nil)
|
||||
end
|
||||
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
|
||||
@@ -485,34 +440,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 = {}
|
||||
|
||||
@@ -600,6 +527,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()
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
-- cache setting
|
||||
local enable_damage = core.settings:get_bool("enable_damage")
|
||||
|
||||
local bar_definitions = {
|
||||
hp = {
|
||||
hud_elem_type = "statbar",
|
||||
position = {x = 0.5, y = 1},
|
||||
text = "heart.png",
|
||||
text2 = "heart_gone.png",
|
||||
number = core.PLAYER_MAX_HP_DEFAULT,
|
||||
item = core.PLAYER_MAX_HP_DEFAULT,
|
||||
direction = 0,
|
||||
size = {x = 24, y = 24},
|
||||
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
|
||||
},
|
||||
breath = {
|
||||
hud_elem_type = "statbar",
|
||||
position = {x = 0.5, y = 1},
|
||||
text = "bubble.png",
|
||||
text2 = "bubble_gone.png",
|
||||
number = core.PLAYER_MAX_BREATH_DEFAULT * 2,
|
||||
item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
|
||||
direction = 0,
|
||||
size = {x = 24, y = 24},
|
||||
offset = {x = 25, y= -(48 + 24 + 16)},
|
||||
},
|
||||
}
|
||||
|
||||
local hud_ids = {}
|
||||
|
||||
local function scaleToHudMax(player, field)
|
||||
-- Scale "hp" or "breath" to the hud maximum dimensions
|
||||
local current = player["get_" .. field](player)
|
||||
local nominal = bar_definitions[field].item
|
||||
local max_display = math.max(player:get_properties()[field .. "_max"], current)
|
||||
return math.ceil(current / max_display * nominal)
|
||||
end
|
||||
|
||||
local function update_builtin_statbars(player)
|
||||
local name = player:get_player_name()
|
||||
|
||||
if name == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local flags = player:hud_get_flags()
|
||||
if not hud_ids[name] then
|
||||
hud_ids[name] = {}
|
||||
-- flags are not transmitted to client on connect, we need to make sure
|
||||
-- our current flags are transmitted by sending them actively
|
||||
player:hud_set_flags(flags)
|
||||
end
|
||||
local hud = hud_ids[name]
|
||||
|
||||
local immortal = player:get_armor_groups().immortal == 1
|
||||
|
||||
if flags.healthbar and enable_damage and not immortal then
|
||||
local number = scaleToHudMax(player, "hp")
|
||||
if hud.id_healthbar == nil then
|
||||
local hud_def = table.copy(bar_definitions.hp)
|
||||
hud_def.number = number
|
||||
hud.id_healthbar = player:hud_add(hud_def)
|
||||
else
|
||||
player:hud_change(hud.id_healthbar, "number", number)
|
||||
end
|
||||
elseif hud.id_healthbar then
|
||||
player:hud_remove(hud.id_healthbar)
|
||||
hud.id_healthbar = nil
|
||||
end
|
||||
|
||||
local show_breathbar = flags.breathbar and enable_damage and not immortal
|
||||
|
||||
local breath = player:get_breath()
|
||||
local breath_max = player:get_properties().breath_max
|
||||
if show_breathbar and breath <= breath_max then
|
||||
local number = scaleToHudMax(player, "breath")
|
||||
if not hud.id_breathbar and breath < breath_max then
|
||||
local hud_def = table.copy(bar_definitions.breath)
|
||||
hud_def.number = number
|
||||
hud.id_breathbar = player:hud_add(hud_def)
|
||||
elseif hud.id_breathbar then
|
||||
player:hud_change(hud.id_breathbar, "number", number)
|
||||
end
|
||||
end
|
||||
|
||||
if hud.id_breathbar and (not show_breathbar or breath == breath_max) then
|
||||
core.after(1, function(player_name, breath_bar)
|
||||
local player = core.get_player_by_name(player_name)
|
||||
if player then
|
||||
player:hud_remove(breath_bar)
|
||||
end
|
||||
end, name, hud.id_breathbar)
|
||||
hud.id_breathbar = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function cleanup_builtin_statbars(player)
|
||||
local name = player:get_player_name()
|
||||
|
||||
if name == "" then
|
||||
return
|
||||
end
|
||||
|
||||
hud_ids[name] = nil
|
||||
end
|
||||
|
||||
local function player_event_handler(player,eventname)
|
||||
assert(player:is_player())
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
if name == "" or not hud_ids[name] then
|
||||
return
|
||||
end
|
||||
|
||||
if eventname == "health_changed" then
|
||||
update_builtin_statbars(player)
|
||||
|
||||
if hud_ids[name].id_healthbar then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if eventname == "breath_changed" then
|
||||
update_builtin_statbars(player)
|
||||
|
||||
if hud_ids[name].id_breathbar then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if eventname == "hud_changed" or eventname == "properties_changed" then
|
||||
update_builtin_statbars(player)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
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
|
||||
local player = core.get_player_by_name(name)
|
||||
if player and ids.id_healthbar then
|
||||
player:hud_remove(ids.id_healthbar)
|
||||
ids.id_healthbar = nil
|
||||
update_builtin_statbars(player)
|
||||
end
|
||||
end
|
||||
return true
|
||||
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
|
||||
local player = core.get_player_by_name(name)
|
||||
if player and ids.id_breathbar then
|
||||
player:hud_remove(ids.id_breathbar)
|
||||
ids.id_breathbar = nil
|
||||
update_builtin_statbars(player)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Append "update_builtin_statbars" as late as possible
|
||||
-- This ensures that the HUD is hidden when the flags are updated in this callback
|
||||
core.register_on_mods_loaded(function()
|
||||
core.register_on_joinplayer(update_builtin_statbars)
|
||||
end)
|
||||
core.register_on_leaveplayer(cleanup_builtin_statbars)
|
||||
core.register_playerevent(player_event_handler)
|
||||
84
builtin/game/tests/test_moveaction.lua
Normal file
84
builtin/game/tests/test_moveaction.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
-- Table to keep track of callback executions
|
||||
-- [i + 0] = count of expected patterns of index (i + 1)
|
||||
-- [i + 1] = pattern to check
|
||||
local PATTERN_NORMAL = { 4, "allow_%w", 2, "on_take", 1, "on_put", 1 }
|
||||
local PATTERN_SWAP = { 8, "allow_%w", 4, "on_take", 2, "on_put", 2 }
|
||||
local exec_listing = {} -- List of logged callbacks (e.g. "on_take", "allow_put")
|
||||
|
||||
-- Checks whether the logged callbacks equal the expected pattern
|
||||
core.__helper_check_callbacks = function(expect_swap)
|
||||
local exec_pattern = expect_swap and PATTERN_SWAP or PATTERN_NORMAL
|
||||
local ok = #exec_listing == exec_pattern[1]
|
||||
if ok then
|
||||
local list_index = 1
|
||||
for i = 2, #exec_pattern, 2 do
|
||||
for n = 1, exec_pattern[i + 1] do
|
||||
-- Match the list for "n" occurrences of the wanted callback name pattern
|
||||
ok = exec_listing[list_index]:find(exec_pattern[i])
|
||||
list_index = list_index + 1
|
||||
if not ok then break end
|
||||
end
|
||||
if not ok then break end
|
||||
end
|
||||
end
|
||||
|
||||
if not ok then
|
||||
print("Execution order mismatch!")
|
||||
print("Expected patterns: ", dump(exec_pattern))
|
||||
print("Got list: ", dump(exec_listing))
|
||||
end
|
||||
exec_listing = {}
|
||||
return ok
|
||||
end
|
||||
|
||||
-- Uncomment the other line for easier callback debugging
|
||||
local log = function(...) end
|
||||
--local log = print
|
||||
|
||||
minetest.register_allow_player_inventory_action(function(_, action, inv, info)
|
||||
log("\tallow " .. action, info.count or info.stack:to_string())
|
||||
|
||||
if action == "move" then
|
||||
-- testMoveFillStack
|
||||
return info.count
|
||||
end
|
||||
|
||||
if action == "take" or action == "put" then
|
||||
assert(not info.stack:is_empty(), "Stack empty in: " .. action)
|
||||
|
||||
-- testMoveUnallowed
|
||||
-- testSwapFromUnallowed
|
||||
-- testSwapToUnallowed
|
||||
if info.stack:get_name() == "default:takeput_deny" then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- testMovePartial
|
||||
if info.stack:get_name() == "default:takeput_max_5" then
|
||||
return 5
|
||||
end
|
||||
|
||||
-- testCallbacks
|
||||
if info.stack:get_name():find("default:takeput_cb_%d") then
|
||||
-- Log callback as executed
|
||||
table.insert(exec_listing, "allow_" .. action)
|
||||
return -- Unlimited
|
||||
end
|
||||
end
|
||||
|
||||
return -- Unlimited
|
||||
end)
|
||||
|
||||
minetest.register_on_player_inventory_action(function(_, action, inv, info)
|
||||
log("\ton " .. action, info.count or info.stack:to_string())
|
||||
|
||||
if action == "take" or action == "put" then
|
||||
assert(not info.stack:is_empty(), action)
|
||||
|
||||
if info.stack:get_name():find("default:takeput_cb_%d") then
|
||||
-- Log callback as executed
|
||||
table.insert(exec_listing, "on_" .. action)
|
||||
return
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -26,13 +26,19 @@ do
|
||||
core.print = nil -- don't pollute our namespace
|
||||
end
|
||||
end
|
||||
math.randomseed(os.time())
|
||||
|
||||
do
|
||||
-- Note that PUC Lua just calls srand() which is already initialized by C++,
|
||||
-- but we don't want to rely on this implementation detail.
|
||||
local seed = 1048576 * (os.time() % 1048576)
|
||||
seed = seed + core.get_us_time() % 1048576
|
||||
math.randomseed(seed)
|
||||
end
|
||||
|
||||
minetest = core
|
||||
|
||||
-- Load other files
|
||||
local scriptdir = core.get_builtin_path()
|
||||
local gamepath = scriptdir .. "game" .. DIR_DELIM
|
||||
local clientpath = scriptdir .. "client" .. DIR_DELIM
|
||||
local commonpath = scriptdir .. "common" .. DIR_DELIM
|
||||
local asyncpath = scriptdir .. "async" .. DIR_DELIM
|
||||
|
||||
@@ -42,7 +48,7 @@ dofile(commonpath .. "serialize.lua")
|
||||
dofile(commonpath .. "misc_helpers.lua")
|
||||
|
||||
if INIT == "game" then
|
||||
dofile(gamepath .. "init.lua")
|
||||
dofile(scriptdir .. "game" .. DIR_DELIM .. "init.lua")
|
||||
assert(not core.get_http_api)
|
||||
elseif INIT == "mainmenu" then
|
||||
local mm_script = core.settings:get("main_menu_script")
|
||||
@@ -65,9 +71,12 @@ elseif INIT == "mainmenu" then
|
||||
elseif INIT == "async" then
|
||||
dofile(asyncpath .. "mainmenu.lua")
|
||||
elseif INIT == "async_game" then
|
||||
dofile(commonpath .. "metatable.lua")
|
||||
dofile(asyncpath .. "game.lua")
|
||||
elseif INIT == "client" then
|
||||
dofile(clientpath .. "init.lua")
|
||||
dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua")
|
||||
elseif INIT == "emerge" then
|
||||
dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua")
|
||||
else
|
||||
error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
|
||||
end
|
||||
|
||||
@@ -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.
|
||||
@@ -189,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).
|
||||
@@ -239,6 +233,12 @@ 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.eo.tr
Normal file
246
builtin/locale/__builtin.eo.tr
Normal file
@@ -0,0 +1,246 @@
|
||||
# textdomain: __builtin
|
||||
Invalid parameters (see /help @1).=Nevalidaj parametroj (kontrolu /help @1)
|
||||
Too many arguments, try using just /help <command>=Tro da parametroj, eble provu /help <ordono>
|
||||
Available commands: @1=Nunaj ordonoj: @1
|
||||
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Uzu «/help <ordono>» por specifa informo, aŭ «/help all» por listigi ĉion.
|
||||
Available commands:=Nunaj ordonoj:
|
||||
Command not available: @1=Ordono neuzebla: @1
|
||||
[all | privs | <cmd>] [-t]=[all | privs | <ordono>]
|
||||
Get help for commands or list privileges (-t: output in chat)=Listigi helpon pri ordonoj («all» aŭ <ordono>) aŭ rajtoj («privs») (kun -t: presu en babilejo)
|
||||
Available privileges:=Nunaj rajtoj:
|
||||
Command=Ordono
|
||||
Parameters=Parametroj
|
||||
For more information, click on any entry in the list.=Por pliaj informoj, klaku ajnan listeron.
|
||||
Double-click to copy the entry to the chat history.=Dufoje-klaku por kopii listeron al la babilejo.
|
||||
Command: @1 @2=Ordono: @1 @2
|
||||
Available commands: (see also: /help <cmd>)=Nunaj ordonoj: (vidu ankaŭ: /help <ordono>)
|
||||
Close=Fermi
|
||||
Privilege=Rajto
|
||||
Description=Priskribo
|
||||
Empty command.=Malplena ordono.
|
||||
Invalid command: @1=Nevalida ordono: @1
|
||||
Invalid command usage.=Nevalida ordonouzo.
|
||||
(@1 s)= (@1 s)
|
||||
Command execution took @1 s=Ruliĝo de ordono postulis @1 s
|
||||
You don't have permission to run this command (missing privileges: @1).=Vi ne rajtas uzi tiun ĉi ordonon (mankataj rajtoj: @1)
|
||||
Unable to get position of player @1.=Ne povis akiri pozicion de ludanto @1.
|
||||
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Nevalida loko-formo. Atendis: (x1,y1,z1) (x2,y2,z2)
|
||||
<action>=<ago>
|
||||
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Roli agon en la babilejo (ekz., «/me mendas picon» montras «<ludanto> mendas picon»)
|
||||
Show the name of the server owner=Montri nomon de la serviladministranto
|
||||
The administrator of this server is @1.=La adminstranto de tiu ĉi servilo estas @1.
|
||||
There's no administrator named in the config file.=Estas neniu agordita administranto en la agordodosiero.
|
||||
@1 does not have any privileges.=@1 havas neniun rajton.
|
||||
Privileges of @1: @2=Rajtoj de @1: @2
|
||||
[<name>]=[<nomo>]
|
||||
Show privileges of yourself or another player=Montri rajtojn de vi aŭ alia ludanto
|
||||
Player @1 does not exist.=Ludanto @1 ne ekzistas.
|
||||
<privilege>=<rajto>
|
||||
Return list of all online players with privilege=Listi ĉeretan ludanton kun specifa rajto
|
||||
Invalid parameters (see /help haspriv).=Nevalidaj parametroj (vidu /help haspriv).
|
||||
Unknown privilege!=Nekonata rajto!
|
||||
No online player has the "@1" privilege.=Neniu ĉeretulo havas la rajton «@1».
|
||||
Players online with the "@1" privilege: @2=Ĉeretuloj kun la rajto «@1»: @2
|
||||
Your privileges are insufficient.=Viaj rajtoj ne sufiĉas.
|
||||
Your privileges are insufficient. '@1' only allows you to grant: @2=Viaj rajtoj ne sufiĉas. «@1» sole lasas vin doni: @2
|
||||
Unknown privilege: @1=Nekonata rajto: @1
|
||||
@1 granted you privileges: @2=@1 donis al vi rajtojn: @2
|
||||
<name> (<privilege> [, <privilege2> [<...>]] | all)=<nomo> (<rajto> [, <rajto2> [<...>]] | all)
|
||||
Give privileges to player=Doni rajtojn al ludanto, aŭ aparte, aŭ ĉiun kune («all»)
|
||||
Invalid parameters (see /help grant).=Nevalidaj parametroj (vidu /help grant).
|
||||
<privilege> [, <privilege2> [<...>]] | all=<rajto> [, <rajto2> [<...>]] | all
|
||||
Grant privileges to yourself=Doni rajtojn al vi mem, aŭ aparte, aŭ ĉiun kune («all»)
|
||||
Invalid parameters (see /help grantme).=Nevalidaj parametroj (vidu /help grantme)
|
||||
Your privileges are insufficient. '@1' only allows you to revoke: @2=Viaj rajtoj ne sufiĉas. «@1» sole lasas vin repreni: @2
|
||||
Note: Cannot revoke in singleplayer: @1=Rimarko: Neeblas repreni rajton sole: @1
|
||||
Note: Cannot revoke from admin: @1=Rimarko: Neeblas repreni rajnton de administranto: @1
|
||||
No privileges were revoked.=Neniu rajto reprenita.
|
||||
@1 revoked privileges from you: @2=@1 reprenis rajtojn de vi: @2
|
||||
Remove privileges from player=Repreni rajtojn de ludanto, aŭ aparte, aŭ ĉiun kune («all»)
|
||||
Invalid parameters (see /help revoke).=Nevalidaj parametroj (vidu /help revoke)
|
||||
Revoke privileges from yourself=Repreni rajtojn de vi mem, aŭ aparte, aŭ ĉiun kune («all»)
|
||||
Invalid parameters (see /help revokeme).=Nevalidaj parametroj (vidu /help revokeme).
|
||||
<name> <password>=<nomo> <pasvorto>
|
||||
Set player's password (sent unencrypted, thus insecure)=Agordi pasvorton de ludanto (sendute senĉifre, kaj tiel malsekure)
|
||||
Name field required.=Nomo bezonata.
|
||||
Your password was cleared by @1.=Via pasvorto forviŝiĝis de @1.
|
||||
Password of player "@1" cleared.=Pasvorto de ludanto «@1» forviŝita.
|
||||
Your password was set by @1.=Via pasvorto agordiĝis de @1.
|
||||
Password of player "@1" set.=Pasvorto de ludanto «@1» agordita.
|
||||
<name>=<nomo>
|
||||
Set empty password for a player=Forviŝi pasvorton de ludanto
|
||||
Reload authentication data=Reenlegi aŭtentigajn datumojn
|
||||
Done.=Finite.
|
||||
Failed.=Malsukcese..
|
||||
Remove a player's data=Forviŝi datumojn de ludanto
|
||||
Player "@1" removed.=Ludanto «@1» forigita.
|
||||
No such player "@1" to remove.=Neniu ekzistanta ludanto «@1» forigebla.
|
||||
Player "@1" is connected, cannot remove.=Ludanto «@1» ĉeretas, do ne forigeblas.
|
||||
Unhandled remove_player return code @1.=Netraktata remove_player redonkodo @1.
|
||||
Cannot teleport out of map bounds!=Neeblas teleporti ekstermonden!
|
||||
Cannot get player with name @1.=Neeblas trovi ludanton nomitan «@1».
|
||||
Cannot teleport, @1 is attached to an object!=Ne povas teleporti @1, ĝi estas ligita al objekto!
|
||||
Teleporting @1 to @2.=Teleportante @1 al @2.
|
||||
One does not teleport to oneself.=Oni kutimas ne teleportu al si mem.
|
||||
Cannot get teleportee with name @1.=Ne trovis alteleportaton nomitan @1.
|
||||
Cannot get target player with name @1.=Ne trovis celatan ludanton nomitan @1.
|
||||
Teleporting @1 to @2 at @3.=Teleportante @1 al @2 ĉe @3.
|
||||
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <al_nomo> | <nomo> <X>,<Y>,<Z> | <nomo> <al_nomo>
|
||||
Teleport to position or player=Teleportiĝi al pozicio aŭ ludanto
|
||||
You don't have permission to teleport other players (missing privilege: @1).=Vi ne rajtas teleporti aliajn ludantojn (mankas rajto: @1).
|
||||
([-n] <name> <value>) | <name>=([-n] <nomo> <valoro>) | <nomo>
|
||||
Set or read server configuration setting=Agordi aŭ vidi servilan agordon
|
||||
Failed. Cannot modify secure settings. Edit the settings file manually.=Malsukcese. Neeblas redakti sekurajn agordojn. Redaktu la agordodosieron permane.
|
||||
Failed. Use '/set -n <name> <value>' to create a new setting.=Malsukcese. Uzu «/set -n <nomo> <valoro>» por krei novan agordon.
|
||||
@1 @= @2=@1 @= @2
|
||||
<not set>=<ne agordita>
|
||||
Invalid parameters (see /help set).=Nevalidaj parametroj (vidu /help set).
|
||||
Finished emerging @1 blocks in @2ms.=Finenlegis @1 monderojn dum @2ms.
|
||||
emergeblocks update: @1/@2 blocks emerged (@3%)=emergeblocks ĝisdatigo: @1/@2 monderoj enlegitaj (@3%)
|
||||
(here [<radius>]) | (<pos1> <pos2>)=(here [<radius>]) | (<pos1> <pos2>)
|
||||
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Enlegi (aŭ, laŭnecese, krei) mondopecojn inter la pozicioj poz1 kaj pos2 (<poz1> kaj <poz2> devas esti inter krampoj)
|
||||
Started emerge of area ranging from @1 to @2.=Ekenlegis mondpecojn inter @1 kaj @2.
|
||||
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Forigi mondpecojn inter la pozicioj poz1 kaj poz2 (<poz1> kaj <poz2> devas esti inter krampoj)
|
||||
Successfully cleared area ranging from @1 to @2.=Sukcese forigis mondpecojn inter @1 kaj @2.
|
||||
Failed to clear one or more blocks in area.=Malsukcesis forigi unu aŭ pli da mondpecoj.
|
||||
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Reŝarĝas lumojn inter la pozicioj poz1 kaj poz2 (<poz1> kaj <poz2> devas esti inter krampoj)
|
||||
Successfully reset light in the area ranging from @1 to @2.=Sukcesis reŝarĝi lumojn inter @1 kaj @2.
|
||||
Failed to load one or more blocks in area.=Malsukcesis enlegante unu aŭ pli da monderoj.
|
||||
List mods installed on the server=Listigi modifaĵojn instalitajn de la servilo.
|
||||
No mods installed.=Neniu modifaĵ instalita.
|
||||
Cannot give an empty item.=Neeblas doni neniun portaĵon.
|
||||
Cannot give an unknown item.=Neeblas doni nekonatan portaĵon.
|
||||
Giving 'ignore' is not allowed.=Doni «ignore» estas ne permesita.
|
||||
@1 is not a known player.=@1 ne estas konata ludanto.
|
||||
@1 partially added to inventory.=@1 parte enmetiĝis al portaĵujon.
|
||||
@1 could not be added to inventory.=@1 ne enmetiĝis al portaĵujon.
|
||||
@1 added to inventory.=@1 enmetiĝis al portaĵujon.
|
||||
@1 partially added to inventory of @2.=@1 parte enmetiĝis al portaĵujon de @2.
|
||||
@1 could not be added to inventory of @2.=@1 ne enmetiĝis al portaĵujon de @2.
|
||||
@1 added to inventory of @2.=@1 enmetiĝis al portaĵujon de @2.
|
||||
<name> <ItemString> [<count> [<wear>]]=<nomo> <PortaĵNomo> [<kvanto> <portu>]]
|
||||
Give item to player=Doni portaĵon al ludanto
|
||||
Name and ItemString required.=Nomo kaj PortaĵNomo postualtaj.
|
||||
<ItemString> [<count> [<wear>]]=<PortaĵNomo> [<kvanto> [<portu>]]
|
||||
Give item to yourself=Doni portaĵon al vi mem.
|
||||
ItemString required.=PortaĵNomo postulata.
|
||||
<EntityName> [<X>,<Y>,<Z>]=<EstaĵoNomo> [<X>, <Y>, <Z>]
|
||||
Spawn entity at given (or your) position=Estigi estaĵon ĉe la donita (aŭ la via) pozicio
|
||||
EntityName required.=EstaĵNomo postulata.
|
||||
Unable to spawn entity, player is nil.=Ne povis estigi estaĵon, ĉar ludanto estas nil.
|
||||
Cannot spawn an unknown entity.=Neeblas estigi nekonatan estaĵon.
|
||||
Invalid parameters (@1).=Nevalidaj parametroj (@1).
|
||||
@1 spawned.=@1 naskiĝis.
|
||||
@1 failed to spawn.=@1 ne naskiĝis.
|
||||
Destroy item in hand=Detrui portaĵon enmanan
|
||||
Unable to pulverize, no player.=Ne povis detrui, neniu ludanto.
|
||||
Unable to pulverize, no item in hand.=Ne povis detrui, ĉar nenio estas tenata.
|
||||
An item was pulverized.=Portaĵo detruiĝis.
|
||||
[<range>] [<seconds>] [<limit>]=[<intertempo>] [<sekundoj>] [<limo>]
|
||||
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=Kontroli kiu lastafoje tuŝis monderon aŭ monderon proksiman al ĝi dum
|
||||
Rollback functions are disabled.=Malfaraj funkcioj estas malŝaltitaj.
|
||||
That limit is too high!=Tiu limo troaltas!
|
||||
Checking @1 ...=Kontrolas @1…
|
||||
Nobody has touched the specified location in @1 seconds.=Neniu tuŝis tiun lokon dum @1 sekundoj.
|
||||
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 sekundoj antaŭe.
|
||||
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Frapi monderon (intertempo@=@1, sekundoj@=@2, limo@=@3).
|
||||
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nomo> [<sekundoj>]) | (:<aganto> [<sekundoj>])
|
||||
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Malfari agojn de ludanto. Implicita valoro de <sekundoj> estas 60. Metu <sekundoj> kiel «inf» por neniu tempolimo
|
||||
Invalid parameters. See /help rollback and /help rollback_check.=Nevalidaj parametroj. Vidu /help rollback kaj /help rollback_check.
|
||||
Reverting actions of player '@1' since @2 seconds.=Malfaras agojn de ludanto «@1» ekde @2 sekundoj antaŭ nun.
|
||||
Reverting actions of @1 since @2 seconds.=Malfaras agojn de @1 ekde @2 sekundoj antaŭ nun.
|
||||
(log is too long to show)=(protokolo trolongas por montri)
|
||||
Reverting actions succeeded.=Sukcesis malfari agojn.
|
||||
Reverting actions FAILED.=MALsukcesis malfari agojn.
|
||||
Show server status=Montri staton de servilon.
|
||||
This command was disabled by a mod or game.=Tiun ordonon malŝaltis modifaĵo aŭ ludo.
|
||||
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
|
||||
Show or set time of day=Montri aŭ ŝanĝi la horon
|
||||
Current time is @1:@2.=Nun estas @1:@2.
|
||||
You don't have permission to run this command (missing privilege: @1).=Vi ne rajtas rulu tiun orodnon (mankata rajto: @1).
|
||||
Invalid time (must be between 0 and 24000).=Nevalida tempo (estu inter 0 kaj 24000)
|
||||
Time of day changed.=Horo ŝanĝita.
|
||||
Invalid hour (must be between 0 and 23 inclusive).=Nevalida horo (estu inter 0 kaj 23, inkluzive).
|
||||
Invalid minute (must be between 0 and 59 inclusive).=Nevalida minuto (estu inter 0 kaj 59, inkluzive).
|
||||
Show day count since world creation=Montri pasitajn tagojn ekde mondokreo
|
||||
Current day is @1.=Nuna tago estas @1.
|
||||
[<delay_in_seconds> | -1] [-r] [<message>]=[<prokrasto_sekunde> | -1] [-r] [<mesaĝo>]
|
||||
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Malŝalti servilon (-1 nuligas planitan malŝalton, -r lasas ludantojn rekonektiĝi)
|
||||
Server shutting down (operator request).=Servilo malŝaltiĝas (laŭ prizorganta peto)
|
||||
Ban the IP of a player or show the ban list=Forbari la IP-adreson de ludanto, aŭ listigi forbaritojn
|
||||
The ban list is empty.=La forbaritolisto malplenas.
|
||||
Ban list: @1=Forbaritoj: @1
|
||||
You cannot ban players in singleplayer!=Vi ne povas forbari ludantojn en unuludanta reĝimo!
|
||||
Player is not online.=Ludanto ne ĉeretas.
|
||||
Failed to ban player.=Malsukcesis forbari ludanton.
|
||||
Banned @1.=Forbaris @1.
|
||||
<name> | <IP_address>=<nomo> | <IP_adreso>
|
||||
Remove IP ban belonging to a player/IP=Nuligi IP-forbaron de ludanto/IP
|
||||
Failed to unban player/IP.=Malsukcesis nuligi forbaron de ludanto/IP-adreso
|
||||
Unbanned @1.=Malforbaris @1.
|
||||
<name> [<reason>]=<nomo> [<kialo>]
|
||||
Kick a player=Elĵeti ludanton
|
||||
Failed to kick player @1.=Malsukcesis elĵeti ludanton @1.
|
||||
Kicked @1.=Elĵetis @1.
|
||||
[full | quick]=[full | quick]
|
||||
Clear all objects in world=Forigu ĉiujn lasitajn portaĵojn en la mondo
|
||||
Invalid usage, see /help clearobjects.=Nevalida uzo, vidu /help clearobjects.
|
||||
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Forigante ĉiun lasitan portaĵon. Tio ĉi eble postulos longan tempon. Vi eble malkonektiĝos pro tempo-elĉerpo. (de @1)
|
||||
Cleared all objects.=Forigis ĉiun lasitan portaĵon.
|
||||
<name> <message>=<nomo> <mesaĝo>
|
||||
Send a direct message to a player=Sendu rekte privatan mesaĝon al ludanto
|
||||
Invalid usage, see /help msg.=Nevalida uzo, vidu /help msg.
|
||||
The player @1 is not online.=La ludanto @1 ne ĉeretas.
|
||||
DM from @1: @2=Privata mesaĝo de @1: @2
|
||||
Message sent.=Mesaĝo sendita.
|
||||
Get the last login time of a player or yourself=Vidi la lastan salutotempon de ludanto, aŭ vi mem
|
||||
@1's last login time was @2.=Lasta salutotempo de @1 estas @2.
|
||||
@1's last login time is unknown.=Lasta salutotempo de @1 estas nesciata.
|
||||
Clear the inventory of yourself or another player=Malplenigi la portaĵujon de vi aŭ alia ludanto.
|
||||
You don't have permission to clear another player's inventory (missing privilege: @1).=Vi ne rajtas malplenigi portaĵujon de alia ludanto (mankata rajto: @1).
|
||||
@1 cleared your inventory.=@1 malplenigis vian portaĵujon.
|
||||
Cleared @1's inventory.=Malplenigis portaĵujon de @1.
|
||||
Player must be online to clear inventory!=Por malplenigi onian portaĵujon, tiu devas ĉereti!
|
||||
Players can't be killed, damage has been disabled.=Nemortigeblas ludantoj, ĉar vundado estas malŝaltita.
|
||||
Player @1 is not online.=Ludanto @1 ne ĉeretas.
|
||||
You are already dead.=Vi jam estas mortinta.
|
||||
@1 is already dead.=@1 estas mortinta.
|
||||
@1 has been killed.=@1 estas murdita.
|
||||
Kill player or yourself=Mortigi ludanton aŭ vin mem
|
||||
@1 joined the game.=@1 aliĝis la ludon.
|
||||
@1 left the game.=@1 foriris de la ludo.
|
||||
@1 left the game (timed out).=@1 foriris de la ludo (tempo-elĉerpo)
|
||||
(no description)=(neniu priskribo)
|
||||
Can interact with things and modify the world=Povas interfaci kaj redakti la mondon
|
||||
Can speak in chat=Povas paroli babileje
|
||||
Can modify basic privileges (@1)=Povas redakti bazajn rajtojn (@1)
|
||||
Can modify privileges=Povas redakti rajtojn
|
||||
Can teleport self=Povas teleporti sin
|
||||
Can teleport other players=Povas teleporti aliajn ludantojn
|
||||
Can set the time of day using /time=Povas ŝanĝi la tempon per /time
|
||||
Can do server maintenance stuff=Povas fari servilestrajn aferojn
|
||||
Can bypass node protection in the world=Povas malatenti monderajn protektojn de la mondo
|
||||
Can ban and unban players=Povas forbari kaj malforbari ludantojn
|
||||
Can kick players=Povas elĵeti ludantojn
|
||||
Can use /give and /giveme=Povas uzi /give kaj /giveme
|
||||
Can use /setpassword and /clearpassword=Povas uzi /setpassword kaj /clearpassword
|
||||
Can use fly mode=Povas ŝalti flugan reĝimon
|
||||
Can use fast mode=Povas ŝalti rapidegan reĝimon
|
||||
Can fly through solid nodes using noclip mode=Povas traflugi monderojn per trapasa reĝimo
|
||||
Can use the rollback functionality=Povas uzi malfarajn funkciojn
|
||||
Can enable wireframe=Povas ŝalti ĉirkaŭkadron
|
||||
Unknown Item=Nekonata portaĵo
|
||||
Air=Aero
|
||||
Ignore=Malatenti
|
||||
You can't place 'ignore' nodes!=Vi ne povas meti «malatentajn» monderojn!
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filtrilo>] | dump [<filtrilo>] | save [<formo> [<filtrilo>]] | reset
|
||||
Handle the profiler and profiling data=Trakti la analizilon kaj analizajn datumojn
|
||||
Statistics written to action log.=Analizoj skribitaj al agoprotokolo.
|
||||
Statistics were reset.=Analizoj forviŝitaj.
|
||||
Usage: @1=Uzado: @1
|
||||
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Formo povas estas txt, csv, lua, json, aŭ json_pretty (struktuoj eble iam ŝanĝiĝos).
|
||||
Values below show absolute/relative times spend per server step by the instrumented function.=Valoroj subaj montras la malrelativan/relativan tempon pasigitan de la servilo je ĉiu paŝo de la funkcio.
|
||||
A total of @1 sample(s) were taken.=Sume @1 ekzemplero(j) konserviĝis.
|
||||
The output is limited to '@1'.=La eligo estas limigita al «@1».
|
||||
Saving of profile failed: @1=Konservado de profilo malsukcesis: @1
|
||||
Profile saved to @1=Profilo konservita al @1
|
||||
246
builtin/locale/__builtin.fr.tr
Normal file
246
builtin/locale/__builtin.fr.tr
Normal file
@@ -0,0 +1,246 @@
|
||||
# textdomain: __builtin
|
||||
Invalid parameters (see /help @1).=Paramètres invalides (voir /help @1).
|
||||
Too many arguments, try using just /help <command>=Trop de paramètres, essayez /help <command>
|
||||
Available commands: @1=Commandes disponibles : @1
|
||||
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Essayez '/help <cmd>' pour obtenir plus d'informations, ou '/help all' pour tout lister.
|
||||
Available commands:=Commandes disponibles :
|
||||
Command not available: @1=Commande non disponible : @1
|
||||
[all | privs | <cmd>] [-t]=[all | privs | <cmd>] [-t]
|
||||
Get help for commands or list privileges (-t: output in chat)=Obtenir de l'aide pour les commandes ou pour lister les privilèges (-t : affichage dans le tchat)
|
||||
Available privileges:=Privilèges disponibles :
|
||||
Command=Commande
|
||||
Parameters=Paramètres
|
||||
For more information, click on any entry in the list.=Pour plus d'informations, cliquez sur une entrée dans la liste.
|
||||
Double-click to copy the entry to the chat history.=Double-cliquez pour copier une entrée dans l'historique du tchat.
|
||||
Command: @1 @2=Commande : @1 @2
|
||||
Available commands: (see also: /help <cmd>)=Commandes disponibles : (voir aussi : /help <cmd>)
|
||||
Close=Fermer
|
||||
Privilege=Privilège
|
||||
Description=Description
|
||||
Empty command.=Commande vide.
|
||||
Invalid command: @1=Commande invalide : @1
|
||||
Invalid command usage.=Usage invalide de la commande.
|
||||
(@1 s)= (@1 s)
|
||||
Command execution took @1 s=L'exécution de la commande a pris @1 s.
|
||||
You don't have permission to run this command (missing privileges: @1).=Vous n'avez pas la permission d'exécuter cette commande (privilèges manquants : @1)
|
||||
Unable to get position of player @1.=Impossible d'obtenir la position du joueur @1.
|
||||
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Format incorrect de la zone. Demandé : (x1, y1, z1) (x2, y2, z2)
|
||||
<action>=<action>
|
||||
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Affiche une action de tchat (ex : '/me commande une pizza' affichage '<joueur> commande une pizza')
|
||||
Show the name of the server owner=Affiche le nom du propriétaire du serveur
|
||||
The administrator of this server is @1.=L'administrateur de ce serveur est @1.
|
||||
There's no administrator named in the config file.=Il n'y a pas d'administrateur indiqué dans le fichier de configuration.
|
||||
@1 does not have any privileges.=@1 ne possède aucun privilège.
|
||||
Privileges of @1: @2=Privilège(s) de @1 : @2
|
||||
[<name>]=[<nom>]
|
||||
Show privileges of yourself or another player=Affiche vos privilèges ou ceux d'un autre joueur.
|
||||
Player @1 does not exist.=Le joueur @1 n'existe pas.
|
||||
<privilege>=<privilège>
|
||||
Return list of all online players with privilege=Renvoie la liste de tous les joueurs en ligne avec un privilège.
|
||||
Invalid parameters (see /help haspriv).=Paramètres invalides (voir /help haspriv)
|
||||
Unknown privilege!=Privilège inconnu !
|
||||
No online player has the "@1" privilege.=Aucun joueur en ligne avant le privilège « @1 »
|
||||
Players online with the "@1" privilege: @2=Joueurs en ligne avec le privilège « @1 » : @2
|
||||
Your privileges are insufficient.=Vos privilèges sont insuffisants.
|
||||
Your privileges are insufficient. '@1' only allows you to grant: @2=Vos privilèges sont insuffisants. '@1' vous autorise seulement d'accorder : @2
|
||||
Unknown privilege: @1=Privilège inconnu : @1
|
||||
@1 granted you privileges: @2=@1 vous a accordé les privilèges : @2
|
||||
<name> (<privilege> [, <privilege2> [<...>]] | all)=<nom> (<privilège> [, <privilège2> [<...>]] | all)
|
||||
Give privileges to player=Accorder le privilège au joueur
|
||||
Invalid parameters (see /help grant).=Paramètres invalides (voir /help grant)
|
||||
<privilege> [, <privilege2> [<...>]] | all=<privilège> [, <privilège2> [<...>]] | all
|
||||
Grant privileges to yourself=Accorder des privilèges à vous-même
|
||||
Invalid parameters (see /help grantme).=Paramètres invalides (voir /help grantme)
|
||||
Your privileges are insufficient. '@1' only allows you to revoke: @2=Vos privilèges sont insuffisants. '@1' vous autorise seulement à révoquer : @2
|
||||
Note: Cannot revoke in singleplayer: @1=Note : Impossible de révoquer en solo : @1
|
||||
Note: Cannot revoke from admin: @1=Note : Impossible de révoquer à l'administrateur : @1
|
||||
No privileges were revoked.=Aucun privilège n'a été révoqué.
|
||||
@1 revoked privileges from you: @2=@1 vous a révoqué le privilège : @2
|
||||
Remove privileges from player=Révoquer les privilèges au joueur
|
||||
Invalid parameters (see /help revoke).=Paramètres invalides (voir /help revoke).
|
||||
Revoke privileges from yourself=Révoquer des privilèges à vous-même
|
||||
Invalid parameters (see /help revokeme).=Paramètres invalides (voir /help revokeme).
|
||||
<name> <password>=<nom> <mot de passe>
|
||||
Set player's password (sent unencrypted, thus insecure)=Voir le mot de passe d'un joueur (envoyé non crypté, soit non sécurisé)
|
||||
Name field required.=Le champ « nom » est requis.
|
||||
Your password was cleared by @1.=Votre mot de passe a été effacé par @1.
|
||||
Password of player "@1" cleared.=Mot de passe du joueur « @1 » effacé.
|
||||
Your password was set by @1.=Votre mot de passe a été défini par @1.
|
||||
Password of player "@1" set.=Mot de passe « @1 » défini.
|
||||
<name>=<nom>
|
||||
Set empty password for a player=Définir un mot de passe pour un joueur
|
||||
Reload authentication data=Recharger les données d'authentification
|
||||
Done.=Fait.
|
||||
Failed.=Échoué.
|
||||
Remove a player's data=Supprimer les données d'un joueur
|
||||
Player "@1" removed.=Joueur « @1 » supprimé.
|
||||
No such player "@1" to remove.=Aucun joueur « @1 » à supprimer.
|
||||
Player "@1" is connected, cannot remove.=Le joueur « @1 » est connecté, impossible de supprimer.
|
||||
Unhandled remove_player return code @1.=La commande remove_player non gérée a retourné le code @1.
|
||||
Cannot teleport out of map bounds!=Impossible de téléporter en dehors des limites de la carte !
|
||||
Cannot get player with name @1.=Impossible d'obtenir le joueur @1.
|
||||
Cannot teleport, @1 is attached to an object!=Impossible de téléporter, @1 est lié à un objet !
|
||||
Teleporting @1 to @2.=Téléportation de @1 vers @2.
|
||||
One does not teleport to oneself.=Impossible de se téléporter vers soi-même.
|
||||
Cannot get teleportee with name @1.=Impossible d'obtenir le téléporté @1.
|
||||
Cannot get target player with name @1.=Impossible d'obtenir le joueur cible @1.
|
||||
Teleporting @1 to @2 at @3.=Téléportation de @1 vers @2 à @3.
|
||||
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>, <Y>, <Z> | <vers_nom> | <nom> <X>, <Y>, <Z> | <nom> <vers_nom>
|
||||
Teleport to position or player=Se téléporter vers une position ou un joueur.
|
||||
You don't have permission to teleport other players (missing privilege: @1).=Vous n'avez pas la permission de téléporter des joueurs (privilège manquant : @1).
|
||||
([-n] <name> <value>) | <name>=([-n] <nom> <valeur>) | <nom>
|
||||
Set or read server configuration setting=Définir ou lire un paramètre de configuration du serveur.
|
||||
Failed. Cannot modify secure settings. Edit the settings file manually.=Échoué. Impossible de modifier les paramètres sécurisés. Éditez le fichier manuellement.
|
||||
Failed. Use '/set -n <name> <value>' to create a new setting.=Échoué. Utilisez '/set -n <nom> <valeur>' pour créer un nouveau paramètre.
|
||||
@1 @= @2=@1 @= @2
|
||||
<not set>=<non défini>
|
||||
Invalid parameters (see /help set).=Paramètres invalides (voir /help set).
|
||||
Finished emerging @1 blocks in @2ms.=Fini de générer @1 blocks en @2 ms.
|
||||
emergeblocks update: @1/@2 blocks emerged (@3%)=Mise à jour de emergeblocks : @1/@2 de blocs générés (@3%)
|
||||
(here [<radius>]) | (<pos1> <pos2>)=(here [<rayon>]) | (<pos1> <pos1>)
|
||||
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Charger (ou, si inexistant, générer), des blocs contenus dans la zone de pos1 à pos2 (<pos1> et <pos2> doivent être entre parenthèses)
|
||||
Started emerge of area ranging from @1 to @2.=Début de la génération de la zone de @1 à @2.
|
||||
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Supprimer les blocs contenus dans la zone de pos1 à pos2 (<pos1> et <pos2> doivent être entre parenthèses)
|
||||
Successfully cleared area ranging from @1 to @2.=La zone de @1 à @2 a été nettoyée avec succès.
|
||||
Failed to clear one or more blocks in area.=Le nettoyage d'un ou plusieurs blocs dans la zone a echoué.
|
||||
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Réinitialiser l'éclairage dans la zone de pos1 à pos2 (<pos1> et <pos2> doivent être entre parenthèses)
|
||||
Successfully reset light in the area ranging from @1 to @2.=L'éclairage dans la zone de @1 à @2 a été réinitialisé avec succès.
|
||||
Failed to load one or more blocks in area.=Le chargement d'un ou plusieurs blocs dans la zone a échoué.
|
||||
List mods installed on the server=Liste les modules installés sur le serveur.
|
||||
No mods installed.=Aucun module installé.
|
||||
Cannot give an empty item.=Impossible de donner un objet vide.
|
||||
Cannot give an unknown item.=Impossible de donner un objet inconnu.
|
||||
Giving 'ignore' is not allowed.=Donner 'ignore' n'est pas autorisé.
|
||||
@1 is not a known player.=Le joueur @1 est inconnu.
|
||||
@1 partially added to inventory.=@1 été partiellement rajouté à l'inventaire.
|
||||
@1 could not be added to inventory.=@1 n'a pas pu être rajouté à l'inventaire.
|
||||
@1 added to inventory.=@1 a été rajouté à l'inventaire.
|
||||
@1 partially added to inventory of @2.=@1 a été partiellement rajouté à l'inventaire de @2.
|
||||
@1 could not be added to inventory of @2.=@1 n'a pas pu être rajouté à l'inventaire de @2.
|
||||
@1 added to inventory of @2.=@1 a été rajouté à l'inventaire de @2.
|
||||
<name> <ItemString> [<count> [<wear>]]=<nom> <CodeObjet> [<nombre> [<usure>]]
|
||||
Give item to player=Donner un objet à un joueur
|
||||
Name and ItemString required.=Le nom et le code de l'objet sont requis
|
||||
<ItemString> [<count> [<wear>]]=<CodeObjet> [<nombre> [<usure>]]
|
||||
Give item to yourself=Donner un objet à vous-même
|
||||
ItemString required.=Code objet requis.
|
||||
<EntityName> [<X>,<Y>,<Z>]=<NomEntité> [<X>, <Y>, <Z>]
|
||||
Spawn entity at given (or your) position=Faire apparaître une entité à une position donnée (ou la vôtre)
|
||||
EntityName required.=Nom de l'entité requis.
|
||||
Unable to spawn entity, player is nil.=Impossible de faire apparaître l'entité, le joueur est inexistant.
|
||||
Cannot spawn an unknown entity.=Impossible de faire apparaître une entité inconnue.
|
||||
Invalid parameters (@1).=Paramètres invalides (@1).
|
||||
@1 spawned.=@1 a apparu.
|
||||
@1 failed to spawn.=@1 a échoué à apparaître.
|
||||
Destroy item in hand=Détruire l'objet dans la main
|
||||
Unable to pulverize, no player.=Impossible de détruire, pas de joueur.
|
||||
Unable to pulverize, no item in hand.=Impossible de détruire, pas d'objet dans la main.
|
||||
An item was pulverized.=Un objet a été détruit.
|
||||
[<range>] [<seconds>] [<limit>]=[<rayon>] [<secondes>] [<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=Vérifier qui a le dernier touché un nœud ou un autre aux environs dans le temps spécifié par <secondes>. Par défaut : rayon @= 0, secondes @= 86400 @= 24h, limite @= 5. Définissez <secondes> à inf pour aucune limite de temps.
|
||||
Rollback functions are disabled.=Les fonctions retour sont désactivées.
|
||||
That limit is too high!=Cette limite est trop grande !
|
||||
Checking @1 ...=Vérification de @1 ...
|
||||
Nobody has touched the specified location in @1 seconds.=Personne n'as touché la position spécificée dans les dernières @1 secondes.
|
||||
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 il y a @5 secondes.
|
||||
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Taper un nœud (rayon @= @1, secondes @= @2, limite @= @3).
|
||||
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nom> [<secondes>]) | (:<acteur> [<secondes>])
|
||||
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Annuler les actions d'un joueur. La valeur par défaut de <secondes> est 60. Définissez <secondes> à inf pour aucune limite de temps.
|
||||
Invalid parameters. See /help rollback and /help rollback_check.=Paramètres invalides. Voir /help rollback et /help rollback_check.
|
||||
Reverting actions of player '@1' since @2 seconds.=Annuler les actions du joueur '@1' depuis @2 secondes.
|
||||
Reverting actions of @1 since @2 seconds.=Annuler les actions de @1 depuis @2 secondes.
|
||||
(log is too long to show)=(le journal est trop long à afficher)
|
||||
Reverting actions succeeded.=Les actions ont été annulées avec succès.
|
||||
Reverting actions FAILED.=L'annulation des actions a échoué.
|
||||
Show server status=Afficher le statut du serveur.
|
||||
This command was disabled by a mod or game.=Cette commande a été désactivée par un module ou un jeu.
|
||||
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0.24000>]
|
||||
Show or set time of day=Afficher ou définir l'heure du jour.
|
||||
Current time is @1:@2.=L'heure actuelle est @1:@2.
|
||||
You don't have permission to run this command (missing privilege: @1).=Vous n'avez pas la permission d'exécuter cette commande (privilège manquant : @1)
|
||||
Invalid time (must be between 0 and 24000).=Heure invalide (doit être comprise entre 0 et 24000).
|
||||
Time of day changed.=L'heure du jour a changé.
|
||||
Invalid hour (must be between 0 and 23 inclusive).=Heure invalide (doit être comprise entre 0 et 23 inclus).
|
||||
Invalid minute (must be between 0 and 59 inclusive).=Minute invalide (doit être comprise entre 0 et 59 inclus).
|
||||
Show day count since world creation=Afficher le nombre de jours écoulés depuis la création du monde.
|
||||
Current day is @1.=Le jour actuel est @1.
|
||||
[<delay_in_seconds> | -1] [-r] [<message>]=[<délai_en_secondes> | -1] [-r] [<message>]
|
||||
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Éteindre le serveur (-1 annule une extinction programmée, -r autorise les joueurs à se reconnecter)
|
||||
Server shutting down (operator request).=Extinction du serveur (requête de l'opérateur).
|
||||
Ban the IP of a player or show the ban list=Bannir l'IP d'un joueur ou affiche la liste des bans.
|
||||
The ban list is empty.=La liste des bans est vide.
|
||||
Ban list: @1=Liste de bans : @1
|
||||
You cannot ban players in singleplayer!=Vous ne pouvez pas bannir des joueurs en solo !
|
||||
Player is not online.=Le joueur n'est pas en ligne.
|
||||
Failed to ban player.=Le bannissement du joueur a échoué.
|
||||
Banned @1.=@1 a été banni.
|
||||
<name> | <IP_address>=<nom> | <adresse_IP>
|
||||
Remove IP ban belonging to a player/IP=Rétablir un IP appartenant à un joueur ou une adresse IP.
|
||||
Failed to unban player/IP.=Le rétablissement du joueur ou de l'adresse IP a échoué.
|
||||
Unbanned @1.=@1 a été rétabli.
|
||||
<name> [<reason>]=<nom> [<motif>]
|
||||
Kick a player=Expulser un joueur
|
||||
Failed to kick player @1.=L'expulsion du joueur @1 a échoué.
|
||||
Kicked @1.=@1 a été expulsé.
|
||||
[full | quick]=[full | quick]
|
||||
Clear all objects in world=Nettoyer tous les objets dans le monde
|
||||
Invalid usage, see /help clearobjects.=Usage invalide, voir /help clearobjects.
|
||||
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Nettoyage de tous les objects. Cela peut prendre du temps. Une inactivité peut surveni (par @1).
|
||||
Cleared all objects.=Tous les objets ont été nettoyés.
|
||||
<name> <message>=<nom> <message>
|
||||
Send a direct message to a player=Envoyer un message privé à un joueur.
|
||||
Invalid usage, see /help msg.=Usage invalide, voir /help msg.
|
||||
The player @1 is not online.=Le joueur @1 n'est pas en ligne.
|
||||
DM from @1: @2=Message privé de @1 : @2
|
||||
Message sent.=Message privé envoyé.
|
||||
Get the last login time of a player or yourself=Obtenir l'horodatage de la dernière connexion d'un joueur ou de vous-même.
|
||||
@1's last login time was @2.=@1 s'est connecté pour la dernière fois au @2.
|
||||
@1's last login time is unknown.=L'horodatage de la dernière connexion de @1 est inconnu.
|
||||
Clear the inventory of yourself or another player=Vider votre inventaire ou celui d'un autre joueur.
|
||||
You don't have permission to clear another player's inventory (missing privilege: @1).=Vous n'avez pas la permission de vider l'inventaire d'un autre joueur (privilège manquant : @1).
|
||||
@1 cleared your inventory.=@1 a vidé votre inventaire.
|
||||
Cleared @1's inventory.=L'inventaire de @1 a été vidé.
|
||||
Player must be online to clear inventory!=Le joueur doit être en ligne pour pouvoir vider son inventaire.
|
||||
Players can't be killed, damage has been disabled.=Les joueurs ne peuvent pas être tués, les dommages ont été désactivés.
|
||||
Player @1 is not online.=Le joueur @1 n'est pas en ligne.
|
||||
You are already dead.=Vous êtes déjà mort.
|
||||
@1 is already dead.=@1 est déjà mort.
|
||||
@1 has been killed.=@1 a été tué.
|
||||
Kill player or yourself=Tuer un joueur ou vous-même.
|
||||
@1 joined the game.=@1 a rejoint la partie.
|
||||
@1 left the game.=@1 a quitté la partie.
|
||||
@1 left the game (timed out).=@1 a quitté la partie (inactivité).
|
||||
(no description)=(sans description)
|
||||
Can interact with things and modify the world=Peut interagir avec des éléments ou modifier le monde.
|
||||
Can speak in chat=Peut écrire dans le tchat.
|
||||
Can modify basic privileges (@1)=Peut modifier les privilèges basiques (@1)
|
||||
Can modify privileges=Peut modifier les privilèges
|
||||
Can teleport self=Peut se téléporter
|
||||
Can teleport other players=Peut téléporter d'autres joueurs
|
||||
Can set the time of day using /time=Peut définir l'heure du jour avec /time
|
||||
Can do server maintenance stuff=Peut effectuer des tâches de maintenance du serveur
|
||||
Can bypass node protection in the world=Peut outrepasser la protection des nœuds dans le monde.
|
||||
Can ban and unban players=Peut bannir et rétablir des joueurs.
|
||||
Can kick players=Peut expulser des joueurs.
|
||||
Can use /give and /giveme=Peut utiliser /give et /giveme
|
||||
Can use /setpassword and /clearpassword=Peut utiliser /setpassword et /clearpassword
|
||||
Can use fly mode=Peut utiliser le mode vol
|
||||
Can use fast mode=Peut utiliser le mode rapide
|
||||
Can fly through solid nodes using noclip mode=Peut voler à travers des nœuds solides en utilisant le mode de collisions désactivées.
|
||||
Can use the rollback functionality=Peut utiliser la fonctionnalité de retour
|
||||
Can enable wireframe=Peut activer la vue fil de fer
|
||||
Unknown Item=Objet inconnu
|
||||
Air=Air
|
||||
Ignore=Ignorer
|
||||
You can't place 'ignore' nodes!=Vous ne pouvez pas placé de nœuds 'ignorés' !
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filtre>] | dump [<filtre>] | save [<format> [<filtre>]] | reset
|
||||
Handle the profiler and profiling data=Traiter le profileur et les données de profilage
|
||||
Statistics written to action log.=Les statistiques sont écrites dans les journaux d'actions.
|
||||
Statistics were reset.=Les statistiques ont été réinitialisées.
|
||||
Usage: @1=Usage : @1
|
||||
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Le format peut être txt, csv, lua, json ou json_pretty (les structures sont sujettes au changement).
|
||||
Values below show absolute/relative times spend per server step by the instrumented function.=Les valeurs inférieures affichent les temps absolu et relatif dépensés par étape du serveur par la fonction utilisée.
|
||||
A total of @1 sample(s) were taken.=@1 échantillons ont été collectés.
|
||||
The output is limited to '@1'.=La sortie est limitée à '@1'.
|
||||
Saving of profile failed: @1=La sauvegarde du profil a échoué : @1
|
||||
Profile saved to @1=Le profil a été sauvegardé dans @1
|
||||
@@ -1,9 +1,27 @@
|
||||
# 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
|
||||
(@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)
|
||||
@@ -189,30 +207,6 @@ You are already dead.=Anda telah mati.
|
||||
@1 is already dead.=@1 telah mati.
|
||||
@1 has been killed.=@1 telah dibunuh.
|
||||
Kill player or yourself=Bunuh pemain atau diri Anda
|
||||
Invalid parameters (see /help @1).=Parameter tidak sah (lihat /help @1).
|
||||
Too many arguments, try using just /help <command>=Terlalu banyak argumen. Coba hanya gunakan /help <perintah>
|
||||
Available commands: @1=Perintah yang tersedia: @1
|
||||
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Gunakan '/help <perintah>' untuk informasi lebih lanjut atau '/help all' untuk melihat daftar semuanya.
|
||||
Available commands:=Perintah yang tersedia:
|
||||
Command not available: @1=Perintah tidak tersedia: @1
|
||||
[all | privs | <cmd>] [-t]=[all | privs | <perintah>] [-t]
|
||||
Get help for commands or list privileges (-t: output in chat)=Ambil bantuan untuk perintah atau daftar hak (-t: keluaran di obrolan)
|
||||
Available privileges:=Hak yang ada:
|
||||
Command=Perintah
|
||||
Parameters=Parameter
|
||||
For more information, click on any entry in the list.=Untuk informasi lebih lanjut, klik pada entri apa pun dalam daftar.
|
||||
Double-click to copy the entry to the chat history.=Klik ganda untuk menyalin entri ke riwayat obrolan.
|
||||
Command: @1 @2=Perintah: @1 @2
|
||||
Available commands: (see also: /help <cmd>)=Perintah yang tersedia: (lihat juga: /help <perintah>)
|
||||
Close=Tutup
|
||||
Privilege=Hak
|
||||
Description=Deskripsi
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset
|
||||
Handle the profiler and profiling data=Menangani profiler dan data profiling
|
||||
Statistics written to action log.=Statistik ditulis ke log action.
|
||||
Statistics were reset.=Statistik diatur ulang.
|
||||
Usage: @1=Penggunaan: @1
|
||||
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Format berupa salah satu dari txt, csv, lua, json, json_pretty (struktur mungkin berubah).
|
||||
@1 joined the game.=@1 bergabung dalam permainan.
|
||||
@1 left the game.=@1 keluar permainan.
|
||||
@1 left the game (timed out).=@1 keluar permainan (kehabisan waktu).
|
||||
@@ -239,6 +233,12 @@ 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'.
|
||||
|
||||
@@ -1,78 +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 (sent unencrypted, thus insecure)=
|
||||
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.=
|
||||
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>
|
||||
@@ -89,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.
|
||||
@@ -101,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ǝ
|
||||
@@ -109,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
|
||||
@@ -140,121 +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
|
||||
You cannot ban players in singleplayer!=
|
||||
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 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 #####
|
||||
|
||||
Set player's password=Imposta la password del giocatore
|
||||
Invalid time.=Orario non valido.
|
||||
[all | privs | <cmd>]=[all | privs | <comando>]
|
||||
Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi
|
||||
Allows enabling various debug options that may affect gameplay=Permette di abilitare varie opzioni di debug che potrebbero influenzare l'esperienza di gioco
|
||||
[<delay_in_seconds> | -1] [reconnect] [<message>]=[<ritardo_in_secondi> | -1] [reconnect] [<messaggio>]
|
||||
Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato)
|
||||
<name> (<privilege> | all)=<nome> (<privilegio> | all)
|
||||
<privilege> | all=<privilegio> | all
|
||||
Can modify 'shout' and 'interact' privileges=Si possono modificare i privilegi 'shout' e 'interact'
|
||||
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
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
# 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.
|
||||
@@ -189,30 +207,6 @@ You are already dead.=Anda sudah pun mati.
|
||||
@1 is already dead.=@1 sudah pun mati.
|
||||
@1 has been killed.=@1 telah berjaya dibunuh.
|
||||
Kill player or yourself=Bunuh pemain atau diri sendiri
|
||||
Invalid parameters (see /help @1).=Parameter tidak sah (sila lihat /help @1).
|
||||
Too many arguments, try using just /help <command>=Terlalu banyak argumen, cuba guna /help <perintah> sahaja
|
||||
Available commands: @1=Perintah yang tersedia: @1
|
||||
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Gunakan '/help <perintah>' untuk maklumat lanjut, atau '/help all' untuk senaraikan kesemuanya.
|
||||
Available commands:=Perintah yang tersedia:
|
||||
Command not available: @1=Perintah tidak tersedia: @1
|
||||
[all | privs | <cmd>] [-t]=[all | privs | <perintah>] [-t]
|
||||
Get help for commands or list privileges (-t: output in chat)=Dapatkan bantuan untuk perintah atau senaraikan keistimewaan (-t: output dalam sembang)
|
||||
Available privileges:=Keistimewaan yang tersedia:
|
||||
Command=Perintah
|
||||
Parameters=Parameter
|
||||
For more information, click on any entry in the list.=Untuk maklumat lanjut, klik pada mana-mana entri dalam senarai.
|
||||
Double-click to copy the entry to the chat history.=Klik dua kali untuk salin entri ke sejarah sembang.
|
||||
Command: @1 @2=Perintah: @1 @2
|
||||
Available commands: (see also: /help <cmd>)=Perintah yang tersedua: (sila lihat juga: /help <perintah>)
|
||||
Close=Tutup
|
||||
Privilege=Keistimewaan
|
||||
Description=Keterangan
|
||||
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<tapisan>] | dump [<tapisan>] | save [<format> [<tapisan>]] | reset
|
||||
Handle the profiler and profiling data=Uruskan pembukah dan data pembukahan
|
||||
Statistics written to action log.=Statistik telah ditulis ke log perlakuan.
|
||||
Statistics were reset.=Statistik telah ditetapkan semula.
|
||||
Usage: @1=Kegunaan: @1
|
||||
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Format boleh jadi salah satu daripada txt, csv, lua, json, json_pretty (struktur boleh berubah kemudian).
|
||||
@1 joined the game.=@1 telah menyertai permainan.
|
||||
@1 left the game.=@1 telah meninggalkan permainan.
|
||||
@1 left the game (timed out).=@1 telah meninggalkan permainan (tamat tempoh masa).
|
||||
@@ -239,6 +233,12 @@ 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'.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user